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 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com To unsubscribe from this group, send email to rubyonrails-core-unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en -~----------~----~----~----~------~----~------~--~---