Ruby’s Struct Class

Ruby’s Struct class is a nifty way to make basic classes with minimal typing. Basic classes are great to avoid complex internal data structures. The following code illustrates a simple Comment class how to create a Comment class using a Struct that behaves in a similar manner.

# comment class with typical syntax
class Comment
  attr_reader :title, :body
  def initialize(title, body)
    @title = title
    @body = body
  end

  def capitalized_title
    title.upcase
  end
end

# comment class using Struct
Comment = Struct.new(:title, :body) do
  def capitalized_title
    title.upcase
  end
end

Struct classes can be instantiated just like any other class. The Comment class inherits from the Struct class, which in turn inherits from the Object class.

>> c = Comment.new("hello", "bunny")
=> #<struct Comment title="hello", body="bunny">
>> c.class
=> Comment
>> c.class.ancestors
=> [Comment, Struct, Enumerable, Object, Kernel, BasicObject]
>> c.capitalized_title
=> "HELLO"

Struct can be very useful to isolate code that depends on a certain internal data structure. Here is an example of some bad code:

class HockeyTeam
  def initialize(data)
    @data = data
  end

  def full_names
    @data.map do |data_point|
      "#{data_point[0]} #{data_point[1]}"
    end
  end
end

data = [["bob", "lob"], ["crazy", "dude"], ["nice", "sue"]]
h = HockeyTeam.new(data).full_names
# => ["bob lob", "crazy dude", "nice sue"]

The HockeyTeam class is terrible because the full_names method depends on the internal data structure of the @data array. Other methods in the HockeyTeam class will also have to depend on the internal data structure of the @data array, so if the data structure changes, the code will need to be updated in multiple places. A Struct can be used to create a Person class that will isolate the internal data structure of @data in one location.

class HockeyTeam
  def initialize(data)
    @players = players(data)
  end

  def full_names
    @players.map do |player|
      "#{player.first_name} #{player.last_name}"
    end
  end

  Player = Struct.new(:first_name, :last_name)
  def players(data)
    data.map do |first_name, last_name|
      Player.new(first_name, last_name)
    end
  end
end

The internal data structure of @data is now completely encapsulated by the HockeyTeam#players method, so if the data structure changes, the code only needs to change in one place.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s