The other day I worked in very complete case of polymorphic association with some subclasses in STI (Single Table Inheritance). For me was very interesting and I wanted to share it with you. The models I have are like the following:
There are two kind of users: Campaign Manager and Account User.
All kind of users have accounts, so we have also an Account model. The cardinality of the relationships are like the following:
- A campaign manager can have multiple accounts, and an account can have multiple campaign managers
- An account user can have only one account, and an account can have multiple account users
To model the kind of user we will use inheritance in our Rails models. We want also to be able to access the accounts from both the CampaignManager and AccountUser. For that we need two relationships, one to connect the users to the Accounting join model, and the other connecting the later with the Account model. So the models should be like the following:
class CampaignManager < User has_many :accountings, as: :accountable, dependent: :destroy has_many :accounts, through: :accountings end
class AccountUser < User has_one :accounting, as: :accountable, dependent: :destroy has_one :account, through: :accounting end
Now we can access the accounts of an user with code like the following:
campaign_manager.accounts account_user.account
Note that "account" for AccountUser is singular because of the cardinality.
The Account model itself:
class Account < ActiveRecord::Base has_many :campaign_managers, through: :accountings, source: :accountable, source_type: 'CampaignManager' has_many :account_users, through: :accountings, source: :accountable, source_type: 'AccountUser' has_many :accountings, dependent: :destroy end
We have polymorphic association called "accountable". It is polymorphic because it can link to either an AccountUser or an CampaignManager object. So we need to tell the Account model the subclasses that we want to retrieve with source_type.
We need also to create the join model. As we said, the Accounting class is the join model that associates the account with the accountable objects. The code looks like:
class Accounting < ActiveRecord::Base belongs_to :account belongs_to :accountable, polymorphic: true validates :accountable_id, uniqueness: true, if: Proc.new { |a| a.accountable.type == 'AccountUser' } end
In the accountings table we will see the following fields:
- account_id
- accountable_id: It corresponds to the campaign_manager id or the account_user id
- accountable_type: It should be "User" for all the rows, and neither "CampaignManager" nor "AccountUser". Don't get confused with the STI type here!