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 -~----------~----~----~----~------~----~------~--~---