A different way to do polymorphic associations

I did not like the way that Rails did polymorphic associations. I
found it impossible to add constraints into the database itself to
make sure that things were hooked up like they were suppose to be.
And doing the validation of all the if’s, and’s, and but’s in Rails
appeared to me would lead to very expensive validations.
I also did not like the concept behind single table inheritance. I
find that whole concept extremely lame.

Mostly from ideas I got from the Postgres mailing list, I implemented
another way to do polymorphic associations. It seems to be working
for me. I thought I would share it. There are definitely some rough
edges but those are not getting in my way right now. I’ve managed to
tip toe around and get this to work with no modifications to Rails and
only a small amount of work (once the tricks are figured out).

The polymophic base class in this example I call ItemBase (with a
table of item_bases). The migration looks like this:

class CreateItemBases < ActiveRecord::Migration
def self.up
create_table :item_bases, :id => false do |t|
t.integer :item_id, :null => false
t.string :item_type, :null => false
t.timestamps
end
execute “ALTER TABLE item_bases ADD CONSTRAINT
fk_item_bases_item_type
FOREIGN KEY (item_type) REFERENCES
item_types(class_name)”
execute "CREATE OR REPLACE FUNCTION item_id_test(item_id INTEGER,
item_type TEXT)
RETURNS BOOLEAN AS $$
DECLARE
tn TEXT;
qry TEXT;

             BEGIN
                 SELECT INTO tn table_name FROM item_types
                     WHERE class_name = item_type;
                 IF NOT FOUND THEN
                     RETURN FALSE;
                 END IF;
                 qry = 'SELECT item_id FROM ' || quote_ident(tn)

|| ’ AS tn ’ ||
'WHERE tn.item_id = ’ || item_id::text ||
‘;’;
EXECUTE qry;
IF NOT FOUND THEN
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;"
execute “ALTER TABLE item_bases ADD CONSTRAINT
ck_item_bases_item_id
CHECK (item_id_test(item_id, item_type))”
execute “ALTER TABLE item_bases ADD CONSTRAINT key_item_id
UNIQUE (item_id)”
execute “ALTER TABLE item_bases ADD CONSTRAINT key_item_tuple
UNIQUE (item_id, item_type)”
execute “CREATE SEQUENCE item_bases_item_id_seq”
end

def self.down
drop_table :item_bases
execute “DROP SEQUENCE item_bases_item_id_seq”
execute “DROP FUNCTION item_id_test(INTEGER, TEXT)”
end
end

The model looks like this:

class ItemBase < ActiveRecord::Base
set_primary_key “item_id”
belongs_to :item, :polymorphic => true

def id
item_id
end
end

In this example, the subclasses can be companies, people, etc. The
migration to create the companies table is:

class CreateCompanies < ActiveRecord::Migration
MY_CLASS_NAME = “Company”
MY_TABLE_NAME = “companies”

