Search code examples
ruby-on-railsactiverecordsti

How to model people and companies with single table inheritance in Rails?


In my Invoicing app invoices can be sent to either a company or a person. As I understand it, this is a good use case for Rails' single table inheritance (STI). Since both types share a number of attributes and functions, I figured that a super-class Recipient might be a good way to go:

class Recipient < ActiveRecord::Base 
end

class Company < Recipient
  has_many :people
end

class Person < Recipient
  belongs_to :company
end

I also understand that I need an attribute type in the Recipient model.

The only thing that bothers me is the fact that a person may (or may not) belong to a company. How can this be modelled in Rails? Normally, I'd simply add another database field company_id to the people table. But there's only one table (recipients) here. So how can this be done?

Thanks for any help.


Solution

  • A structure could look like this:

    class Recipient < ActiveRecord::Base 
      has_many :invoices
    end
    
    class Company < Recipient
      has_many :people
    end
    
    class Person < Recipient
      belongs_to :company
    end
    
    class Invoice < ActiveRecord::Base
      belongs_to :recipients
    end
    
    # Schema
    create_table "invoices", force: :cascade do |t|
      t.integer "recipient_id"
      t.datetime "created_at", null: false
      t.datetime "updated_at", null: false
      t.index ["recipient_id"], name: "index_invoices_on_recipient_id"
    end
    
    create_table "recipients", force: :cascade do |t|
      t.integer "company_id"
      t.string "type"
      t.datetime "created_at", null: false
      t.datetime "updated_at", null: false
    end
    

    I've just tried in the console:

    > Recipient.all
     => [#<Company:0x007fd55d797220
      id: 1,
      company_id: nil,
      type: "Company",
      created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
      updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>,
     #<Person:0x007fd55d796730
      id: 2,
      company_id: 1,
      type: "Person",
      created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
      updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>,
     #<Person:0x007fd55d796208
      id: 3,
      company_id: nil,
      type: "Person",
      created_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00,
      updated_at: Fri, 04 Aug 2017 10:57:41 UTC +00:00>]
    
    > Person.last.company
      Person Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Person') ORDER BY "recipients"."id" DESC LIMIT ?  [["LIMIT", 1]]
     => nil
    
    > Person.first.company
      Person Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Person') ORDER BY "recipients"."id" ASC LIMIT ?  [["LIMIT", 1]]
      Company Load (0.2ms)  SELECT  "recipients".* FROM "recipients" WHERE "recipients"."type" IN ('Company') AND "recipients"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
     => #<Company id: 1, company_id: nil, type: "Company", created_at: "2017-08-04 10:57:41", updated_at: "2017-08-04 10:57:41">