Sandip Mane
by Sandip Mane
2 min read

Categories

  • Ruby

Tags

  • data structure
  • ruby

In Ruby, OpenStruct is a data structure, similar to Hash. With this, we can define attributes with values (key value pairs).

OpenStruct is a class which uses ruby’s metaprogramming for its implementation, since its not part of the ruby core, we have to require ostruct library which defines it.

> require "ostruct"

Usage

Here is how we use it and other important methods

> user = OpenStruct.new(name: "Sandip")
> user.age = 200

> user.name       # => "Sandip"
> user[:name]     # => "Sandip"
> user["name"]    # => "Sandip"

> user.age        # => 200, same as [:age] or ["age"]
> user.to_h       # => { :name => "Sandip", :age => 200 }

delete_field

> user
# => #<OpenStruct name="Sandip", age=200>

> user.delete_field(:age) # alternatively use String i.e. "age"
> user
# => #<OpenStruct name="Sandip">

eql?

OpenStruct will be equal when the item that it is matching against is OpenStruct and has same attributes.

To compare objects OpenStruct uses hash method, the hash is computed from the attributes, hence two OpenStructs with same attributes will have same hash.

> user_1 = OpenStruct.new(name: "Sandip", age: 200)
> user_2 = OpenStruct.new("name" => "Sandip", :age => "200")

> user_1.eql? user_2      # => false

> user_2.age = 200

> user_1.eql? user_2      # => true
> user_1 == user_2        # => true

> user_1.hash             # => 3725528521673775168
> user_2.hash             # => 3725528521673775168

> user_1.eql? 3725528521673775168   # => false

dig

> office_address  = OpenStruct.new(city: "Pune", country: "IN")
> addresses       = OpenStruct.new(home: nil, office: office_address)
> user            = OpenStruct.new(name: "Sandip", addresses: addresses)

> user.dig(:addresses, :home, :city)      # => nil
> user.dig(:addresses, "office", :city)   # => "Pune"

each_pair

Yields attributes as symbols in key value pair, returns enumerator if no block is given.

> user = OpenStruct.new(name: "Sandip", age: 200)
> user.each_pair { |k, v| print "#{k} => #{v}\n" }
# => name => Sandip
# => age => 200

> user.each_pair.with_index { |(k, v), i| print "#{i}: #{k} => #{v}\n" }
# => 0: name => Sandip
# => 1: age => 200

Lazy loading methods

For memory optimization and efficiency, the methods are defined based on certain actions.

> vehicle = OpenStruct.new(make: "TATA", year: 2020)
> vehicle.singleton_methods   # => []

> vehicle.make                # => TATA

> vehicle.singleton_methods   # => [ make, make=(x) ]

Use case

Here’s one use case from Ruby on Rails app best suited for OpenStruct.

# app/models/user.rb
class User
  # schema: name, email
end

# app/representers/user.rb
class UserRepresenter
  attr_accessor :user

  def initialize(user = nil)
    @user = user || default_user
  end

  def attributes
    { name: user.name, email: user.email }
  end

  private
    def default_user
      OpenStruct.new(name: "Anonymous", email: "default@example.com")
    end
end

> UserRepresenter.new(user).attributes
# => { name: "Sophie Brown", email: "sophie@example.com"}

> UserRepresenter.new.attributes
# => { name: "Anonymous", email: "default@example.com"}

That’s all folks! Thanks for reading 😎