David Heinemeier Hansson
2004-Dec-14 12:36 UTC
The defaults of single-table inheritance (Part 911)
So I''ve been thinking. How could single-table inheritance remain the default mode of operations while still appeasing the desire to work with inheritance in a way that didn''t relate to mapping. I think I just might have the first swing at such an approach. If the inheritance_column isn''t available in the table, then single-table inheritance doesn''t kick in. If it is present, everything works as it does now. The implementation is terribly simple: def descends_from_active_record? superclass == Base || !columns_hash.has_key?(inheritance_column) end In fact, it''s so simple that I''m suspecting more might need to be done. But let''s hear from the expert in inheritance-without-mapping, Curt Sampson, on that. I also improved the error message you get if you by accident attempt to use a column called "type" for something else than storing the inheritance class data. Example: ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: ''bad_class!''. This error is raised because the column ''type'' is reserved for storing the class in case of inheritance. Please rename this column if you didn''t intend it to be used for storing the inheritance class or overwrite Company.inheritance_column to use another column for that information. Could it be? Happiness and bliss all around in inheritance land :)? I think it just might! -- David Heinemeier Hansson, http://www.basecamphq.com/ -- Web-based Project Management http://www.rubyonrails.org/ -- Web-application framework for Ruby http://macromates.com/ -- TextMate: Code and markup editor (OS X) http://www.loudthinking.com/ -- Broadcasting Brain
On Tue, 14 Dec 2004 13:36:45 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> I think I just might have the first swing at such an approach. If the > inheritance_column isn''t available in the table, then single-table > inheritance doesn''t kick in. If it is present, everything works as it > does now. The implementation is terribly simple: > > def descends_from_active_record? > superclass == Base || !columns_hash.has_key?(inheritance_column) > endActually, it is _almost_ just that simple. This is similar to what I''ve done in some of the development spikes I''ve been working on to implement CONTI and CLSTI. However, I switched gears at one point to lokking at all of the places where ''descends_from_active_record?'' is called, and calling it only on the condition that STI is being used. This seemed to be more expressive in terms of the code, because---unless we are using STI---we don''t actually _care_ if the class descends directly from ActiveRecord::Base. They both achieve the same thing, but the latter seems to provide a better explanation in the code itself at the point where we are doing the real work. I''m going to post those patches to trac today (will let you know), and you should get a better feel for what I mean. -- Regards, John Wilger ----------- Alice came to a fork in the road. "Which road do I take?" she asked. "Where do you want to go?" responded the Cheshire cat. "I don''t know," Alice answered. "Then," said the cat, "it doesn''t matter." - Lewis Carrol, Alice in Wonderland
Jason Anderson
2004-Dec-14 21:36 UTC
Re: The defaults of single-table inheritance (Part 911)
Thanks for the new error message! I was having trouble with that exact problem last night, and it took me about two hours to notice the section in the docs which mentioned the ''type'' column. The new message makes things a lot clearer. Cheers, Jason Anderson jason-sz1IRuf4Ssz3oGB3hsPCZA@public.gmane.org http://www.thenewjhp.com On Tue, 14 Dec 2004 13:36:45 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote: (snip)> I also improved the error message you get if you by accident attempt to > use a column called "type" for something else than storing the > inheritance class data. Example: > > ActiveRecord::SubclassNotFound: > The single-table inheritance mechanism failed > to locate the subclass: ''bad_class!''. This error > is raised because the column ''type'' is reserved > for storing the class in case of inheritance. > > Please rename this column if you didn''t intend it > to be used for storing the inheritance class or > overwrite Company.inheritance_column to use another > column for that information.
Curt Sampson
2004-Dec-15 02:04 UTC
Re: The defaults of single-table inheritance (Part 911)
On Tue, 14 Dec 2004, David Heinemeier Hansson wrote:> So I''ve been thinking. How could single-table inheritance remain the > default mode of operations while still appeasing the desire to work > with inheritance in a way that didn''t relate to mapping. > ... > In fact, it''s so simple that I''m suspecting more might need to be done. > But let''s hear from the expert in inheritance-without-mapping, Curt > Sampson, on that.Well, leaving aside the fact that I don''t agree with this as a default in the first place, I think that this is about as good as it''s going to get, especially now that there''s a reasonable chance that if you get surprised by this you''re going to hit an error message that explains what''s going on. cjs -- Curt Sampson <cjs-gHs2Wiolu3leoWH0uzbU5w@public.gmane.org> +81 90 7737 2974 http://www.NetBSD.org Make up enjoying your city life...produced by BIC CAMERA
John Wilger
2004-Dec-15 17:18 UTC
Another Go at a CLSTI implementation [Was: Re: The defaults of single-table inheritance (Part 911)]
On Tue, 14 Dec 2004 13:36:45 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> So I''ve been thinking. How could single-table inheritance remain the > default mode of operations while still appeasing the desire to work > with inheritance in a way that didn''t relate to mapping. > > I think I just might have the first swing at such an approach. If the > inheritance_column isn''t available in the table, then single-table > inheritance doesn''t kick in. If it is present, everything works as it > does now. The implementation is terribly simple: > > def descends_from_active_record? > superclass == Base || !columns_hash.has_key?(inheritance_column) > endOK, here''s another idea for implementing the various inheritance methods (specifically CLSTI). If we use the idea that David provided above, STI and CONTI are essentially taken care of by the database schema automatically. This seems to be a pretty good compromise between using STI as the default and not using it as the default---it''s only the _default_ if the ''type'' column is defined. Also, I''ve come to see Curt''s point about how my previous stab at CLSTI has some problems (correct me if I''m wrong, but I think it had mainly to do with the fact that the superclass was the one mucking about with the subclass''s implementation and forcing it to use CLSTI based on what the superclass wanted). How about this: class Contact < ActiveRecord::Base # do stuff here as normal end class Employee < Contact inherit_table_for :contact #do custom stuff here end In the above code, the ''inherit_table'' method would be the line used to specify that we are using CLSTI. This would be a specialization of the ''belongs_to'' method that would also override the ''method_missing'' method for this class so that, when assigning to or reading from an attribute that actually maps to the ''Contact'' class, it would use the method on the object stored in the ''contact'' property (an instance of Contact as per the ''belongs_to'' spec.) Additionally, the ''save'' method would be extended to also save the associated Contact object whenever the Employee object was saved (and any validation errors on the Contact object could be propogated as validation errors on Employee). My thought is that this would all do the same stuff that you would do within the Employee class definition if you were just going to use a ''belongs_to'' relationship, but it would cut down on the amount of code you would have to write within that class definition. If you needed to customize the behavior in a way not supported by ''inherit_table_for'', you could obviously just implement the logic yourself on an as-needed basis. How does this sound? Does it make sense? Does it address the concerns raised? Does it raise any new ones? -- Regards, John Wilger ----------- Alice came to a fork in the road. "Which road do I take?" she asked. "Where do you want to go?" responded the Cheshire cat. "I don''t know," Alice answered. "Then," said the cat, "it doesn''t matter." - Lewis Carrol, Alice in Wonderland
John Wilger
2004-Dec-15 17:22 UTC
Re: Another Go at a CLSTI implementation [Was: Re: The defaults of single-table inheritance (Part 911)]
On Wed, 15 Dec 2004 12:18:49 -0500, John Wilger <johnwilger-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> class Contact < ActiveRecord::Base > # do stuff here as normal > end > > class Employee < Contact > inherit_table_for :contact > #do custom stuff here > endOne more point: you could also use the ''inherit_table_from'' method to link a class that isn''t a superclass: class Supervisor < ActiveRecord::Base inherit_table_from :contact end Which would allow you to combine the table definitions without inheriting the class logic contained in Contact. -- Regards, John Wilger ----------- Alice came to a fork in the road. "Which road do I take?" she asked. "Where do you want to go?" responded the Cheshire cat. "I don''t know," Alice answered. "Then," said the cat, "it doesn''t matter." - Lewis Carrol, Alice in Wonderland
David Heinemeier Hansson
2004-Dec-17 12:46 UTC
Re: Another Go at a CLSTI implementation [Was: Re: The defaults of single-table inheritance (Part 911)]
> My thought is that this would all do the same stuff that you would do > within the Employee class definition if you were just going to use a > ''belongs_to'' relationship, but it would cut down on the amount of code > you would have to write within that class definition. If you needed to > customize the behavior in a way not supported by ''inherit_table_for'', > you could obviously just implement the logic yourself on an as-needed > basis.It''s an interesting idea. But wouldn''t all of employee methods already be available in contact once you do Contact < Employee? I''m just wondering what would be needed in order to turn inheritance into composition and whether it''s a viable approach. Please do explore, though. Anyway, a reason that I think we might need to have the superclass know _something_ about the subclasses is because I''d like this to work with all the strategies: Content < ActiveRecord::Base Post < Content SummerPost < Post Article < Content Content.find_all # => [ <Post>, <Post>, <SummerPost>, <Article>, <Article> ] In other words, the superclass should be capable of returning instances of its subclasses when the finders are used. I recognize that this would seem funny in a OO-pure world, but I believe its one of the concessions that it makes sense to trade with the database for pragmatic reasons. If we can make that happen someway through the subclasses, I''d be happy too. And actually, thinking about it, I do think we can. Ruby provides a bunch of callbacks, so we could modify the superclass at runtime with this information. P.S.: John, it would be helpful if the diffs provided for exploring code could come without the extra baggage of stylistic reformations. It''s a bit hard to see the handful of lines that represents the actual patch amongst the many lines of style changes. Also, it''s always a good idea just to follow the style as currently present in the code. This is not something specifically for John, but for all contributors (such as using "and" instead of && etc). -- David Heinemeier Hansson, http://www.basecamphq.com/ -- Web-based Project Management http://www.rubyonrails.org/ -- Web-application framework for Ruby http://macromates.com/ -- TextMate: Code and markup editor (OS X) http://www.loudthinking.com/ -- Broadcasting Brain