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.