A plethora of ways to instantiate a Ruby object

Ruby is a very flexible language and there are many ways to instantiate an object. There are pros and cons for each making them more or less appropriate in various use cases. Consider the task of defining a Paragraph class to track the style of a paragraph DOM element. A simple class definition and usage pattern might look like this

class Paragraph

  attr_accessor :font, :size, :weight, :justification

end

p = Paragraph.new
p.font = 'Times'
p.size = 14
p.weight = 300
p.justification = 'right'

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Times, 14, 300, right

The instantiated object uses instance variables to maintain the state and defines public getter and setter methods that allow you to update the paragraph style at any time. This is a very flexible approach, but it does not enforce a complete style definition. You might run into problems if a consumer requires such and does not appropriately handle properties with nil values. To address this concern, it is not unusual to enforce completeness by setting up all state upon instantiating the object through the use of an initializer.

class Paragraph

  def initialize(font, size, weight, justification)
    @font = font
    @size = size
    @weight = weight
    @justification = justification
  end

end

p = Paragraph.new('Times', 14, 300, 'right')

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Times, 14, 300, right

In this example, Ruby will check the number of parameters passed to the initialize method against its arity, which insures that all the the style attributes are set upon instantiation. However, this approach is already becoming unwieldy due to the number of parameters, the strict parameter ordering requirement, and the need to memorize the ordering of the parameters. A Ruby idiom that addresses these concerns passes a single hash to the initialize method. For example

class Paragraph

  def initialize(style)
    @font = style.fetch(:font, 'Helvetica')
    @size = style.fetch(:size, 12)
    @weight = style.fetch(:weight, 200)
    @justification = style.fetch(:justification, 'right')
  end

end

p = Paragraph.new(font: 'Times', weight: 300)

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Times, 12, 300, right

This approach reduces the cognitive load on the developer by allowing the attributes to be set with an unordered list of key/value pairs. It also minimizes the number of pairs required by setting reasonable defaults for each style attribute.

Alternatively, in Ruby 2.1, we can take advantage of Keyword Arguments to clarify the method signature.

class Paragraph

  def initialize(font: 'Helvetica',
                 size: 12,
                 weight: 200,
                 justification: 'right')

    %w{font size weight justification}.each do |attribute|
      eval "@#{attribute} = #{attribute}"
    end

  end

end

p = Paragraph.new(font: 'Times', weight: 300)

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Times, 12, 300, right

Here the method parameters and their defaults are captured in the method signature instead of being buried in the method definition. This could improve the usability of class, especially if an automated documentation system is in use.

Sometimes, you may want to encourage a more declarative instantiation. Enlisting the use of a meaningfully named Struct to capture object state can help achieve this. For example

class Paragraph

  Style = Struct.new :font, :size, :weight, :justification

  def style
    @style ||= Style.new('Helvetica', 12, 200, 'right')
  end

  def initialize(&block)
    yield style
  end

end

p = Paragraph.new do |style|
  style.font = 'Times'
  style.size = 16
  style.weight = 300
end

puts "#{p.style.font}, #{p.style.size}, #{p.style.weight}, #{p.style.justification}"
# => Times, 16, 300, right

While not much different from the first example (using only attribute accessors), the usage makes it clear that these are style attributes which are being initialized. If the style method is made private, Paragraph becomes immutable, which may be advantageous in some cases.

Taking this one step further, a custom Domain Specific Language (DSL) can be created to achieve a more human readable interface.

class Paragraph

  Style = Struct.new :font, :size, :weight, :justification

  def style
    @style ||= Style.new('Helvetica', 12, 200, 'right')
  end

  def initialize &block
    instance_eval &block
  end

  def write(parameters)
    style.font = parameters.fetch(:using, 'Helvetica')
    style.size = parameters.fetch(:at, 12)
  end

end

p = Paragraph.new do
  write using: 'Times', at: 14
end

puts "#{p.style.font}, #{p.style.size}, #{p.style.weight}, #{p.style.justification}"
# => Times, 14, 200, right

