TIL #1: Single Table Inheritance

Daniel Dye
Seekster Development
2 min readFeb 19, 2018

Why and when should you use Single Table Inheritance.

Photo by Jack Anstey on Unsplash

I’m a bit of an avid user of Ruby on Rails and we at Seekster use it as our major server-sided language, so the examples I’ll be using will be in Ruby!

Single Table Inheritance is the concept of having multiple models use a single table, hence the name. An example would be:

class Animal < ApplicationRecord;
class Bear < Animal;
class Fish < Animal;
class Dog < Animal;

Although this is a common example that explains the basics of the concept quite well, it isn’t used in real life applications too often.

class Payment < ApplicationRecord;
class CashPayment < Payment;
class CardPayment < Payment;

Whereas the above is similar to something I’m actively working on!

I’ve now given examples of what the vague models would look like, but how exactly do they benefit us? Why would you need two models linked to the same database table?

In my direct experience. Callbacks.

I’ve come to realize that conditional statements are the bane of easy-to-understand code.

Easy-to-understand code has a non-branching path of logic.

# Without STI
class Payment < ApplicationRecord
belongs_to :invoice
belongs_to :card_transaction, optional: true
before_create :check_state_of_card_transaction, if: -> { payment_type == 'card' } def check_state_of_card_transaction
self.state = 'captured' if card_transaction.captured?
end
end
# With STI
class Payment < ApplicationRecord
belongs_to :invoice
end
class CardPayment < Payment
belongs_to :card_transaction

after_create :check_state_of_card_transaction
private def check_state_of_card_transaction
self.state = 'captured' if card_transaction.captured?
end
end
class CashPayment < Payment
before_create do
self.state = 'captured'
end
end

And as you may have guessed from the code above, I have an Invoice model that’s something like this:

# Without STI
class Invoice < ApplicationRecord
has_many :payments
end
# With STI
class Invoice < ApplicationRecord
has_many :payments
has_many :cash_payments
has_many :card_payments
end

Which now allows me to create the corresponding Controllers that make everything wonderful and Rails-worthy.

# /app/controllers/cash_payments_controller.rb
class CashPaymentsController < ApplicationController
before_action :set_invoice

def index
render json: @invoice.cash_payments
end
def create
render json: @invoice.cash_payments.create!(payment_params)
end
private def set_invoice
@invoice = Invoice.find(params[:invoice_id])
end
private def payment_params
params.permit(:amount, :currency)
end
end
# /app/controllers/card_payments_controller.rb
class CardPaymentsController < ApplicationController
before_action :set_invoice

def index
render json: @invoice.card_payments
end
def create
render json: @invoice.card_payments.create!(payment_params)
end
private def set_invoice
@invoice = Invoice.find(params[:invoice_id])
end
private def payment_params
params.permit(
:amount, :currency,
card_transaction_attributes: [
:token
]
)
end
end

This is my first blog post, so I’m a bit out of practice. Let me know how I can improve :) I hope you learned something!

--

--

Daniel Dye
Seekster Development

Software Developing Freak. CTO of Seekster, Your Trusted Service Partner.