The latest big ActiveRecord
feature to Rails has been polymorphic
associations. Not very clear at first, I found out they’re easier to understand
with a couple examples.
A common one:
class Person < ActiveRecord::Base
has_one :address, :as => :addressable
end
class Company < ActiveRecord::Base
has_one :address, :as => :addressable
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
This basically allows the address class to belongs_to
any model. Which is
nice, the alternative would be to say:
class Address < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
Then you’d have all these foreign keys in your addresses table but you only would ever have a value for one of them because an address can only belong to a person or a company but not both.
OK that was the basics.
Now the rails docs refer to the as
keyword parameter to has_one
and
has_many
as specifying a “polymorphic interface.” What is that?
When I think"polymorphic interface", I think something along the lines of the
<<
message in Ruby. I can send that message to a bunch of different objects
like arrays, strings, IO streams, etc. and they all know what to do when they
receive it.
I think <<
as a kind of interface that all those different classes implement.
From a statically typed language standpoint, such as Java, it would be as if we
had a nice little hierarchy of classes all implementing a common interface.
Pretending Java allowed operator overloading and <<
was an operator it might
look like this.
public interface Collection {
Collection <<(Object anObject);
}
And the classes.
public class Array implements Collection {
public Collection << (Object object) {
// add object to me
}
}
public class String implements Collection {
public Collection << (Object object) {
// add object to me
}
}
Where am I going with this? Stay with me here.
Lets rotate the class diagram for the Person
, Company
, and Address
90
degrees to the left (I don’t know how to draw that here but if you write it out
on paper you’ll see what I mean) and make Person
and Company
inherit from
Address
.
class Address < ActiveRecord::Base
end
class Person < Address
end
class Company < Address
end
Now neither a Person
nor a Company
are an Address
so lets instead use that
“polymorphic interface” as our superclass name.
class Addressable < ActiveRecord::Base
end
class Person < Addressable
end
class Company < Addressable
end
That’s better. A Person
and a Company
are addressable but what happened to
the Address
class.
class Addressable < ActiveRecord::Base
has_one :address
end
There we go. Now Person
and Company
has_one
address, they inherit it from
Addressable
, and no polymorphic associations.
But wait, inheritance? More specifically in the Rails world, “single-table
inheritance” (STI) ? Let’s
pretend a Person
and a Company
have very little state in common; what does
using (STI) give us? It’s gives
us a giant table with a lot of empty columns for the Company
attributes when
the row is a Person
and a lot of empty columns for the Person
attributes
when the row is a Company
.
I don’t like it.
As more Addressable
‘s come into the system that table is going to get bigger
and bigger. To me using Addressable
makes sense, inheritance is about
behavior and not about state and if I was modeling without thinking about a
database I probably would of first came up with using the Addressable
class.
What can we conclude? We can say that STI is basically a way to map an inheritance hierarchy for a bunch of classes that have a lot of common state not behavior.
Now there are other ways to map an inheritance hierarchy to a relational database. In fact there’s 3 commonly used ones. Rails only gives us the simplest one STI.
- Single Table Inheritance (STI) - The whole hierarchy is mapped to a single table with an extra column (“type” in Rails) that holds the class name for that row
- One Table per Concrete Class - Each concrete class gets its own table, the superclass (like all good superclasses) is abstract and does not get its own table. Any common state in the superclasses is duplicated in each subclass table.
- One Table per Class - Each class in the hierarchy gets its own table. Subclass tables have foreign keys referencing their corresponding superclass row in the superclass table.
For the 2nd one it won’t work. But that last one “One Table per Class” is interesting.
Back to our Person
, Company
, and Addressable
example.
If we mapped each of those classes to its own table what would or database look like?
addressables (id)
people (id, name, age, height, weight, addressable_id)
companies (id, size, established_date, addressable_id)
addresses (id, street, city, state, addressable_id)
Hmmm. We get to use the nice hierarchy that uses behavior to model inheritance
with Addressable
as our superclass and we don’t have to put all the subclasses
into their own table. So we got rid of that big ugly STI table with all those empty columns. When reading a
Person
from the database, we’d do a join to Person
’s superclass table
addressables
on addressable_id
. And to read a Person
’s address we’d do a
join to addresses
on addressable_id
.
So we eliminated polymorphic associations and replaced them with inheritance,
assuming rails supported our inheritance mapping scheme of “One Table per
Class”, which it doesn’t but lets keep going. And I like our hierarchy, a
Person
is Addressable
, a Company
is Addressable
it reads nice and
logical. One beef. What’s the deal with that addressables
table? A table
with one column. That’s weird. For now, mark it down as a disadvantage and
some ugliness with this method.
But wait. Here comes another feature. Users now want to be able tag people and companies. We could use the Rails plugin acts_as_taggable, which uses polymorphic associations. Let’s not and keep running with this to see where it takes us.
A Taggable
class sounds good. But wait. Ruby doesn’t allow multiple
inheritance and it doesn’t have the Java/C# equivalent of interfaces. Ruby uses
modules
instead. OK so we screwed up. We should of modeled Addressable
as
a module
and not a class. I say we didn’t screw up - we modeled that because
that’s what the current requirements were; we didn’t know tagging was coming.
That’s another agile story.
Anyway, let’s try using modules.
module Addressable
end
module Taggable
end
class Person < ActiveRecord::Base
include Addressable
include Taggable
end
class Company < ActiveRecord::Base
include Addressable
include Taggable
end
There that’s better now Addresses and Companies are both addressable and taggable.
module Addressable
has_one :address
end
Wait, we can’t do that in Rails. OK how about this.
module Addressable
def self.included(klazz) # klazz is that class object that included this module
klazz.class_eval do
has_one :address
end
end
end
There now, each class that includes the Addressable
module has_one
Address
. Hold up. What would our database look like?
addresses (id, street, city, state, person_id, company_id)
Oh no, we’re back to where we started. An Address
can’t belongs_to
a
Person
and a Company
at the same time. We need polymorphic associations.
Wow, that was a trip. What did we learn. At first we started out using
polymorphic associations. Then we decided to refactor and try modeling in what
I’d call “the more natural way” using inheritance and Addressable
and
Taggable
classes. But we eventually got burnt when a Person
and a Company
both needed to be Taggable
as well. So we used Ruby’s version of
interfaces/multiple inheritance by using modules. And that got us right back to
where we started
The moral of the story is this because Rails only allows you to map inheritance
hierarchies using STI plus Ruby’s
lack of “interfaces we can’t model using interface like classes like
Addressable” and Taggable
. Instead we use polymorphic associations and let
them use that “polymorphic interface” for the name of the association:
class Address < ActiveRecord::Base
# here's where we'll use Addressable
belongs_to :addressable, :polymorphic => true
end
class Tagging < ActiveRecord::Base
# here's where we'll use Taggable
belongs_to :taggable, :polymorphic => true
end
I like modeling using interfaces such as Addressable
and Taggable
. I really
like the following Java classes.
public interface Addressable {
Address getAddress ();
void setAddress (Address address);
}
public class Address {
private String street;
private String city;
private State state;
private Zipcode zipcode;
private Addressable addressable; // anyone who implements Addressable
// various getters and setters, etc.
}
public class Person implements Addressable {
private Address address;
public Address getAddress () {
return address;
}
public void setAddress (Address address) {
this.address = address;
}
}
public class Company implements Addressable {
private Address address;
public Address getAddress () {
return address;
}
public void setAddress (Address address) {
this.address = address;
}
}
And for Taggable
public interface Taggable {
Collection getTaggings ();
}
public class Tagging {
private Taggable taggable; // anyone who implements Taggable
private Tag tag;
// various getters/setters, etc.
}
public class Person implements Addressable, Taggable {
// Addressable implementation from above
private Collection taggings;
public Collection getTaggings () {
return taggings;
}
}
public class Company implements Addressable, Taggable {
// Addressable implementation from above
private Collection taggings;
public Collection getTaggings () {
return taggings;
}
}
In Object-relational mapping (ORM) libraries in the Java/.Net world they provide all 3 inheritance mapping schemes whereas in Rails you only get STI.
Were the other 2 inheritance mapping schemes too “enterprise” for Rails? Too
complicated for DHH to implement? Nah, Ruby’s lack of interfaces make the other
2 schemes not as powerful as in languages that support interfaces e.g. like in
the above example Addressable
worked fine, but the minute we introduced
Taggable
i.e. another “interface” we were done for.
And Rails form of “polymorphic associations” aren’t found in Java/.Net
object-relational mapping libraries as well. Of course not, Ruby’s use of
modules as a form of multiple inheritance required something unique,
“polymorphic associations” are that something. Look at the source for the
popular acts_as_taggable
plugin. It uses polymorphic associations and it uses
modules to include
in any shared behavior such as instance and classes
methods. This is nice. You call acts_as_taggable
in your class definition
and you get all this behavior for free.
I’m sorry, but I miss my interfaces. Looking at the above Java code, you might
say “yeah but look at that Addressable
and Taggable
implementation in the
Person
and Company
classes its exactly the same, its not DRY. In Ruby with modules you’d only have to implement
that once and just include
it into the Person
and Company
classes”.
That’s true but if someone decides to change that module it might break classes
that include
it - that’s another story something called the “brittle base
class”. I also like the fact that its explicit, its duplicated but I can see
the code in both the Person
and Company
classes, no messing around in
$RAILS_ROOT/vendor/plugins
.
Well if you made it through that you’re just as crazy as I am. But I’m scoring this one a point for Java/C#. Ruby usually dominates but being able to use those Java/C# interfaces just seem more natural and better to me. And the fact that Rails’ polymorphic associations are a new thing, there’s definitely a source of confusion around them in the Rails community.
Next Steps & Related Reading
Detect emerging problems in your codebase with Ruby Science. We’ll deliver solutions for fixing them, and demonstrate techniques for building a Ruby on Rails application that will be fun to work on for years to come.