Exploring Ruby Structs
Ruby structs are a simple but often overlooked data structure.
Structs basically provide a mechanism for creating a class with a fixed set of data attributes. They can be a great alternative to using hashes and arrays to hold data with fixed structure. They also provide one an easy way to create value objects.
Basic Usage
The struct constructor returns a class that accepts the specified attributes in the constructor and provides accessors for them:
Person = Struct.new(:name, :age)
Use it just like any other class:
bob = Person.new('Bob', 50)bob.name # => "Bob"
Simple equality, instances are equal to another instance of the same struct (or a subclass) with the same values.
bob2 = Person.new('Bob', 50)bob == bob2 # => trueold_bob = Person.new('Bob', 100)bob == old_bob # => falsesimilar_bob = Struct.new(:name, :age).new('Bob', 50)bob == similar_bob # => false
Implements
to iterate over all values:Enumerable
# Enumerablebob.each { |f| puts f }# => "Bob"# => 50
Hash-like access using strings or symbols for the property keys:
bob['name'] # => "Bob"bob[:name] # => "Bob"
Array-like access using indexes:
bob[0] # => "Bob"bob[1] # => 50AnnualSales = Struct.new(:jan, :feb, :mar, :apr, :may, :jun,:jul, :aug, :sep, :oct, :nov, :dec)sales_2014 = AnnualSales.new(10, 20, 10, 30, 40, 50,20, 10, 0, 5, 10, 20)# Get sales in February and Decembersales_2014.values_at(2, 11) # => [10, 20]# Get sales this summar (June, July, August)sales_2014.values_at(5..7) # => [50, 20, 10]
Between the ability to iterate through the values, position preservation, and indexed access, Structs provide functionality similar to named tuples in python and other languages.
If you're using ruby >= 2.0.0 you can turn a struct into a hash via
:#to_h
bob.to_h # => {:name=>"Bob", :age=>50}
Easily extensible:
Person = Struct.new(:name, :age) dodef old?age > 18endendbob = Person.new('Bob', 50)p bob.old? # => true
Since structs are just regular classes you can also just subclass it:
class Person < Struct.new(:name, :age)def greet"Hi #{name}"endend
This approach is slightly less optimal than the block method shown earlier. The subclass approach creates a class just to sub-class it which is rather useless. The block approach incorporates the methods into the class definition the struct constructor uses directly.
Alternatives
OpenStruct, Hashie, and ActiveModel are among the most popular of several dozen other projects that provide similar functionality. I put together a small benchmark comparing these implementations based on a post on stackoverflow, see Struct vs. OpenStruct vs. Hashie vs. ActiveModel. It's not terribly scientific but demonstrates that structs are really fast,
is really slow and Hashie and ActiveModel are close enough to not matter for most purposes. As always, choose the best structure to model your domain before worrying about performance.OpenStruct