Possible to have "polymorphic has_one" relationship in rails?

markquezada picture markquezada · Sep 8, 2011 · Viewed 29k times · Source

I'd like to do something like this:

Category
--------
- id
- name

Tag
--------
- id
- tag


Campaign
--------
- id
- name
- target (either a tag *or* a category)

Is a polymorphic association the answer here? I can't seem to figure out how to use it with has_one :target, :as => :targetable.

Basically, I want Campaign.target to be set to a Tag or a Category (or potentially another model in the future).

Answer

Kristian PD picture Kristian PD · Sep 8, 2011

I don't believe you're in need of a has_one association here, the belongs_to should be what you're looking for.

In this case, you'd want a target_id and target_type column on your Campaign table, you can create these in a rake with a t.references :target call (where t is the table variable).

class Campaign < ActiveRecord::Base
  belongs_to :target, :polymorphic => true
end

Now campaign can be associated to either a Tag or Category and @campaign.target would return the appropriate one.

The has_one association would be used if you have a foreign key on the target table pointing back to your Campaign.

For example, your tables would have

Tag: id, tag, campaign_id Category: id, category, campaign_id

and would have a belongs_to :campaign association on both of them. In this case, you'd have to use has_one :tag and has_one :category, but you couldn't use a generic target at this point.

Does that make more sense?

EDIT

Since target_id and target_type are effectively foreign keys to another table, your Campaign belongs to one of them. I can see your confusion with the wording because logically the Campaign is the container. I guess you can think of it as Campaign has a single target, and that's a Tag or a Container, therefore it belongs in a Tag or Container.

The has_one is the way of saying the relationship is defined on the target class. For example, a Tag would have be associated to the campaign through a has_one relationship since there's nothing on the tag class that identifies the association. In this case, you'd have

class Tag < ActiveRecord::Base
  has_one :campaign, :as => :target
end

and likewise for a Category. Here, the :as keyword is telling rails which association relates back to this Tag. Rails doesn't know how to figure this out upfront because there's no association with the name tag on the Campaign.

The other two options that may provide further confusion are the source and source_type options. These are only used in :through relationships, where you're actually joining the association through another table. The docs probably describe it better, but the source defines the association name, and source_type is used where that association is polymorphic. They only need to be used when the target association (on the :through class) has a name that isn't obvious -- like the case above with target andTag -- and we need to tell rails which one to use.