Hi, My first big Rails disappointment: single-table inheritance and polymporphic joins don''t play nicely together. On the rails-talk list Josh Susser was kind enough to tell me to steer clear of this combination as it "seems to be right up there with ''never get involved in a land war in Asia'' as a classic blunder." He also said that the Rails implementation for STI and polymorphic joins area incompatible. I have been thinking about it and it seems like the concepts should be compatible. It also doesn''t seem very cool that two Rails core features are incompatible. I''d like to know if there is potentially a way to patch the core so this isn''t a problem. For example, below is a test I wrote to work on this problem. These are my Active Record models and the database records. -------------------------------------------------------------------- class Comment < ActiveRecord::Base belongs_to :resource, :polymorphic=>true end class Person < ActiveRecord::Base has_many :comments, :as=>:resource, :dependent=>:destroy end class Customer < Person end class Employee < Person end -------------------------------------------------------------------- I seed the database with two people. mysql > SELECT * FROM people; +----+----------+------+---------+------------+ | id | type | name | balance | department | +----+----------+------+---------+------------+ | 1 | Customer | Carl | 123 | NULL | | 2 | Employee | Earl | NULL | Accounting | +----+----------+------+---------+------------+ -------------------------------------------------------------------- I can create a comment by POST /comment/create Parameters: {"commit"=>"Submit", "action"=>"create", "controller"=>"comment", "comment"=>{"resource_type"=>"Customer", "body"=>"asdf", "resource_id"=>"1"}} With these results mysql > SELECT * FROM comments; +----+---------------+-------------+------+------------+ | id | resource_type | resource_id | body | created_at | +----+---------------+-------------+------+------------+ | 1 | Customer | 1 | asdf | 2006-09-02 | +----+---------------+-------------+------+------------+ -------------------------------------------------------------------- When I do GET /customer/show/1, I don''t see the comment because the generated SQL won''t find it SELECT * FROM people WHERE (people.`id` = ''1'' ) AND ( (people.`type` ''Customer'' ) ) LIMIT 1 SELECT * FROM comments WHERE (comments.resource_id = 1 AND comments.resource_type = ''Person'') During a destroy operation POST /customer/destroy/1 the same two lines of SQL are generated and the comment is not deleted from the database. In the first SQL statement the STI is clever enough to look for a Customer not a Person. The second statement is the problem. Presumably, Active Record hardcodes ''Person'' into some association string when when the person.rb model file is first read and the has_many method is called. Then this string is used whenever someone does Customer.find(1).comments. Is this true? Now, I know that I could create this comment with resource_type ''Person'' but that isn''t right conceptually and causes other problems. When someone goes Comment.find(1).resource they will get an instance of Person when they should get an instance of Customer. -------------------------------------------------------------------- Does the has_many association exist only once for the Person model or does the has_many exist twice (ie once for Employee and once for Customer)? If it is the later then could Active Record hardcode the class name instead of ''Person''? What I would really like to know is if this sti-polymorphic incompatibility solvable or is this so deeply ingrained in Active Record that solving it would be a year long nightmare? It seems like it would be very nice if these two features played well together and be a nice bit of polish on Active Record and Rails. Thank you, Peter --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---
Peter, In general, STI and polymorphic joins work great togeher, as long as you always store the base class name as the type value, rather than the descendant class name. Thus, if you were to store Person (rather than Customer) as resource_type, you''d be fine. - Jamis Peter Michaux wrote:> Hi, > > My first big Rails disappointment: > > single-table inheritance and polymporphic joins don''t play nicely together. > > On the rails-talk list Josh Susser was kind enough to tell me to steer > clear of this combination as it "seems to be right up there with > ''never get involved in a land war in Asia'' as a classic blunder." He > also said that the Rails implementation for STI and polymorphic joins > area incompatible. I have been thinking about it and it seems like the > concepts should be compatible. It also doesn''t seem very cool that two > Rails core features are incompatible. I''d like to know if there is > potentially a way to patch the core so this isn''t a problem. > > For example, below is a test I wrote to work on this problem. These > are my Active Record models and the database records. > > -------------------------------------------------------------------- > > class Comment < ActiveRecord::Base > belongs_to :resource, :polymorphic=>true > end > > class Person < ActiveRecord::Base > has_many :comments, :as=>:resource, :dependent=>:destroy > end > > class Customer < Person > end > > class Employee < Person > end > > -------------------------------------------------------------------- > I seed the database with two people. > > mysql > SELECT * FROM people; > > +----+----------+------+---------+------------+ > | id | type | name | balance | department | > +----+----------+------+---------+------------+ > | 1 | Customer | Carl | 123 | NULL | > | 2 | Employee | Earl | NULL | Accounting | > +----+----------+------+---------+------------+ > > -------------------------------------------------------------------- > I can create a comment by POST /comment/create > > Parameters: {"commit"=>"Submit", > "action"=>"create", > "controller"=>"comment", > "comment"=>{"resource_type"=>"Customer", "body"=>"asdf", > "resource_id"=>"1"}} > > With these results > > mysql > SELECT * FROM comments; > > +----+---------------+-------------+------+------------+ > | id | resource_type | resource_id | body | created_at | > +----+---------------+-------------+------+------------+ > | 1 | Customer | 1 | asdf | 2006-09-02 | > +----+---------------+-------------+------+------------+ > > -------------------------------------------------------------------- > > When I do GET /customer/show/1, I don''t see the comment because the > generated SQL won''t find it > > SELECT * FROM people WHERE (people.`id` = ''1'' ) AND ( (people.`type` > ''Customer'' ) ) LIMIT 1 > SELECT * FROM comments WHERE (comments.resource_id = 1 AND > comments.resource_type = ''Person'') > > During a destroy operation POST /customer/destroy/1 the same two lines > of SQL are generated and the comment is not deleted from the database. > > In the first SQL statement the STI is clever enough to look for a > Customer not a Person. The second statement is the problem. > Presumably, Active Record hardcodes ''Person'' into some association > string when when the person.rb model file is first read and the > has_many method is called. Then this string is used whenever someone > does Customer.find(1).comments. Is this true? > > Now, I know that I could create this comment with resource_type > ''Person'' but that isn''t right conceptually and causes other problems. > When someone goes Comment.find(1).resource they will get an instance > of Person when they should get an instance of Customer. > > -------------------------------------------------------------------- > > Does the has_many association exist only once for the Person model or > does the has_many exist twice (ie once for Employee and once for > Customer)? If it is the later then could Active Record hardcode the > class name instead of ''Person''? > > What I would really like to know is if this sti-polymorphic > incompatibility solvable or is this so deeply ingrained in Active > Record that solving it would be a year long nightmare? It seems like > it would be very nice if these two features played well together and > be a nice bit of polish on Active Record and Rails. > > Thank you, > Peter > > > >--~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---
On 9/2/06, Jamis Buck <jamis@37signals.com> wrote:> In general, STI and polymorphic joins work great togeher, as long as you > always store the base class name as the type value, rather than the > descendant class name. Thus, if you were to store Person (rather than > Customer) as resource_type, you''d be fine.Hi Jamis, Thanks for the reply. I have tried what you said and it did work for me. However it gets a messy when creating a new comment if some of the models that can have comments are using STI and some aren''t or different depths of STI are being used (haven''t tried the later). It becomes necessary to test for which model the comment is being created. If it is a Customer then use Person for resource_type. However if it is an Article then just use Article. Also how do you get get around the Comment.find(1).resource giving back a Person instance when it really should give back a Customer instance? Thank you, Peter --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---
Peter Michaux wrote:> On 9/2/06, Jamis Buck <jamis@37signals.com> wrote: > >> In general, STI and polymorphic joins work great togeher, as long as you >> always store the base class name as the type value, rather than the >> descendant class name. Thus, if you were to store Person (rather than >> Customer) as resource_type, you''d be fine. > > Hi Jamis, > > Thanks for the reply. I have tried what you said and it did work for me. > > However it gets a messy when creating a new comment if some of the > models that can have comments are using STI and some aren''t or > different depths of STI are being used (haven''t tried the later). It > becomes necessary to test for which model the comment is being > created. If it is a Customer then use Person for resource_type. > However if it is an Article then just use Article.You can just do Customer.base_class (or Article.base_class) to obtain the base ActiveRecord class for a particular subclass.> Also how do you get get around the Comment.find(1).resource giving > back a Person instance when it really should give back a Customer > instance?Note that AR::Base#find will always "do the right thing". In other words, if you do Person.find(1), and record #1 has a type of Customer, a Customer record will be returned. You''ll get the right type back, not because of the Comment#resource_type column, but because of the Person#type column. Basically, the resource_type column is used _only_ to determine which table to pull the associated data from. - Jamis --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---
On 9/2/06, Jamis Buck <jamis@37signals.com> wrote:> > > > > In general, STI and polymorphic joins work great togeher, as long as you > > > always store the base class name as the type value, rather than the > > > descendant class name. Thus, if you were to store Person (rather than > > > Customer) as resource_type, you''d be fine. > > > > However it gets a messy when creating a new comment if some of the > > models that can have comments are using STI and some aren''t or > > different depths of STI are being used (haven''t tried the later). It > > becomes necessary to test for which model the comment is being > > created. If it is a Customer then use Person for resource_type. > > However if it is an Article then just use Article. > > You can just do Customer.base_class (or Article.base_class) to obtain > the base ActiveRecord class for a particular subclass.Great! I added this to my comment class and it seems to work well def resource_type=(sType) super(sType.constantize.base_class.to_s) end> > Also how do you get get around the Comment.find(1).resource giving > > back a Person instance when it really should give back a Customer > > instance? > > Note that AR::Base#find will always "do the right thing". In other > words, if you do Person.find(1), and record #1 has a type of Customer, a > Customer record will be returned. You''ll get the right type back, not > because of the Comment#resource_type column, but because of the > Person#type column. Basically, the resource_type column is used _only_ > to determine which table to pull the associated data from.Really cool! Jamis, thank you very much! I wasted a week on this problem trying to solve it in all sorts of bizarre ways. Now I have hope STI and polymorphic joins can do the job for me the way I was hoping. Thanks again, Peter --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---