Polymorphic associations are promiscuous associations — that is, they are unrestricted in terms of what other classes the association can be with.
Polymorphic associations allow for associations from one model to other model class*es*(note the plural). It is something like STI, but over the association instead of for the model class.
One-to-one(1-1), one-to-many(1-n, n-1), and many-to-many(n-n) relationships can be expressed as polymorphic associations.
Polymorphic associations can be used to replace loaded/decorated HABTM associations(where there are attributes on the join table) with a real model. In these situations they are often combined with ThroughAssociations.
Polymorphic joins also allow for real two-way associations between various model classes. This is not otherwise possible in a situation where modelA and modelB both belongs_to modelC.
Two steps are required to use a polymorphic association. First the model being shared exposes an interface (akin to a virtual class) for the other models to associate with; Second, create the associations from other models to that interface.
The class that will be subject to associations from other models has a virtual association. This association creates an interface that the other models can then associate with.
Example: Address exposes an interface named addressable
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
The association(@belongs_to@) must have a name, and the attribute :polymorphic. The choice of name for the interface is arbitrary.
Any class that will associate with our shared class must declare that association through the interface previously declared.
Example: User associates with the Address class through addressable
class User < ActiveRecord::Base
has_one :address, :as => :addressable
end
The association(has_one in this case) has a name, and refers to the interface via the :as attribute. Possible associations are has_one and has_many.
Use the association the way you normally would.
bob = User.find(7) # => #<User:0x2391234 ... >
bob.address # => #<Address:0x238eaac @attributes={...}>
frank = User.find(3) # => #<User:0x2576dc4 ... >
frank.create_address(...) # => #<Address:0x2576d74 @attributes={...}>
The generic structure:
[Shared model]
|
| belongs_to (:polymorphic => true)
|
(the interface)
,------------' `---------.
| |
| has_one/has_many | has_one/has_many
| (:as => :interfacename) | (:as => :interfacename)
| |
[ClassA] [ClassB]
Example: Address joined to User and Order as addressable
[Address]
|
| belongs_to (:polymorphic => true)
|
addressable (the interface)
,————-’ `————-.
| |
| has_one | has_one
| (:as => :addressable) | (:as => :addressable)
| |
[Order] [User]
The table for the model being shared tracks not only the foreign key, but also the type of model that the association is with.
These fields will be named interface_id and interface_type
Example: Address table with polymorphic columns (addressable_id and addressable_type)
addresses --------- street city country addressable_id addressable_type # <- is a string
Rails 2.0+ offers a TableDefinition#references method with a polymorphic option to conveniently create these fields in migrations. For example, the above table could be created with the following migration:
create_table :addresses do |t|
t.string :street
t.string :city
t.string :country
t.references :addressable, :polymorphic => true
end
See the API Documentation for further info.
Example: Querying a User’s Address and an Order’s Address
bob = User.find(7)
bob.address
# SELECT * FROM addresses WHERE (addresses.addressable_id = 7 AND addresses.addressable_type = 'User')
order_17 = Order.find(17)
order_17.address
# SELECT * FROM addresses WHERE (addresses.addressable_id = 17 AND addresses.addressable_type = 'Order')
What happens when the addressable_id happens to be the same coming from two models? Right now I see nothing stopping an Order and a User from having and id of 1, both with separate address. How does the relationship handle this?
The relationship is not just based on the id. It’s also based on the type. see: AND addresses.addressable_type = 'User'). If Order and User both have the same id, the addressable_type column will be the “tie breaker”.
What about foreign keys? Because the
If you need to guarantee referential integrity you can always use a trigger (if your database supports it). This is obviously non-portable though.
What kind of column should I make addressable_type
Generally string.
There are two reasons to store the Base class. First, the _id is unique to this class and between the two of them we can define a unique object in the model. Secondly, at some point Rails will want to hand you an instance of the model. It need the actual name of the class in order to call the ‘find’ function on it.
(Object.const_get('NameOfYourModel').find(id)
Note: this is not how it actually works though. Instead, what you get back is an “AssociationProxy” object that generally, but not always, works as you’d expect. Specifically, methods beginning with double underscores, methods beginning with “proxy_”, nil?, send, and and methods added to class Object by your code are intercepted by the proxy object and not passed on to the underlying ActiveRecord object.
For the specific case of extensions you add to Object this can be overridden by executing
class ActiveRecord::Associations::AssociationProxy
undef_method :my_method
end
directly after you add the method to class Object.
At least on MySQL this seems to work fine. I wound up adding the Enum manually (using an “execute(’alter table…” in my migration rather than using add_column), and not using the enum plugin.
For certain operations on large tables it seems to be quite a bit faster.
frank.create_address automatically exists for the user class – or do we have to create that? What would that look like if we do have to create that? I’ve tried to recreate the above with my model but I get a ‘undefined method’ error.
I believe what the author probably intended was frank.address.create(). Incidentally, you could get the form shown in article by using or following the pattern in Luke Redpath’s Demeter’s Revenge plugin. See the article for the motivations.
Polymorphic associations are very useful for many situations. However, for the case described here it might be better for the owning object to have the foreign key to the owned object.
For example, expand has_one to take a :through object. When :through is specified, it means the foreign key lives in the owning object:
class Person
has_one :business_address, :through => :business_address_id, :class_name => 'Address'
has_one :billing_address, :through => :billing_address_id, :class_name => 'Address'
end
this makes it very easy to model the case where the main reason for breaking attributes into their own class is not because they will be referred to my multiple entities but because they will be referred to multiple times by a single entity.
In order to use polymorhic associations to have multiple addresses on my person object I’m going to have to add yet another metafield to Address, to distingish them. Instead of just using foreign keys in Person to distinguish them, the natural database modelling way.
So say this instead:
class Person
belongs_to :business_address, :foreign_key => 'business_address_id', :class_name => 'Address'
belongs_to :billing_address, :foreign_key => 'billing_address_id', :class_name => 'Address'
end
Polymorphic associations are promiscuous associations — that is, they are unrestricted in terms of what other classes the association can be with.
Polymorphic associations allow for associations from one model to other model class*es*(note the plural). It is something like STI, but over the association instead of for the model class.
One-to-one(1-1), one-to-many(1-n, n-1), and many-to-many(n-n) relationships can be expressed as polymorphic associations.
Polymorphic associations can be used to replace loaded/decorated HABTM associations(where there are attributes on the join table) with a real model. In these situations they are often combined with ThroughAssociations.
Polymorphic joins also allow for real two-way associations between various model classes. This is not otherwise possible in a situation where modelA and modelB both belongs_to modelC.
Two steps are required to use a polymorphic association. First the model being shared exposes an interface (akin to a virtual class) for the other models to associate with; Second, create the associations from other models to that interface.
The class that will be subject to associations from other models has a virtual association. This association creates an interface that the other models can then associate with.
Example: Address exposes an interface named addressable
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
The association(@belongs_to@) must have a name, and the attribute :polymorphic. The choice of name for the interface is arbitrary.
Any class that will associate with our shared class must declare that association through the interface previously declared.
Example: User associates with the Address class through addressable
class User < ActiveRecord::Base
has_one :address, :as => :addressable
end
The association(has_one in this case) has a name, and refers to the interface via the :as attribute. Possible associations are has_one and has_many.
Use the association the way you normally would.
bob = User.find(7) # => #<User:0x2391234 ... >
bob.address # => #<Address:0x238eaac @attributes={...}>
frank = User.find(3) # => #<User:0x2576dc4 ... >
frank.create_address(...) # => #<Address:0x2576d74 @attributes={...}>
The generic structure:
[Shared model]
|
| belongs_to (:polymorphic => true)
|
(the interface)
,------------' `---------.
| |
| has_one/has_many | has_one/has_many
| (:as => :interfacename) | (:as => :interfacename)
| |
[ClassA] [ClassB]
Example: Address joined to User and Order as addressable
[Address]
|
| belongs_to (:polymorphic => true)
|
addressable (the interface)
,————-’ `————-.
| |
| has_one | has_one
| (:as => :addressable) | (:as => :addressable)
| |
[Order] [User]
The table for the model being shared tracks not only the foreign key, but also the type of model that the association is with.
These fields will be named interface_id and interface_type
Example: Address table with polymorphic columns (addressable_id and addressable_type)
addresses --------- street city country addressable_id addressable_type # <- is a string
Rails 2.0+ offers a TableDefinition#references method with a polymorphic option to conveniently create these fields in migrations. For example, the above table could be created with the following migration:
create_table :addresses do |t|
t.string :street
t.string :city
t.string :country
t.references :addressable, :polymorphic => true
end
See the API Documentation for further info.
Example: Querying a User’s Address and an Order’s Address
bob = User.find(7)
bob.address
# SELECT * FROM addresses WHERE (addresses.addressable_id = 7 AND addresses.addressable_type = 'User')
order_17 = Order.find(17)
order_17.address
# SELECT * FROM addresses WHERE (addresses.addressable_id = 17 AND addresses.addressable_type = 'Order')
What happens when the addressable_id happens to be the same coming from two models? Right now I see nothing stopping an Order and a User from having and id of 1, both with separate address. How does the relationship handle this?
The relationship is not just based on the id. It’s also based on the type. see: AND addresses.addressable_type = 'User'). If Order and User both have the same id, the addressable_type column will be the “tie breaker”.
What about foreign keys? Because the
If you need to guarantee referential integrity you can always use a trigger (if your database supports it). This is obviously non-portable though.
What kind of column should I make addressable_type
Generally string.
There are two reasons to store the Base class. First, the _id is unique to this class and between the two of them we can define a unique object in the model. Secondly, at some point Rails will want to hand you an instance of the model. It need the actual name of the class in order to call the ‘find’ function on it.
(Object.const_get('NameOfYourModel').find(id)
Note: this is not how it actually works though. Instead, what you get back is an “AssociationProxy” object that generally, but not always, works as you’d expect. Specifically, methods beginning with double underscores, methods beginning with “proxy_”, nil?, send, and and methods added to class Object by your code are intercepted by the proxy object and not passed on to the underlying ActiveRecord object.
For the specific case of extensions you add to Object this can be overridden by executing
class ActiveRecord::Associations::AssociationProxy
undef_method :my_method
end
directly after you add the method to class Object.
At least on MySQL this seems to work fine. I wound up adding the Enum manually (using an “execute(’alter table…” in my migration rather than using add_column), and not using the enum plugin.
For certain operations on large tables it seems to be quite a bit faster.
frank.create_address automatically exists for the user class – or do we have to create that? What would that look like if we do have to create that? I’ve tried to recreate the above with my model but I get a ‘undefined method’ error.
I believe what the author probably intended was frank.address.create(). Incidentally, you could get the form shown in article by using or following the pattern in Luke Redpath’s Demeter’s Revenge plugin. See the article for the motivations.
Polymorphic associations are very useful for many situations. However, for the case described here it might be better for the owning object to have the foreign key to the owned object.
For example, expand has_one to take a :through object. When :through is specified, it means the foreign key lives in the owning object:
class Person
has_one :business_address, :through => :business_address_id, :class_name => 'Address'
has_one :billing_address, :through => :billing_address_id, :class_name => 'Address'
end
this makes it very easy to model the case where the main reason for breaking attributes into their own class is not because they will be referred to my multiple entities but because they will be referred to multiple times by a single entity.
In order to use polymorhic associations to have multiple addresses on my person object I’m going to have to add yet another metafield to Address, to distingish them. Instead of just using foreign keys in Person to distinguish them, the natural database modelling way.
So say this instead:
class Person
belongs_to :business_address, :foreign_key => 'business_address_id', :class_name => 'Address'
belongs_to :billing_address, :foreign_key => 'billing_address_id', :class_name => 'Address'
end