Ruby’s OpenStruct: An Introduction

by James

OpenStructs are magic — they’re like hashes, but far less fussy about what methods you throw at them. The key feature of OpenStructs is this: they allow you to arbitrarily set and access attributes for your models, on the fly.

Here’s an example:

require 'ostruct'

s = OpenStruct.new
s.name # => nil
s.name = 'James'
s.name # => 'James'

Note that we don’t need to initialise the ‘name’ attribute: we just set the value and Ruby takes care of the rest. Want to add a new attribute? Easy.

s.age # => nil
s.age = 29
s.age # => 29

We can add any attribute we want to our objects, on the fly, and different instances of the OpenStruct class can have different attributes. Importantly, we don’t have to worry about what methods we send to our OpenStructs, because anything that doesn’t have a value just returns nil 1:

s.location # => nil
s.hobbies # => nil

Clearing Values

To unset a value, we can do one of two things: in most cases, it’s the obvious…

2.age # => 29
s.age = nil
s.age # => nil

…but to grab the value before setting the attribute back to nil, we can use delete_field 2:

# Returns 29, and sets 'age' to nil
age = s.delete_field('age')
age # => 29
s.age # => nil

Initialising With Values

We can create new OpenStruct objects with attribute/value pairs by providing a hash:

s = OpenStruct.new(:name => 'James', :occupation => 'Rubyist')
s.name # => 'James'
s.occupation # => 'Rubyist'

How It Works

Behind the scenes, Ruby uses a hash to implement OpenStruct’s easy access so we get all the flexibility of a hash with a nice, clean, method-style access laid on top. Don’t worry about the details: just throw whatever you want into your OpenStructs, and let Ruby take care of your data.

Advanced Initialization

If you want object with the flexibility of OpenStructs but with default values for some attributes then something like the following would work:

require 'ostruct'

class MyStruct < OpenStruct
  # Hard-code age and status
  def initialize(hash = {})
    super hash.merge(:age => 29, :role => 'Manager')
  end
end

s = MyStruct.new(:name => 'Bob')
s.name # => 'Bob'
s.age # => 29
s.role # => 'Manager'

# We can override the default values
s = MyStruct.new(:age => 45)
s.age # => 45

OpenStruct vs Struct

Ruby’s Struct is subtly, but importantly, different to OpenStruct. How to use Struct is another blog post, but put simply Structs are less flexible. Check the Struct docs for more info :)

Notes:

  1. This can be a curse as well as a blessing, because you’ll often get nil when you’d expect Ruby to raise a NoMethodError. Checkout OpenStruct’s method_missing to see what’s going on behind the scenes.
  2. This is one of OpenStruct’s few built-in instance methods which don’t act as attribute accessors. Check the docs for details.