A twist on polymorphism

I’m struggling with the following problem:

I have a class, call it ‘School’.
An instance of School can have all sorts of assets, such as ‘Books’,
‘Tables’, ‘Chairs’, ‘Teachers’.
Each of these asset types is modeled by it’s own model and has a
corresponding table in the db. So, there is a table for 'Chair,
another for ‘Teacher’ and so on.
In summary, in the abstract, each school has_many assets and each
asset belongs_to a school.

I’d like to be able to do things like:
school = School.find(3)
school.assets << Chair.new(chair_params)
school.assets << Teacher.new(teacher_params)
school.assets.each{|a| puts a.name}

I tried to solve it by defining an abstract class:
class Asset << ActiveRecord::Base
self.abstract_class = true
end

and inheriting from it:
class Book << Assets
belongs_to :school
end

and

class School << ActiveRecord::Base
has_many :assets
end

That gets me part way there but I fear is fundamentally flawed. Most
of the time it results in complaints from raisl that the table
‘Assets’ doesn’t exist, which is true…

Any suggestions on how to model the relationship I described would be
much appreciated!

Yoram

On 3 March 2011 21:10, Yoram [email protected] wrote:

I’m struggling with the following problem:

I have a class, call it ‘School’.
An instance of School can have all sorts of assets, such as ‘Books’,
‘Tables’, ‘Chairs’, ‘Teachers’.
Each of these asset types is modeled by it’s own model and has a
corresponding table in the db. So, there is a table for 'Chair,
another for ‘Teacher’ and so on.
In summary, in the abstract, each school has_many assets and each
asset belongs_to a school.

Have you looked at Single Table Inheritance?

Colin

Seems like this is just a reverse of a normal polymorphic association. A
tricky problem to solve, since rails doesn’t seem to be designed to
handle
it.

When you call “school_instance.assets” the underlying query would
essentially need to be “give me everything from the books table,
teachers
table, and chairs table”. That’s basically impossible if you understand
the
SQL behind it, since each of these tables has a different schema. Single
Table Inheritance solves that issue.

It is possible to work around it if you want 3 separate tables…

If an asset can belong to only one school, it’s probably easiest to do
away
with the abstract parent class and just have them each “belong_to
:school”

Then in your School class, you can add a method to retrieve all the
associated assets:

class School

has_many :teachers
has_many :books
has_many :chairs

def assets
self.teachers + self.books + self.chairs
end

end

To add an asset, you’ll have to call the specific “teachers <<” “books
<<”
or “chairs <<” However, you could also add a method to handle that as
well:

def add_asset(item)
self.books << item if item.is_a?(Book)
self.chairs << item if item.is_a?(Chair)
self.teachers << item if item.is_a?(Teacher)
end

I’m staying away from STI because I want to be able to add additional
asset types over time, none of which share attributes with other
assets. So - with STI, I could end up with a table that has hundreds
of columns.

It depends what you’re going to do with it… Do you know?

Blog: http://random8.zenunit.com/
Twitter: http://twitter.com/random8r
Learn http://sensei.zenunit.com/
New video up now at http://sensei.zenunit.com/ real fastcgi rails deploy
process! Check it out now!

On Mar 3, 4:42pm, Yoram [email protected] wrote:

I’m staying away from STI because I want to be able to add additional
asset types over time, none of which share attributes with other
assets. So - with STI, I could end up with a table that has hundreds
of columns.

And this is a problem because…? Sounds an awful lot like premature
optimization.

This sort of thing is not supported well by Rails, as it’s tricky to
handle in general (after all, one could create additional subclasses
of Asset on-the-fly so Rails can’t be sure which tables it should be
querying) and even if possible wouldn’t support some operations
cleanly - imagine trying to paginate the assets association…

One thought was using a join model with a polymorphic belongs_to and
has_many :through, but that won’t work either:

One thing that might work would be to have a model that holds the
common attributes that then belongs_to the specific types. Not
optimal, but might work with some usage patterns.

I’m assuming the models you’re using are generic examples and not the
real models, so I’ll skip addressing the fact that there are very few
operations / actions that make sense applied to both ‘Chair’ and
‘Teacher’. Even including both in a single list would seem pretty
peculiar.

–Matt J.

Thanks all…

I ended up solving this much along the lines that Tim recommended -
lots of ‘has_many’ statements in the School model and several custom
built accessors such as those that Tim described.

I appreciate the help.

Yoram