Search code examples
ruby-on-railsrubydatabase-designhas-and-belongs-to-manysti

How to create HABTM association with STI model sub-classes?


Lets say I have a STI model called Company. It has three sub-classes Firm, Client and PriorityClient.

class Company < ActiveRecord::Base
    scope :firms_n_clients, -> { where(type: %w(Firm Client)) }
end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Company; end

I have another model called Country. Now I want to create a has_and_belongs_to_many association between Country and firms_n_clients(only Firm and Client type of Company). How would it be?

Thanks in advance.


Solution

  • has_and_belongs_to_many associations accept scopes. Some of them are discussed in the Ruby on Rails documentation. Assuming the necessary join table exists, the association can be established like so:

    class Country < ActiveRecord::Base
        has_and_belongs_to_many :companies, -> { where(type: %w(Firm Client)) }
    end
    
    class Firm < Company
        has_and_belongs_to_many :countries
    end
    
    class Client < Company
        has_and_belongs_to_many :countries
    end
    

    Please note the duplicate Code in Client and Firm. This is on purpose, because it reveals explicitly that Clients and Firms have and belong to countries, and PriorityClients do not.

    I have not tested the below code, but an even better way to modify the HABTM Association would be to merge the the firms_n_clients scope:

    class Country < ActiveRecord::Base
        has_and_belongs_to_many :companies, -> { merge(Company.firms_n_clients) }
    end
    

    This has several advantages: The Country model doesn't need to know about the different Company types, and modifying the scope will affect the association as well.