Convert API Response Into a Model in Rails
Let’s get down straight to the business.
Let’s take an example of showing the invoices to the user from Stripe.
First, let’s write what we would like to have without worrying about the implementation.
I like short and clean controllers. Something like the following would do.
app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def index
@invoices = Invoice.find_all_by_user(current_user)
end
def show
@invoice = Invoice.new(params[:id])
render
end
end
Below is what I would like in my view: app/views/invoices/_invoice.html.erb
<% unless (invoice.total == 0) %>
<tr>
<td><%= link_to invoice.number, invoice_path(invoice.id) %></td>
<td><%= invoice.date %></td>
<td><%= number_to_currency(invoice.total, negative_format: "(%u%n)") %></td>
<td><%= invoice.period_start %> to <%= invoice.period_end %></td>
<td><%= invoice.paid? ? 'Paid' : 'Unpaid' %></td>
</tr>
<% end %>
I have experienced that writing the interface first, as we did above, gives me a lot of clarity during implementation. Now, let’s start implementing it in a model.
app/models/invoice.rb
class Invoice
attr_reader :stripe_invoice
def self.find_all_by_user(user)
if user.present?
stripe_invoices_for_user(user).map do |invoice|
new(invoice)
end
else
[]
end
end
def initialize(invoice_id_or_object)
if invoice_id_or_object.is_a? String
@stripe_invoice = retrieve(invoice_id_or_object)
else
@stripe_invoice = invoice_id_or_object
end
end
def to_partial_path
"invoices/#{self.class.name.underscore}"
end
def id
stripe_invoice.id
end
def number
stripe_invoice.number
end
def total
cents_to_dollars(stripe_invoice.total)
end
def date
convert_stripe_time(stripe_invoice.date)
end
def paid?
stripe_invoice.paid
end
def subscription
stripe_invoice.subscription
end
def period_start
convert_stripe_time(stripe_invoice.period_start)
end
def period_end
convert_stripe_time(stripe_invoice.period_end)
end
def user
@user ||= User.find_by(stripe_customer_id: stripe_invoice.customer)
end
def balance
if paid?
0.00
else
amount_due
end
end
def amount_due
cents_to_dollars(stripe_invoice.amount_due)
end
def subtotal
cents_to_dollars(stripe_invoice.subtotal)
end
def amount_paid
if paid?
amount_due
else
0.00
end
end
def plan
stripe_invoice.lines.data[0].plan.name
end
def plan_amount
cents_to_dollars stripe_invoice.lines.data[0].plan.amount
end
def pay
stripe_invoice.pay
end
def self.pay_if_pending(user)
invoices = find_all_by_user(user)
unless invoices.empty? || invoices.first.paid?
invoices.first.pay
end
end
def self.upcoming(user)
new(Stripe::Invoice.upcoming(customer: user.stripe_customer_id) || nil )
end
private
def self.stripe_invoices_for_user(user)
Stripe::Invoice.all(customer: user.stripe_customer_id).data
end
def retrieve(invoice_id)
Stripe::Invoice.retrieve(invoice_id)
end
def convert_stripe_time(time)
Time.zone.at(time).strftime('%D')
end
def cents_to_dollars(amount)
amount / 100.0
end
end
Here, the Stripe gem takes exposes many methods on the response. But we can take any vanilla JSON response any do the same.
Many years ago, I learned this pattern form Upcase. Since then I am always parsing API responses like this. Upcase is free now and definitely worth a look.
Download the code as gist.