TIL #1: Single Table Inheritance
Why and when should you use Single Table Inheritance.
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
endclass 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
endclass 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!