DEV Community

Burdette Lamar
Burdette Lamar

Posted on

All About Ruby OpenStruct

What's an OpenStruct?

From the documentation for Ruby's OpenStruct class:

An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values.

It's much like a Struct, but with more bells and whistles: you can, for example, add and delete attributes.

To demonstrate OpenStruct, I'm going to use Ruby and the Ruby Interactive Shell, irb, beginning with their versions:

p `ruby --version`.chomp
"ruby 2.6.3p62 (2019-04-16 revision 67580) [x64-mingw32]"
p `irb --version`.chomp
"irb 1.0.0 (2018-12-18)"

Read on:

The Basics

Make OpenStruct available (and show its superclass):

require 'ostruct'
p OpenStruct.superclass
Object

Create an OpenStruct object, then display it (method :to_s is an alias of method :inspect):

person = OpenStruct.new(:name => 'Burdette Lamar', :city => 'Houston', :state => 'TX')
puts person.to_s
#<OpenStruct name="Burdette Lamar", city="Houston", state="TX">

An undefined attribute returns nil, and does not raise an exception:

p person.country
nil

Add an attribute, by assigning to it:

person.country = 'USA'
p person.country
"USA"

Remove an attribute (method :delete_field returns the deleted value):

p person.delete_field(:country)
"USA"
p person
#<OpenStruct name="Burdette Lamar", city="Houston", state="TX">

Setting a attribute's value to nil does not delete the attribute.

person.state = nil
p person
#<OpenStruct name="Burdette Lamar", city="Houston", state=nil>

Accessing Attributes

Create an attribute with an accessor method, or method :[]= with an augument (symbol or string):

p person.hair_color = :gray
:gray
p person[:eye_color] = :brown
:brown
p person['hair_style'] = :ponytail
:ponytail

Get an attribute's value with its accessor method or method :[] with an argument:

p person.hair_color
:gray
p person[:eye_color]
:brown
p person['hair_style']
:ponytail

Change an attribute's value with its accessor method or method :[]= and an argument:

p person.hair_color = :silver
:silver
p person[:eye_color] = :dark_brown
:dark_brown
p person['hair_style'] = :tie_back
:tie_back

Weird names are allowed:

p person[:'Weight (pounds)'] = 191
191
p person[:'Weight (pounds)']
191
p person['Weight (pounds)']
191
p person.send(:'Weight (pounds)')
191

Get all attributes as a hash.

p person.to_h
{:name=>"Burdette Lamar", :city=>"Houston", :state=>nil, :hair_color=>:silver, :eye_color=>:dark_brown, :hair_style=>:tie_back, :"Weight (pounds)"=>191}

Process all attributes into a hash:

p person.to_h {|name, value| [name.to_s, value.to_s] }
{"name"=>"Burdette Lamar", "city"=>"Houston", "state"=>"", "hair_color"=>"silver", "eye_color"=>"dark_brown", "hair_style"=>"tie_back", "Weight (pounds)"=>"191"}

A new OpenStruct instance has no methods:

person = OpenStruct.new(:name => 'Burdette Lamar', :city => 'Houston', :state => 'TX')
p person.methods(false)
[]

Accessing an attribute with method :[] does not create accessor methods:

p person[:name]
"Burdette Lamar"
p person['name']
"Burdette Lamar"
p person.methods(false)
[]

But accessing it with method :[]= does create accessor methods:

person[:name] = 'Lamar, Burdette'
p person.methods(false)
[:name, :name=]

As does accessing an attribute with either of its accessor methods:

p person.state
"TX"
p person.methods(false)
[:state=, :name=, :name, :state]
person.city = 'Boston'
p person.methods(false)
[:city=, :state=, :name=, :name, :state, :city]

Dig for content in objects that support method :dig:

MyStruct = Struct.new(:foo)
ostruct = OpenStruct.new(
        :bar => MyStruct.new(
              Array.new([
                                Hash.new(
                                      :baz => 'Bag'
                                )
                          ])
        )
  )
p ostruct.dig(:bar, :foo, 0, :baz)
{:baz=>"Bag"}

Freeze an OpenStruct object:

ostruct = OpenStruct.new(:a => 0)
p ostruct.frozen?
false
ostruct.freeze
p ostruct.frozen?
true

For the Marshal library:

ostruct = OpenStruct.new(:a => 0, :b => 1)
p ostruct
#<OpenStruct a=0, b=1>
x = ostruct.marshal_dump
p x
{:a=>0, :b=>1}
ostruct.marshal_load(x)
p ostruct
#<OpenStruct a=0, b=1>

Iteration

Iterate with method :each_pair:

person.each_pair do |name, value|
  p [name, value]
end
[:name, "Lamar, Burdette"]
[:city, "Boston"]
[:state, "TX"]

Get an enumerator for the attributes.:

enum = person.each_pair
p enum
#<Enumerator: #<OpenStruct name="Lamar, Burdette", city="Boston", state="TX">:each_pair>

More Ways to Create an OpenStruct Object

Create an OpenStruct from a Struct:

Person = Struct.new(:name, :city, :state)
struct = Person.new('Burdette Lamar', 'Houston', 'TX')
ostruct = OpenStruct.new(struct)
p ostruct
#<OpenStruct name="Burdette Lamar", city="Houston", state="TX">

Create an OpenStruct from another OpenStruct:

another_ostruct = OpenStruct.new(ostruct)
p another_ostruct
#<OpenStruct name="Burdette Lamar", city="Houston", state="TX">

Create an empty OpenStruct:

ostruct = OpenStruct.new
p ostruct
#<OpenStruct>

Equality

A couple of OpenStruct instances to compare:

a = OpenStruct.new(:a => 0, :b => 1)
b = OpenStruct.new(:b => 1, :a => 0)
p a
#<OpenStruct a=0, b=1>
p b
#<OpenStruct b=1, a=0>

Methods :== and :eql? test equality.

p a == b
true
p a.eql?(b)
true

Method :hash returns an integer value, not a hash:

p a.hash
664573704
p b.hash
664573704

More

Top comments (0)