Going deeper - Part I
It’s funny how seemingly simple features can trigger huge changes to an app’s data structure.
Case in point: my Dealbook app currently has three main models to store information on deals - Company, Investor and Deal. The Investor model represents the entity making the investment (buyer). The Company model represents the entity receiving the investment (seller). Deal is a through model linking Companies and Investors, but also containing deal information, such as close date.
The associations look like this:
class Company < ActiveRecord::Base
has_many :deals
has_many :investors, :through => :deals
end
class Investor < ActiveRecord::Base
has_many_and_belongs_to_many :deals
has_many :companies, :through => :deals
end
class Deal < ActiveRecord::Base
belongs_to :company
has_many_and_belongs_to_many :investors
end
Notice that a given Deal only belongs to one company, but has many investors. That makes sense, because a typical deal usually involves several investors putting money into a single company.
So far, so good. My app was running happily with these model associations, until I ran into the following scenario: what if one of the investors is actually another company?
With the current models, I would have to keep two separate records for the same company (both as Company and as Investor). This is obviously a bad idea, since the company’s data would be scattered around. Ideally, the Company object should be able to store investments into other companies directly.
It seems inevitable that we will need companies to reference themselves. Since dealing with self-references can be mind-numbing, I will skip it for now and address other issues first. To avoid the self-reference, I will use a workaround by temporarily creating a Corporate model to represent a company when it is the buyer. The current Company model will represent only the target company for now. Later, we will try to merge these two models into one.
Polymorphism
The first step to solving the puzzle is finding a way to allow Deal buyers to be both Investors or Corporates. This most likely involves creating a polymorphic association between deals and buyers:
class Company < ActiveRecord::Base
has_many :deals
end
class Deal < ActiveRecord::Base
belongs_to :company
belongs_to :buyer, :polymorphic => true
end
class Investor < ActiveRecord::Base
has_many :deals, :as => :buyer
has_many :companies, :through => :deals
end
class Corporate < ActiveRecord::Base
has_many :deals, :as => :buyer
has_many :companies, :through => :deals
end
There is one problem here, however. (Can you spot it?) The current association between Deals and Investors is not has_many, but rather has_and_belongs_to_many (HABTM), which does not accept polymorphism. Changing it to a simple has_many relationship is not an option, since an investor can have many deals and a deal can have many investors.

Two levels deep
One solution would be to change the HABTM association into a has_many through, which does allow polymorphism. Since we already have a ‘has_many through deals’ association, we would now have two intermediary models between target companies (Company) and their buyers (Investors or Corporates). Fortunately, multi-level through associations are supported as of Rails 3.1.
Here, I included a new through model called Dealing:
class Company < ActiveRecord::Base
has_many :deals
has_many :participations, :through => :deals
end
class Deal < ActiveRecord::Base
belongs_to :company
has_many :dealings
end
class Dealing < ActiveRecord::Base
belongs_to :deal
belongs_to :buyer, :polymorphic => true
end
class Investor < ActiveRecord::Base
has_many :dealings, :as => :buyer
has_many :deals, :through => :dealings
has_many :companies, :through => :deals
end
class Corporate < ActiveRecord::Base
has_many :dealings, :as => :buyer
has_many :deals, :through => :dealings
has_many :companies, :through => :deals
end
This works! Now we can access companies directly from an investor by simply using:
> an_investor.companies
# returns array of companies invested by an_investor
The only catch is that polymorphic through associations only go in one direction, so trying the opposite call does not work:
> a_company.buyers
> a_company.investors
> a_company.corporates
# all of the above throw NoMethodError
There are a few ways around this, which I will cover later. (If you’re curious, check out this post.)
So now that we can accept both types of buyers in Deals, it’s time to eliminate the Corporate model and create a self-referencial Company.
TO BE CONTINUED…