Sometimes we don't have control over how an object is instantiated. The class might be defined in a third party library or already in use in our own code, making it difficult to change. In such a case, we can use the Builder pattern by defining a class that creates objects for us. In this way, we can create an interface of our own choosing. For example, let us imagine that the Paragraph class is defined as follows

class Paragraph

  def initialize(font, size, weight, justification)
    @font = font
    @size = size
    @weight = weight
    @justification = justification
  end

end

and cannot be altered. We can define a Builder class that creates Paragraph objects for us, but allows us to set the style attributes in a block.

require 'ostruct'

class Builder

  def self.configure(klass, &block)
    return unless block_given?
    struct = OpenStruct.new
    struct.instance_eval &block
    defaults[klass] = struct.to_h
  end

  def self.create(klass, &block)
    struct = OpenStruct.new defaults[klass]
    struct.instance_eval &block if block_given?
    parameters = defaults[klass].keys.map{ |k| struct[k] }
    klass.new(*parameters)
  end

  private

    def self.defaults
      @@defaults ||= {}
    end

end

With this in place, we can set sensible defaults, which are tracked by the Builder. The pre-existing Paragraph class has no defaults.

Builder.configure(Paragraph) do
  self.font = 'Helvetica'
  self.size = 14
  self.weight = 200
  self.justification = 'right'
end

We can then see that when a Paragraph is created, it reflects those defaults.

p = Builder.create(Paragraph)

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Helvetica, 14, 200, right

and that those defaults can be overridden at creation time.

p = Builder.create(Paragraph) do
  self.font = 'Times'
  self.size = 16
end

puts "#{p.font}, #{p.size}, #{p.weight}, #{p.justification}"
# => Times, 16, 200, right

Thus, with relatively little extra work and no impact on the existing paragraph class, we can improve the way in which we instantiate Paragraph objects, adding features such as the ability to have default attribute values.

Simple internal DSLs in Ruby

It seem that creating a Doman Specific Language (DSL) is both considered all the rage and an overused scourge. In Ruby, it is really easy to create one, and I suspect that is why they are a popular tool for Rubyists. Although I've used many DSLs I have never have built one of my own. I have always had the desire to write my own programming language but am very daunted by the difficulty of crafting an elegant language that does not break down for all but the simplest cases, let alone writing an efficient language parser.

Anyway, if we focus on writing an Internal DSL, one which is built in and leverages a core language, we can accomplish this in Ruby with a simple instance_eval.

module DSL
  def self.enable(klass, &block)
    container = klass.new
    container.instance_eval(&block)
  end
end

Here I create a DSL module with a single enable method that accepts a class that defines the DSL methods and a block of code. A new instance of the class specifying the DSL is created and the block that is passed in is evaluated in the context of the class, thus making the DSL methods available within the block.

If we wanted to create a DSL for a pseudo reverse Polish notation (RPN) calculator, we would simply define a class with methods that define the operations in the language. For example:

class Calculator

  def initialize
    self.stack = []
  end

  def push value
    stack.push value
  end

  def add
    calculate { stack.pop + stack.pop }
  end

  def subtract
    calculate { stack.pop - stack.pop }
  end

  def multiply
    calculate { stack.pop * stack.pop }
  end

  def divide
    calculate do
      a = stack.pop
      b = stack.pop
      b / a
    end
  end

  private

    attr_accessor :stack

    def calculate &block
      result = block.call
      stack.push result
      return result
    end

end

Then using the DSL is as simple as calling DSL.enable with the Calculator class and a block of RPN as shown in the following RSpec tests. Note that the result of the RPN operations are given as the output of the call to DSL.enable.

describe 'Calculator' do

  it 'should add two numbers' do

    result = DSL.enable Calculator do
      push 1
      push 2
      add
    end

    expect(result).to eq(3)

  end

  it 'should divide two numbers' do

    result = DSL.enable Calculator do
      push 6
      push 2
      divide
    end

    expect(result).to eq(3)

  end

  it 'should handle multiple operations' do

    result = DSL.enable Calculator do
      push 3
      push 6
      push 2
      divide
      multiply
    end

    expect(result).to eq(9)

  end

end

Not only does implementing the DSL in this way provide access to the operators, but it can also hold state by way of instance variables (stack in this example).