DEV Community

Josiah Campbell
Josiah Campbell

Posted on

The Value in Response Objects

The are many ways in how to return from a Ruby method. A method can use an explicit return keyword and return a value, or one can rely on Ruby's use of expressions to skip using return for brevity.

But there are also tactics in what to return as well. Periodically, a return value may need to have metadata about what it represents. Consider the situation of a call to a payment processor like Stripe. A wrapper method might look like this.

def charge_card(card)
  charge = PaymentProcessor.charge(card)
  charge
end
Enter fullscreen mode Exit fullscreen mode

Seems simple enough. However, the charge may be successful, or the charge may fail. The credit card CVV may be wrong or the payment processor may be entirely offline. The return value will differ in that case.

def charge_card(card)
  PaymentProcessor.charge(card)
rescue => e
  nil
end
Enter fullscreen mode Exit fullscreen mode

Returning nil from an external error is a fine short term solution, but over time may fail to be clear to other team members, or its meaning may be lost to time. In its current form, the method would need a comment for clarity on what nil means.

There's a better way. In fact, the web has been using it since the beginning. It's a response object! In Ruby terms, a response class that looks like an HTTP response would have a status attribute, a body attribute, and any extra information needed.

class Response
  attr_accessor :status, :body

  def success?
    status == :ok # similar to a 200 status
  end
end

def charge_card(card)
  charge = PaymentProcessor.charge(card)
  Response.new.tap do |r|
    r.status = :ok
    r.body = { charge: charge }
  end
rescue => e
  Response.new.tap do |r|
    r.status = :error
    r.body = { error: e.message }
  end
end
Enter fullscreen mode Exit fullscreen mode

This implementation is more verbose. But that's ok since other programmers will have more clarity in what's happened to the charge. Instead of inferring what nil is or hoping that a charge object is returned, the return value will always be a consistent Response instance.

response = charge_card(card)
if response.success?
  # do successful things
else 
  # retry, or do something else
end
Enter fullscreen mode Exit fullscreen mode

In general, this form of data parcelling is known as a value object, and helps represent data in more unique and extensible ways. It takes full advantage of Ruby's object-oriented features, and makes code more web-like in the process.

Top comments (0)