Ruby - Struct vs. OpenStruct
Intro
Ruby has a lot of useful data structures. In this blog, we’ll explore Struct
and OpenStruct
. First, we’ll learn how they work by implementing each of them in Ruby. Dont worry, it’s easier than you think.
After than, we’ll summarize the differences, and examine when you should use one or the other.
If you just want to see when to use one or the other, skip to the Summary.
Struct
Let’s define it:
Ruby’s official documentation says:
a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.”
How does it work?
Struct
is essentially a shortcut for building simple classes with attributes and maybe a method or two. Let’s see how it’s used:
User = Struct.new(:first, :last) do
def email
"#{first}.#{last}@mrod.space"
end
end
mike = User.new("Michael", "Rodrigues")
mike.first
#=> "Michael"
mike.last
#=> "Rodrigues"
mike.email
#=> "Michael.Rodrigues@mrod.space"
mike.class
#=> User
If the example looks familiar, I basically ripped it right out of the Ruby core documentation. Make a mental note of that; Struct
is part of Ruby core. Its source code is written in C. This is in contrast to OpenStruct
which is part of the standard library. It’s written in Ruby and you have to require
it before you use it. You don’t need to do anything to use Struct
.
Now, back to Struct
. It might just be easier to show you a class that gives you what Struct
gives you with much less work:
class User
attr_accessor :first
attr_accessor :last
def initialize(first, last)
@first = first
@last = last
end
def email
"#{first}.#{last}@mrod.space"
end
end
mike.first
#=> "Michael"
mike.last
#=> "Rodrigues"
mike.email
#=> "Michael.Rodrigues@mrod.space"
mike.class
#=> User
Instantiated objects of our User
class above behave exactly like instantiations of our User
Struct
in the example before it. Remember, Struct.new
returns a class, keep this in mind as we transition our focus to OpenStruct
.
OpenStruct
Let’s define it:
The official Ruby documentation says:
a data structure, similar to a
Hash
, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.
How does it work?
Unlike Struct
, OpenStruct
isn’t going to define a new class to instantiate objects from. However, it will let you arbitrarily define new attributes on the fly. Let’s look a tweaked example from the Ruby documentation:
require "ostruct" # remember, part of std lib, not core
event = OpenStruct.new
event.timestamp = Time.now
event.message = "Something notable happened."
event.timestamp
#=> 2019-08-29 23:20:29 -0700
event.message
#=> "Something Notable happened."
event.sender
#=> nil
event.sender = "Michael"
event.sender
#=> "Michael"
We instantiate our OpenStruct
object, event
, and instantly set two attributes on it. Notice we didn’t define them beforehand, we just set them at will. We can easily get them by name.
You can also pass in a Hash
at instantiation:
require "ostruct"
event = OpenStruct.new(
{
timestamp: Time.now,
message: "Short message.",
sender: "Michael"
}
)
event.sender
#=> "Michael"
Summary
So, in summary:
Struct.new
returns a class with fixed attributesOpenStruct.new
returns an instance ofOpenStruct
, attributes can be added and removed after instantiationStruct
is part of Ruby core, written in COpenStruct
is part of the Ruby Standard Library, written in RubyStruct
is instantiated with a class name, list of attributes, and optional methodsOpenStruct
can be instantiated with aHash
Struct
sacrifices flexibility for performanceOpenStruct
sacrifices some performance for flexibility and ease of use
When to use
Use Struct
when:
- speed is a concern
- you know all of your attributes at instantiation
Use OpenStruct
when:
- speed is not a concern
- you don’t know all of your attributes at instantiation