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.
Top comments (0)