caikesouza

Ruby Idioms - Constructors with Structs

The conventional way to define a constructor method in Ruby is by creating a method with a special name of initialize:

class Fruit
  def initialize(name)
    @name = name
  end
end

The initialize method is invoked by the ruby interpreter every time we create a new object by calling the new class method:

banana = Fruit.new('banana')

Now let’s take a quick look at another construct in Ruby called Struct. A Struct is a convenient way to bundle data together without having to define a class:

Struct.new(:age, :gender)

Once you assign an instance of a Struct to a constant, then it behaves pretty much like a class:

Person = Struct.new(:age, :gender)
Person.class # => Class
person = Person.new(28, 'Male')
person.class # => Person
person.age # => 28
person.gender # => Male

You can also add methods to Structs, like so:

Person = Struct.new(:age, :gender) do
  def walk
    'Walking..'
  end
end

The problem with adding methods to a Struct is that they are meant to group data. When you find yourself adding methods to a Struct, then it’s probably a code smell, and a sign that you can move on to defining an actual class.

I recently started experimenting with a way to use the best of both worlds: use a class to define behavior, together with a Struct that defines the data to which the class should be initialized. The way to have those two constructs working together is by subclassing a  class definition from a Struct object:

class Person < Struct.new(:age, :gender)
  def walk
    'Walking..'
  end
end

This allows us to initialize an object with age and gender in the same way we were doing before with the initialize method:

person = Person.new(28, 'male')

As a bonus, it also gives us accessor methods to those attributes, for free:

class Person < Struct.new(:age, :gender)
  def to_s
    "Age: #{age}; Gender: #{gender}"
  end
end

I see three major benefits in using this alternative constructor syntax over the conventional initialize method. First, it’s easier to scan the code and quickly point out how to properly initialize an object from the class. Because the syntax is different, your brain doesn’t have to look for one special method out of a long list of methods that the class might also define.  

Second, it’s less code. As we have previously seen, subclassing from a Struct object not only defines constructor method for the class, but it also gives us attribute accessors for free.  

Lastly, this syntax enforces simplicity. Let’s say we have a Fruit class and we start off with two attributes, name and calories:

class Fruit < Struct.new(:name, :calories)
end

Then, later we add more attributes to the Fruit class’ constructor:

class Fruit < Struct.new(:name, :calories, :sodium, :potassium, :protein)
end

Now we also have sodium, potassium, and protein, and needless to say this looks bad. We are exposing too much from the internals of our Fruit class to the outside world.

This would look bad on any method signature, but being the constructor, this is especially bad.  

In this example, we can refactor the sodium, potassium, and protein into another abstraction, a class or perhaps a Struct, and take it as an argument to our constructor, like so:

class Fruit < Struct.new(:name, :calories, :nutrition_facts)
end

There are some downsides to defining constructors this way, as pointed out by Josh Susser, Josh Cheek and James Edward Gray II. However, I don’t consider them show stoppers, and I’ve concluded that there are enough benefits to justify considering the use of Struct as an alternative to writing conventional constructors in Ruby.

To learn more about how constructors work in Ruby, I recommend you visit Avdi Grimm’s Ruby Tapas and watch Episode 7.

10.30.12 ← See All Posts
blog comments powered by Disqus