def self.up
create_table MY_TABLE_NAME, :id => false do |t|
t.integer :item_id, :null => false
t.string :item_type, :null => false, :default =>
MY_CLASS_NAME
t.timestamps
#
# Attributes start here
#
t.string :name, :null => false
end
execute “ALTER TABLE #{MY_TABLE_NAME} ALTER COLUMN item_id
SET DEFAULT nextval(‘item_bases_item_id_seq’)”
execute “ALTER TABLE #{MY_TABLE_NAME} ADD CONSTRAINT
#{MY_TABLE_NAME}item_type
CHECK (item_type = ‘#{MY_CLASS_NAME}’)"
execute "ALTER TABLE #{MY_TABLE_NAME} ADD CONSTRAINT
fk
#{MY_TABLE_NAME}_item_id
FOREIGN KEY (item_id, item_type)
REFERENCES item_bases(item_id, item_type)
ON DELETE CASCADE INITIALLY DEFERRED”
ItemType.create(:table_name => MY_TABLE_NAME, :class_name =>
MY_CLASS_NAME)
end

def self.down
drop_table MY_TABLE_NAME
ItemType.destroy_all(:table_name => MY_TABLE_NAME)
end
end

I have not written a generator yet but instead I create a couple of
constants and then the rest of the migration is the same except for
the subclass specific fields.

The model is:

class Company < ActiveRecord::Base
set_primary_key “item_id”
set_sequence_name “item_bases_item_id_seq”
has_one :item_base, :as => :item

subclass specific validations

validates_presence_of :name
end

First, as you can see, most of the migration is introducing
constraints into the database. These are PostgreSQL specific. For
other databases, there are probably similar concepts. And for those
who do not believe in db constraints, just don’t do them.

In the constraints, another table is referenced: item_types. Its
associted class is ItemTypes. It simply maps table names to class
names for the db to use. As part of the migration for each subclass,
I add in the needed row into the item_types table.

In brief, for item_bases, the id is constrainted to be unique. I also
constrain the ( id, type) tuple so I can use it as a foreign key. The
item_id_test verifies that an entry id and type are in the proper
table. Going the other direction, the companies table has a deferred
constraint that a matching entry is in item_bases. At the time of the
commit, both entries must be there. There is no way for duplicates or
other weird mixtures. These same constraints could be lifted up into
Rails if that is the user’s choice.

The main difference between this style and the normal Rails style of
polymophic association is that the id and type are in both the base
and the subclass. The base does not have its own id. So, with an id,
you can find the base instance. From the base instance, you can find
the subclass instance. Also, from the subclass instance, you can find
the base instance. This is roughly the same as normal Rails – the
only difference is the id is the same for both the base instance and
the subclass instance. (The type is the same too.)

I find this works well for me. It is robust and somewhat fool proof
once it is set up. There is nothing here that could not be
generalized so a generator could be written to create the base class
and a second generator that would create each subclass passing in the
name of the base class.

Someone has mentioned that this can not be extended. i.e. the base
class could not itself be a subclass of another class. But I don’t
understand why not. I have not tried it so I can’t say for sure.
But, the subclass specific attributes could be foo_id and foo_type
which would form the base of the foo class.

The one rough edge is there is no wrapper around the whole thing that
will return an Item. I don’t know enough yet about the magic of Rails
and ActiveRecord to do this. It seems like it could be done somehow.

If anyone finds this useful, let me know. I’d like to hear what
others think about it.

Thank you,
Perry

I think I may need to go this route. However first I need to exhaust
the simpler options.

I’m still very new to ROR and it has been decades since I’ve done any
serious programming.
I like to think my design skills are up to date.

I would like to subclass a class in the model, and have its
persistance handled for me (but I guess
it isn’t so the first thing I need to do is find a pattern that works
in most cases)

In my case I would like to say

A < Active record

B < A

and when I did a list (just a plain scaffolding list) of As I would
get all the As and all the Bs listing only A columns

when I do a list of Bs I should get Bs only, with their columns from A
and B.

Seems like this should be the default behavior, but I’m even having
trouble finding it anywhere.
None of the examples in the books I’m reading have inheritance in the
domain model.

Thanks

On Aug 14, 10:05 am, Perry S. [email protected]

mkumba wrote:

I think I may need to go this route. However first I need to exhaust
the simpler options.

I’m still very new to ROR and it has been decades since I’ve done any
serious programming.
I like to think my design skills are up to date.

I would like to subclass a class in the model, and have its
persistance handled for me (but I guess
it isn’t so the first thing I need to do is find a pattern that works
in most cases)

In my case I would like to say

A < Active record

B < A

and when I did a list (just a plain scaffolding list) of As I would
get all the As and all the Bs listing only A columns

when I do a list of Bs I should get Bs only, with their columns from A
and B.

So far, it sounds like you want just “Single Table Inheritance” which
rails does out of the box.

http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs