Theoretical: Should models be subclasses of AR or mix it in?

All,

My apologies if this is a little long-winded and stream of
consciousness-y :).

SUMMARY: Why do we extend ActiveRecord instead of mixing it in to our
model classes?

Been thinking about my app’s models and starting to want to build
something of a hierarchy. I have some commonality across 3 of my model
classes and I’d liek to aggregate the behavior in a superclass.
However, I am stuck with all of my models being descendants of AR. So
within my set of models, I can’t aggregate common behavior into a
superclass within that hierarchy since my DB tables (legacy) do not
reflect that hierarchical structure.

So I’ve been thinking about why the model relationship to AR is
extension and not composition (why do models extend AR instead of
include/mixin it). I can certainly agree with the idea that the essence
of a model class is that it’s an OR mapping, but if you mixed in the
OR-mapping-ness instead of forcing it through your one shot at
inheritance (extending AR), you would still have the freedom to have
more non-database concerns be addressed (like a model object hierarchy
that doesn’t map to your persistence layer).

Given the fact that model classes do extend AR, you can certainly create
“pure model” classes that then mixin the AR subclasses or delegate to
the AR subclasses to handle persistence and then you can have whatever
model object heirarchy you want. Which I guess makes sense since your
application model shouldn’t necessarily be bound so tightly to your
persistence layer. Another option is to mix in your common domain
behavior from a module (“interface”).

My thought is that the model side of Rails is sort of presented as
though you can handle everything within the constraints of being an AR
descendant, but in fact, the AR descendants are just a persistence
object layer.

It just feels like the persistence piece should be what is mixed in, not
the domain behavior.

Any thoughts?

Wes

If you mixed in AR instead of subclassing it, would the whole
method_missing scheme work?

On one of my projects I actually had a “base” active record model and
subclassed it for other models - would that work for you?

As an alternative to changing how models use AR, you could create a
module
with the shared methods and mix that into each of your models

mark

Yup, absolutely.

Recognizing that you can go either way, I’m questioning why the AR
functionality isn’t mixed in since it’s more operational in nature (AR
“enables” a model to be easily backed in a relational store).

Make sense?

Wes

No I’ve had to do that as well.

I’m talking about several model objects that all share certain
attributes, and for whatever reason, are persisted to different tables.

So, maybe model1, model2, model3 share attr1, attr2 but they each have 5
other attrs that differ.

One way to go is to take advantage of AR to impose a pseudo-object model
onto your DB schema and store the 2 common attrs in one table and then
have 3 separate tables to store the 5 differing attributes. This would
lend itself to a “base” object that maps to the “common” table and 3
concrete AR objects that each map to a specific table.

But, many times, you either can’t (legacy) schema, or don’t want to get
into the business of representing your object model so directly in the
schema. You don’t mind the overhead of storing the same types of
attributes in three different tables because keeping the entities in one
place is more important than the “object purity” of the relational
schema.

There is an ability to declare the superclass of a set of classes as
“abstract” which implies that it doesn’t have a persistent table behind
it, but I don’t think that you can put any code which you would expect
to interact with a table to read or write in that class.

Here’s a specific example. My app. stores filesystem artifacts, like
images, HTML docs, and stylesheets. All of these objects share a
filesystem path. They are curently efined as separate model objects.
I’d like to centralize the path accessor methods to make things DRYer.
And this is how I came to write the post.

Wes

On 8/15/06, Wes G. [email protected] wrote:

All,

My apologies if this is a little long-winded and stream of
consciousness-y :).

SUMMARY: Why do we extend ActiveRecord instead of mixing it in to our
model classes?

I have an ModelHelper module in /lib which reopens AR::Base and holds
methods I want to share across all models. I just need to be careful I
don’t overwrite any critical AR::Base functionality, of course.

jh


James H.
Web application developer
Vancouver, BC

“Developing a coherent political analysis is in many respects
contingent upon an ability to connect one context to another, a
process not dissimilar to playing the kid’s game of dot-to-dot.”

  • Ward Churchill, from ‘“A Government of Laws”?’

Daniel H. wrote:

On one of my projects I actually had a “base” active record model and
subclassed it for other models - would that work for you?

Could you explain what you’ve gotten to work? I tried a couple of
different schemes and was unable to get a subclass of a subclass of
ActiveRecord::Base to store correctly.

In my case, I had class A < ActiveRecord::Base and class B < A. A had
two attributes that I would expect to be common to other subclasses of
A. B had its own set of attributes. I was unable to get B to
successfully save.

Wes

My testing shows that the only way to have an ActiveRecord backed object
heirarchy with more than one level is to have the table backing the
superclass contain all of the potential attributes of any of its
subclasses.

So if A < ActiveRecord::Base, B < A, and C < B, and

A has attr1, attr2
B has attr3
C has attr4.

Assume that attr1 and attr2 are required (NOT NULL in the DB).

you would create the table to back A, and then depending on whether you
store B or C, then you will get either attrs 1,2,3 (saving class B) or
attrs 1,2,4 (saving class C) to be saved in class A’s backing table.

Wes

On Sunday 20 August 2006 18:24, Wes G. wrote:

Assume that attr1 and attr2 are required (NOT NULL in the DB).

you would create the table to back A, and then depending on whether you
store B or C, then you will get either attrs 1,2,3 (saving class B) or
attrs 1,2,4 (saving class C) to be saved in class A’s backing table.

Wes

That sounds like single inheritance? Except without a type field?

I actually could have used single table inheritance in my code, but
thought
the two relations I wanted to store, “carpools” and “carpool
notification
erquests”, were distinct enough that they belonged in different schemas.
Also, I wanted to avoid having NULL fields. Here’s the relevant
migration:

create_table “carpool_notification_requests” do |t|
t.column “email”, :string, :default => “”, :null => false
t.column “starting_city_id”, :integer, :default => 0, :null => false
t.column “destination_city_id”, :integer, :default => 0, :null =>
false
end

create_table “carpools” do |t|
t.column “starting_city_id”, :integer, :limit => 10, :default => 0,
:null =>
false
t.column “destination_city_id”, :integer, :limit => 10, :default => 0,
:null
=> false
t.column “leave_at”, :time, :null => false
t.column “return_at”, :time, :null => false
t.column “email”, :string, :limit => 70, :null => false
t.column “notes”, :text
end

And the subclassing model:
class CarpoolNotificationRequest < Carpool
set_table_name ‘carpool_notification_requests’

Carpool has validation which don’t make sense here

def validate
end

end

The reason I subclassed Carpool was to inherit its belongs_to relations
and
some of its methods.

I hope this helps!