In Ruby, classes are cheap

In Ruby, classes are cheap

Developers coming from Perl or Php often bring conventions from those languages into Ruby. One important thing I like to tell new Ruby developers is that classes in Ruby are cheap so use them liberally. Ruby is an Object Oriented language and objects are the best way to communicate your intent to future developers reading your code. Let’s look at examples of where a class is more appropriate.

First, we have a method returning multiple values for status.

class ExternalOrderService
  def handle_order(order_status)
    # stuff here
    if order_status.include?('Fail on Shipment')
      message = 'Order Failed because of Shipment'
      success =  false
    elsif order_status.include?('Order Processed')
      message = 'Order Failed because of Shipment'
      success =  false
    else
      message = 'Order Failed for unknown reason'
      success =  false
    end
    [message, success]
  end
end

Where the method call looks like this:

message, success = service.handle_order(results)
  if success
    ...
  end

Ruby won’t complain if you do this but it’s more a Perlish way of doing things. Also, to another developer this doesn’t make much sense. Why is message coming before success?

Another common approach is, after you fell in love with Ruby Hash, you want to return a hash.

class ExternalOrderService
  def handle_order(order_status)
    # stuff here
    if order_status.include?('Fail on Shipment')
      {
        message: 'Order Failed because of Shipment',
        success:  false
      }
    elsif order_status.include?('Order Processed')
      {
        message: 'Order Failed because of Shipment',
        success:   false
      }
    else
      {
        message:  'Order Failed for unknown reason',
        success:   false
      }
    end
  end
end

and call it like this:

result_hash = service.handle_order(results)
  if result_hash.fetch(:success)
    ...
  end

There is really nothing wrong with this either but for me the best way is to create a simple class with the fields as attributes. Often times I will put it inside the class itself.

  class StatusMessage
    attr_reader :success, :message
    alias_method :success?, :success

    def initialize(message, success)
      @message = message
      @success = !!success
    end

  end

  def handle_order(order_status)
    # stuff here
    if order_status.include?('Fail on Shipment')
      StatusMessage.new('Order Failed because of Shipment',false)
    elsif order_status.include?('Order Processed')
      StatusMessage.new('Order Failed because of Shipment',true)
    else
      StatusMessage.new('Order Failed for unknown reason',false)
    end
  end
end

I would call it like this:

  result = service.handle_order(results)
  if result.success?
    ...
  end

Another nice thing with this, is with a simple alias_method to get boolean-looking success? method.

Simple lesson: use classes liberally.