domingo, 12 de julio de 2015

When STI met polymorphic associations

It was a very long time from the last time that I wrote a post. A lot of things happened but I continue working with Ruby!

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
So we have a many-to-many relationship and a one-to-many relationship. That is why we need to model such relationships using the special Accounting join model. If we add into this that we have STI, then we end up with a pretty interesting model, isn't it?

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! 
Very important! We need a validation of uniqueness in the case the accountable object is an AccountUser, because an AccountUser only can have a single Account.

And that's all!



No hay comentarios:

Publicar un comentario