John Wilger
2004-Dec-11 02:58 UTC
Proposal for Modifications to the ActiveRecord Inheritance Model
Hello, Based on recent discussions regarding the Single Table Inheritance model currenlty used by ActiveRecord, and with DHH''s blessing for exploring some alternative implementations, I''ve posted a proposal (in PDF format) here: http://thatwebthing.com/uploads/Proposal_for_Changes_to_the_ActiveRecord_Inheritance_Model_rev1.pdf Please look it over and reply here with any comments/suggestions. Thanks! -- 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
Gleb Arshinov
2004-Dec-11 05:57 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
>>>>> "John" == John Wilger <johnwilger-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> writes:John> Hello, Based on recent discussions regarding the Single John> Table Inheritance model currenlty used by ActiveRecord, and John> with DHH''s blessing for exploring some alternative John> implementations, I''ve posted a proposal (in PDF format) <snip> John> Please look it over and reply here with any John> comments/suggestions. John, Thanks for a great writeup. It was really helpful go get my mind around the problem. Here are my thoughts. I think there are two related groups of issues here. One group is relational-style composition/normalization and the other one is OO "is-a" inheritance. I think that relational stuff is prerequisite for OO stuff. I''ll talk about relational issues only, seeing how it is Friday night :-) * For relational-style composition I want to be able to store an object split up/normalized into multiple tables. Aside of declaring the list of these tables for the class, I want Rails to take care of all the details of loading (including doing a join)/saving into multiple tables. Tables foo [id, a, b, c, bar_id, baz_id] bar [id, d, e] baz [id, f, g] zip [id, i, j, bar_id] class Foo << ActiveRecord::Base base_table :foo includes_table :bar, :baz # has data attributes a, b, c, d, e, f, g end class Zip << ActiveRecord::Base base_table :zip includes_table :bar # has data attributes i, j, d, e end I don''t necessarily think of bar/baz as entities, they can be just mixins with no real meaning of their own. Their sole purpose is normalization. * A modification of the above is when mix-ins are full blown entities, or at least have some behavior in addition to the data. E.g. class Bar << ActiveRecord::Base def my_sum d + e end end class Zip << ActiveRecord::Base base_table :zip includes_class :Bar end puts Zip.find(1).my_sum Including a class appears to be a more general case of including a table. So, for table inclusion a reasonable implementation would automatically generate new classes for mix-in tables, and call on class inclusion to do the rest of the work. * Efficient implementation of class inclusion seems to require automatic piggybacking, which would be a cool feature in itself. E.g. find_all function of Zip would automatically do: def find_all ActiveRecord::Base.find_all(*params, :also_load => :Bar) end An in the same way you could do: Opportunity.find_first(["owner" => 23], :also_load => :Account).account.address which will execute only 1 SQL query. * I want to be able to include multiple children classes/mixins, with all the implementation consequences. Gleb
David Heinemeier Hansson
2004-Dec-11 15:27 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
> Please look it over and reply here with any comments/suggestions.Great work, John. I think it would be helpful to use the patterns for ORM inheritance as defined by Martin Fowler in Patterns of Enterprise Application Architecture. They map well to the concepts outlined in the proposal and provide additional perspectives: * No inheritance is Concrete Table Inheritance[1] (CONTI), but with a door open to have broken hierarchies where a subclass doesn''t inherit all the attributes of the superclass. I think we should attempt to close that door if possible. It should certainly not be an encouraged form of design. * Multi Table Inheritance is Class Table Inheritance[2] (CLSTI). I think we need to solve the problem of knowing what class a given record should be initialized as the same way as with single table inheritance: Have an explicit type column. Also, I don''t think it''s beneficial to be able to instantiate the same record from two different classes. When we''re working in the hierarchy we should rely on polymorphism. Now that we have recognized all three strategies as well-known patterns, I think it''ll hopefully also make it easier to deal meaningfully with Single Table Inheritance (STI) as the default. I''ve found this strategy to be the simplest both conceptually and technically while at the same time doing a decent job at providing inheritance. Hence, I am not at all convinced that CONTI would make a better default. I am willing to accept that STI might surprise for some programmers who expect CONTI to be default. In return for that surprise to some, we get what I perceive as the most generally applicable strategy as the default. I''d like to believe that this is very much in line with The Principle of Least Surprise as practiced by Matz[3]: "''I was surprised by this feature of the language, so therefore Ruby violates the principle of least surprise.'' Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my (Matz''s) surprise. And it means the principle of least surprise after you learn Ruby very well. I register that the "after you learn Ruby very well" seems to map to the fact that none of the people who''ve been working with Active Record for a long time appear to be the most vocal in their "surprise" with STI as the default (please correct me if I''m wrong). So. I propose the following: * STI remains the default inheritance strategy, but is improved with much better error messages that can inform programmers expecting CONTI early on with information on what they need to do to use CONTI or CLSTI (this is easily done in the instantiate Base.instantiate(record) method). * CONTI and CLSTI are added as optional strategies defined in the superclass as: class Person < ActiveRecord::Base inheritance_by :class_table end # Uses CLSTI class Employee < Person end class Account < ActiveRecord::Base inheritance_by :concrete_table end # Uses CONTI class Checking < Account end * A new class variable is added that can control the default inheritance scheme: ActiveRecord::Base.default_inheritance_by = :single_table ActiveRecord::Base.default_inheritance_by = :class_table ActiveRecord::Base.default_inheritance_by = :concrete_table ...if something else than :single_table is set as the default_inheritance_by, you''ll of course be able to overwrite that using class Content < ActiveRecord::Base inheritance_by :single_table end # Uses STI class Post < Content end Finally, I don''t think that the "includes_table" and friends direction is the one to go for Active Record. If you want to denormalize like that, you should use associations to link the pieces together afterwards -- and possibly add some delegation. Even though Active Record has grown beyond some of the limitations of the original pattern[4], it is still not a data mapper and is primarily intended to do 1-1 connections between class and table. [1] http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html [2] http://www.martinfowler.com/eaaCatalog/classTableInheritance.html [3] http://www.artima.com/intv/ruby4.html [4] http://www.martinfowler.com/eaaCatalog/activeRecord.html -- 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
Curt Sampson
2004-Dec-13 09:58 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Sat, 11 Dec 2004, David Heinemeier Hansson wrote:> The principle of least surprise means principle of > least my (Matz''s) surprise. And it means the principle of > least surprise after you learn Ruby very well. > > I register that the "after you learn Ruby very well" seems to map to > the fact that none of the people who''ve been working with Active Record > for a long time appear to be the most vocal in their "surprise" with > STI as the default (please correct me if I''m wrong).This is a valid analysis, though you''re basing this on a tautology (you''re not surprised by things you already know), which makes me doubt the soundness of it. Your proposal, while covering CONTI, CLSTI and STI, seems to leave out the most common case of inheritance in Ruby: none of the above. This is exactly what bit me in the first place: standard OO programming styles and standard refactorings cease to work with ActiveRecord because it forces a particular structure on to the inheritance hierarchy. I think that ActiveRecord suffers for this, in several ways. First of all, I, at least, read this: class Parent end class Child < Parent end as saying that I can use a child anywhere I use a parent, and it will have the same behavior. Unfortunately, doing the same thing in ActiveRecord gives you a child class with very different behavior. There seems to be extensive use in ActiveRecord of global settings instead of polymorphism. I have various problems with this. First, and probably most crippling, Sometimes the settings can''t be used because of their global nature. I need the functionality that setting primary_key_prefix_type to :table_name_with_underscore provides, but I can''t use it because it would affect a few legacy tables that I have yet to get rid of in my system. However, it also tends to make for procedural (using if and case statements) rather than object-oriented (using polymorphism) code, not to mention just plain longer code. Where I would do this: class ActiveRecord::Base def primary_key; "id"; end end class MyActiveRecord < ActiveRecord::Base def primary_key; table_name + "_id"; end end we instead have this: # Accessor for the prefix type that will be prepended to every # primary key column name. The options are :table_name and # :table_name_with_underscore.... Remember that this is a # global setting for all Active Records. cattr_accessor :primary_key_prefix_type @@primary_key_prefix_type = nil class ActiveRecord::Base case primary_key_prefix_type when :table_name Inflector.foreign_key( class_name_of_active_record_descendant(self), false) when :table_name_with_underscore Inflector.foreign_key( class_name_of_active_record_descendant(self)) else "id" end end Plus I still have to figure out where in my program I''m going to set primary_key_prefix_type; as with many global variables there''s no one obvious place to be doing this. The data are getting spread around as well; setting used only in code executed in the context of an ActiveRecord object are being stored in the ActiveRecord.class object. We suppport this with some groping around in the inheritance hierarchy, with which I see all sorts of problems: # Returns the name of the class descending directly from ActiveRecord # in the inheritance hierarchy. def class_name_of_active_record_descendant(klass) if klass.superclass == Base return klass.name elsif klass.superclass.nil? raise ActiveRecordError, "#{name} doesn''t belong in a hierarchy descending from ActiveRecord" else class_name_of_active_record_descendant(klass.superclass) end end This: a) forces the programmer into a fixed inheritance hierarchy; b) kills duck typing; c) is once again (more if statements) rather than polymophic; ...well, I could go on, but you probably get the idea by now. 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
olivier hericord
2004-Dec-13 12:50 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
I''m not a talented ruby programmer and i''m a newbie with rails... but i''m glad to see that the figure 8 in John Wilger pdf was exactly what i was trying to achieve with rails.... http://ohericord.free.fr/CTI.jpg to implement that DB structure i did that: class Person < ActiveRecord::Base end class User < ActiveRecord::Base belong_to:Person end i would love to be abble to do that: class Person < ActiveRecord::Base def person method here to store delete etc... in the people table end class User < Person def user method here to store delete etc... in the users table and in the person table by inheritance.... end because the user table just define fields that are relative to users even if a whole bunch of user informations must be stored in people table.... On Dec 11, 2004, at 4:27 PM, David Heinemeier Hansson wrote:>> Please look it over and reply here with any comments/suggestions. > > Great work, John. > > I think it would be helpful to use the patterns for ORM inheritance as > defined by Martin Fowler in Patterns of Enterprise Application > Architecture. They map well to the concepts outlined in the proposal > and provide additional perspectives: > > * No inheritance is Concrete Table Inheritance[1] (CONTI), but with a > door open to have broken hierarchies where a subclass doesn''t inherit > all the attributes of the superclass. I think we should attempt to > close that door if possible. It should certainly not be an encouraged > form of design. > > * Multi Table Inheritance is Class Table Inheritance[2] (CLSTI). I > think we need to solve the problem of knowing what class a given > record should be initialized as the same way as with single table > inheritance: Have an explicit type column. Also, I don''t think it''s > beneficial to be able to instantiate the same record from two > different classes. When we''re working in the hierarchy we should rely > on polymorphism. > > Now that we have recognized all three strategies as well-known > patterns, I think it''ll hopefully also make it easier to deal > meaningfully with Single Table Inheritance (STI) as the default. I''ve > found this strategy to be the simplest both conceptually and > technically while at the same time doing a decent job at providing > inheritance. > > Hence, I am not at all convinced that CONTI would make a better > default. I am willing to accept that STI might surprise for some > programmers who expect CONTI to be default. In return for that > surprise to some, we get what I perceive as the most generally > applicable strategy as the default. I''d like to believe that this is > very much in line with The Principle of Least Surprise as practiced by > Matz[3]: > > "''I was surprised by this feature of the language, so > therefore Ruby violates the principle of least surprise.'' > Wait. Wait. The principle of least surprise is not for you > only. The principle of least surprise means principle of > least my (Matz''s) surprise. And it means the principle of > least surprise after you learn Ruby very well. > > I register that the "after you learn Ruby very well" seems to map to > the fact that none of the people who''ve been working with Active > Record for a long time appear to be the most vocal in their "surprise" > with STI as the default (please correct me if I''m wrong). > > So. I propose the following: > > * STI remains the default inheritance strategy, but is improved with > much better error messages that can inform programmers expecting CONTI > early on with information on what they need to do to use CONTI or > CLSTI (this is easily done in the instantiate Base.instantiate(record) > method). > > * CONTI and CLSTI are added as optional strategies defined in the > superclass as: > > class Person < ActiveRecord::Base > inheritance_by :class_table > end > > # Uses CLSTI > class Employee < Person > end > > class Account < ActiveRecord::Base > inheritance_by :concrete_table > end > > # Uses CONTI > class Checking < Account > end > > * A new class variable is added that can control the default > inheritance scheme: > > ActiveRecord::Base.default_inheritance_by = :single_table > ActiveRecord::Base.default_inheritance_by = :class_table > ActiveRecord::Base.default_inheritance_by = :concrete_table > > ...if something else than :single_table is set as the > default_inheritance_by, you''ll of course be able to overwrite that > using > > class Content < ActiveRecord::Base > inheritance_by :single_table > end > > # Uses STI > class Post < Content > end > > Finally, I don''t think that the "includes_table" and friends direction > is the one to go for Active Record. If you want to denormalize like > that, you should use associations to link the pieces together > afterwards -- and possibly add some delegation. > > Even though Active Record has grown beyond some of the limitations of > the original pattern[4], it is still not a data mapper and is > primarily intended to do 1-1 connections between class and table. > > [1] > http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html > [2] http://www.martinfowler.com/eaaCatalog/classTableInheritance.html > [3] http://www.artima.com/intv/ruby4.html > [4] http://www.martinfowler.com/eaaCatalog/activeRecord.html > -- > 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 > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
John Wilger
2004-Dec-13 14:40 UTC
[OT] DB Design Program? [Was: Re: Proposal for Modifications to the ActiveRecord Inheritance Model]
On Mon, 13 Dec 2004 13:50:35 +0100, olivier hericord <hangonsoft-GANU6spQydw@public.gmane.org> wrote:> http://ohericord.free.fr/CTI.jpgOlivier, Purely as a matter of curiosity---what program did you use to generate the above table diagram? -- 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-13 14:58 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Sat, 11 Dec 2004 16:27:15 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> I think it would be helpful to use the patterns for ORM inheritance as > defined by Martin Fowler in Patterns of Enterprise Application > Architecture. They map well to the concepts outlined in the proposal > and provide additional perspectives:Agreed. I''ll update the proposal (probably on Monday) to use this terminology. I''ll hang my head in shame and admit that I haven''t read _Patterns_ yet, although it''s been on my list for a while. ;-)> > * No inheritance is Concrete Table Inheritance[1] (CONTI), but with a > door open to have broken hierarchies where a subclass doesn''t inherit > all the attributes of the superclass. I think we should attempt to > close that door if possible. It should certainly not be an encouraged > form of design.By this, I assume you mean that given: class Animal < ActiveRecord::Base inheritance_by :concrete_table def foo "foo" end end class Mammal < Animal _implementation here..._ end class Bird < Animal _implementation here..._ end The database tables _mammals_ and _birds_ would both be required to implement *at least* the same columns as the table _animals_. I''ll admit that this makes sense. If you don''t want to have _all_ of the attributes from an _animals_ table here, then it probably makes more sense to use mixins for the specific functionality. My only concern would be the performance impact of enforcing this.> > * Multi Table Inheritance is Class Table Inheritance[2] (CLSTI). I > think we need to solve the problem of knowing what class a given record > should be initialized as the same way as with single table inheritance: > Have an explicit type column. Also, I don''t think it''s beneficial to be > able to instantiate the same record from two different classes. When > we''re working in the hierarchy we should rely on polymorphism.I have no problem with the explicit _type_ column---I don''t see any other way around the problem that wouldn''t obfuscate the code. However, I _do_hink it is beneficial to be able to instantiate the same record with more than one class. Using the example from my proposal, let''s say we have a Contact class with a descendant Account and a descendant Employee. An Employee should also be able to be an Account (presumably, so that the employee is able to log in to some system), but if Employee descended from Account, then _every_ employee would have to have an account. If we put them at the level of siblings, then it would be easy to have both Employee and Account share common information derived form Contact, but there is no explicit relationship between the two. (This would also enable you to create more specializations of Account [say, an AdminAccount] and have an Employee be able to also be one of those instead of just a regular account. By using an explicit _type_ column, we could have that column store a list (serialized Array?) of classes that the Contact record could be instantiated as. For instance, when we create a new Employee, the _type_ column would contain both Employee and Contact. At this point, you would not be able to instantiate the record as an Account. However, once we create a new Account instance based on the same Contact (the ability to do this will obviously require some new syntax---some thought will have to be given here), Contact will be added to the list stored in _type_, and now Contact will know that it can instantiate as either an Employee _or_ an Account.> > Now that we have recognized all three strategies as well-known > patterns, I think it''ll hopefully also make it easier to deal > meaningfully with Single Table Inheritance (STI) as the default. I''ve > found this strategy to be the simplest both conceptually and > technically while at the same time doing a decent job at providing > inheritance. > > Hence, I am not at all convinced that CONTI would make a better > default. I am willing to accept that STI might surprise for some > programmers who expect CONTI to be default. In return for that surprise > to some, we get what I perceive as the most generally applicable > strategy as the default. I''d like to believe that this is very much in > line with The Principle of Least Surprise as practiced by Matz[3]: > > "''I was surprised by this feature of the language, so > therefore Ruby violates the principle of least surprise.'' > Wait. Wait. The principle of least surprise is not for you > only. The principle of least surprise means principle of > least my (Matz''s) surprise. And it means the principle of > least surprise after you learn Ruby very well. > > I register that the "after you learn Ruby very well" seems to map to > the fact that none of the people who''ve been working with Active Record > for a long time appear to be the most vocal in their "surprise" with > STI as the default (please correct me if I''m wrong).The problem with this attitude in general (and I''m not accusing you of doing so intentionally) is that it says "Whoever has been using this the longest is automatically right---noobs need not complain." And of course those who have been working with it the longest are, at this point, the least vocal in their "surprise". They''re used to it; it''s no longer a surprise. ;-) However...> > So. I propose the following: > > * STI remains the default inheritance strategy, but is improved with > much better error messages that can inform programmers expecting CONTI > early on with information on what they need to do to use CONTI or CLSTI > (this is easily done in the instantiate Base.instantiate(record) > method). > > * CONTI and CLSTI are added as optional strategies> * A new class variable is added that can control the default > inheritance scheme: > > ActiveRecord::Base.default_inheritance_by = :single_table > ActiveRecord::Base.default_inheritance_by = :class_table > ActiveRecord::Base.default_inheritance_by = :concrete_table > > ...if something else than :single_table is set as the > default_inheritance_by, you''ll of course be able to overwrite thatSimply in the interests of maintaining backwards compatibility (as far as I''m concerned), I don''t see a problem with maintining STI as the default given the compromise of being able to easily change this default for the entire application. BTW, I have a patch ready (against repo version 103, though I''ll update and integrate to the latest today) that implements the above for STI and CONTI (haven''t tackled CLSTI yet) with unit tests. Since it could still be considered as a development spike or proof of concept rather than something ready for inclusion in the distribution, is Trac still the best way to get this out there? Or should I send it to you directly, DHH? -- 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
olivier hericord
2004-Dec-14 00:09 UTC
Re: [OT] DB Design Program? [Was: Re: Proposal for Modifications to the ActiveRecord Inheritance Model]
Omnigraffle on macOSX but it''s a hand job....no automatic generation :) but it''s great to build the DB like that...everything is so clear after that On Dec 13, 2004, at 3:40 PM, John Wilger wrote:> On Mon, 13 Dec 2004 13:50:35 +0100, olivier hericord > <hangonsoft-GANU6spQydw@public.gmane.org> wrote: >> http://ohericord.free.fr/CTI.jpg > > Olivier, > > Purely as a matter of curiosity---what program did you use to generate > the above table diagram? > > -- > 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 > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Curt Sampson
2004-Dec-14 03:14 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Mon, 13 Dec 2004, John Wilger wrote:> However, I _do_hink it is beneficial to be able to instantiate the > same record with more than one class. Using the example from my > proposal, let''s say we have a Contact class with a descendant Account > and a descendant Employee. An Employee should also be able to be an > Account (presumably, so that the employee is able to log in to some > system), but if Employee descended from Account, then _every_ employee > would have to have an account.We''re getting into the crux of an object-relational mapping problem here: relational databases can represent things via relations that are not easily represented through inheritance. The currently popular solution to this is to be to reduce the power of the database by moving it back to a hierarchical model, but you seem to be rejecting the reduction in expressive power in this case. It''s sounding to me as if what you really want here is not inheritance per se, but classes representing the joins between other tables. Let''s just review the schema again, to make sure I''ve got this right (or right enough for the sake of this discussion). I''ll use sort of an abbreviated SQL whose meaning should be obvious, and chuck in some sample data fields for each type, as well as the key relationships. TABLE contact (contact_id PRIMARY KEY, name, ...) TABLE account (contact_id PRIMARY KEY REFERENCES contact, salesman, ...) TABLE employee (contact_id PRIMARY KEY REFERENCES contact, salary, ...) Here you have three basic objects, Contact, Account and Employee. In addition, your schema specifies that every Employee is also a Contact, and every Account is also a Contact. All this so far is easily expressible through inheritance, but you may also have Contacts that are both Accounts and Employees, which is where inheritance breaks down. I see a couple of ways of dealing with this. One is to forget about jamming Employee and Account stuff into a common object, and use composition instead. So if you want to know the name of the employee with the highest salary, you fetch that employee and then ask for "employee.contact.name". Another is to have new, perhaps dynamically created objects that mix in appropriate stuff that comes out of a join. If you fetch just "all contacts," none of them will respond to "contact.salary", but if you fetch "all employees" they will all respond to "contact.salary". You could even fetch a list of "all contacts who are both employees and accounts," and all of contact.name contact.salesman contact.salary would be valid properties. Perhaps you want to give these objects names, which could be done automatically based on the relations already expressed in the database: class Contact; end class Employee; end everyone = Employee.fetch_all # Efficient: no join. everyone = ContactEmployee.fetch_all # Magically appearing class. everyone.each { |employee| puts("#{employee.name}, #{employee.salary}") } But that can lead to an explosion of classes as things like ContactAccountEmployee automagically appear. Perhaps just a fetch method that can deal with the joins for us: class Contact; end class Employee; end class Account; end both = Contact.fetch_join([Employee, Account]) both.each { |ea| puts("#{ea.name}, #{ea.salary}, #{ea.salesman}") } I''m not sure how clear all this is, but unfortunately I''m a bit short on time at the moment. Perhaps I can explain again if there are questions. One more issue, though: if you''re going to use inheritance, rather than: class Parent < ActiveRecord::Base inheritance_by :class_table end class Child < Parent end How about this? class Parent < ActiveRecord::Base end class Child < Parent inherit_as_class_table Parent end This allows me to do refactorings I can''t otherwise do: class Parent < ActiveRecord::Base end class Child < Parent ... end class GoodChild < Child inherit_as_class_table Parent end class NaughtyChild < Child inherit_as_class_table Parent end In this case, good and naughty children are stored in separate tables because they have quite different attributes, but they can still share common behavior as in-memory objects. 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
Eric Ocean
2004-Dec-14 03:32 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Dec 13, 2004, at 7:14 PM, Curt Sampson wrote:> One more issue, though: if you''re going to use inheritance, rather > than: > > class Parent < ActiveRecord::Base > inheritance_by :class_table > end > class Child < Parent > end > > How about this? > > class Parent < ActiveRecord::Base > end > class Child < Parent > inherit_as_class_table Parent > endI agree with Curt that subclasses should specify the inheritance model, not the superclass (which requires the superclass to be aware of the needs of subclasses, breaking encapsulation). One of the benefits of OO style is that you can use objects without their consent so long as you stick to their published interface. Best, Eric
Eric Ocean
2004-Dec-14 03:45 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
After pressing the send button, I realized that the "subclasses should specify inheritance" policy is also the reason why STI should not be the default, because it breaks that rule. It''s extremely annoying when you have to modify a superclass because it''s got more magic happening than the interface would indicate. What I initially liked about Rails was that the magic was understandable and predictable. Things like uniform naming conventions (by default) are HUGE timesavers and lower the complexity dramatically. But IMO, regardless of how "simple" and "useful" STI is for some people, making it the default is a bad idea because it''s: (a) easy to add if you want it, and simultaneously (b) a PITA to remove if you don''t. Put another way: STI is a _minor_ time saver if you want it, and a MAJOR time waster if you don''t. That''s not true of the rest of the magic in Rails. Automatic STI is an anomaly and I think many people on the list seem to recognize that. Best, Eric (who still likes Rails a lot :-)
David Heinemeier Hansson
2004-Dec-14 12:27 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
> The database tables _mammals_ and _birds_ would both be required to > implement *at least* the same columns as the table _animals_. I''ll > admit that this makes sense. If you don''t want to have _all_ of the > attributes from an _animals_ table here, then it probably makes more > sense to use mixins for the specific functionality. My only concern > would be the performance impact of enforcing this.This is exactly what I mean. Lets try to see if a check can be made without killing performance. If not, we''ll just recommend it as a way you should design your classes.> If we put them at the level of siblings, then it would be easy to have > both Employee and Account share common information derived form > Contact, but there is no explicit relationship between the two. (This > would also enable you to create more specializations of Account [say, > an AdminAccount] and have an Employee be able to also be one of those > instead of just a regular account.I think this is pretty confusing and overloading and overburdening inheritance. I think it would make more sense to use composition instead, so you have: class Account < ActiveRecord::Base belongs_to :account end class Contact < ActiveRecord::Base has_one :account end class Employee < Contact end I''m not seeing the good of opening up for these kinds of polymorphism circumventions. I think it might clutter the solution space more than open it up.> The problem with this attitude in general (and I''m not accusing you of > doing so intentionally) is that it says "Whoever has been using this > the longest is automatically right---noobs need not complain." And of > course those who have been working with it the longest are, at this > point, the least vocal in their "surprise". They''re used to it; it''s > no longer a surprise. ;-)Newbies do need to complain. And I certainly do need to listen. That doesn''t mean I have to agree all the time, though ;)> BTW, I have a patch ready (against repo version 103, though I''ll > update and integrate to the latest today) that implements the above > for STI and CONTI (haven''t tackled CLSTI yet) with unit tests. Since > it could still be considered as a development spike or proof of > concept rather than something ready for inclusion in the distribution, > is Trac still the best way to get this out there? Or should I send it > to you directly, DHH?Let''s just get it on Trac, then everyone can have a look. And it sounds great! It''ll be very nice to have all of these additional strategies available. -- 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
John Wilger
2004-Dec-14 15:27 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Tue, 14 Dec 2004 13:27:23 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> Let''s just get it on Trac, then everyone can have a look. And it sounds > great! It''ll be very nice to have all of these additional strategies > available.Done. http://dev.rubyonrails.org/trac.cgi/ticket/303 -- 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-14 16:53 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Tue, 14 Dec 2004 13:27:23 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> > The database tables _mammals_ and _birds_ would both be required to > > implement *at least* the same columns as the table _animals_. I''ll > > admit that this makes sense. If you don''t want to have _all_ of the > > attributes from an _animals_ table here, then it probably makes more > > sense to use mixins for the specific functionality. My only concern > > would be the performance impact of enforcing this. > > This is exactly what I mean. Lets try to see if a check can be made > without killing performance. If not, we''ll just recommend it as a way > you should design your classes.The patch I posted earlier does take care of this problem, but again, not sure how it will affect performance in loaded usage. It basically implements a method that is called inside ''initialize'' and ''instantiate'' (so it works on both Base.new and Base.find and related). The method creates an instance of the superclass (unless the current class is a first-level AR class) and then makes sure that each of the attribute columns available in that object are also available on the new/found object. If any of them are missing (because the column doesn''t exist in the table), an exception is raised.> > > If we put them at the level of siblings, then it would be easy to have > > both Employee and Account share common information derived form > > Contact, but there is no explicit relationship between the two. (This > > would also enable you to create more specializations of Account [say, > > an AdminAccount] and have an Employee be able to also be one of those > > instead of just a regular account. > > I think this is pretty confusing and overloading and overburdening > inheritance. I think it would make more sense to use composition > instead, so you have: > > class Account < ActiveRecord::Base > belongs_to :account > end > > class Contact < ActiveRecord::Base > has_one :account > end > > class Employee < Contact > end > > I''m not seeing the good of opening up for these kinds of polymorphism > circumventions. I think it might clutter the solution space more than > open it up.I''m not thinking on totally different lines here. Internally, the AR class would use composition to create the relationships. My thinking is that this would happen automatically, though, rather than requiring explicit work inside the model class definition. class ActiveRecord::Base def self.inherited(child) # do same stuff as currently doing unless self == ActiveRecord::Base has_one Inflector.underscore(child.name).to_sym, :dependent => true child.belongs_to :clsti_parent, :class_name => name # belongs_to shows up in the docs as a public method---I''m assuming this would work # do some other stuff here to map the parent class''s db-related attributes onto child # and make sure that parent gets updated/saved/deleted with child end end end Obviously, I pulled the above out of my arse this very moment---but it illustrates my thinking, I think.> > The problem with this attitude in general (and I''m not accusing you of > > doing so intentionally) is that it says "Whoever has been using this > > the longest is automatically right---noobs need not complain." And of > > course those who have been working with it the longest are, at this > > point, the least vocal in their "surprise". They''re used to it; it''s > > no longer a surprise. ;-) > > Newbies do need to complain. And I certainly do need to listen. That > doesn''t mean I have to agree all the time, though ;) >Of course you don''t always have to agree. If you did, I''d have _serious_ concerns about the future of Rails! ;-) -- 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
Curt Sampson
2004-Dec-15 01:56 UTC
Re: Proposal for Modifications to the ActiveRecord Inheritance Model
On Tue, 14 Dec 2004, John Wilger wrote:> unless self == ActiveRecord::Base > ...Just FYI, this is considered pretty much the antithisis of an an object-oriented style. First, you''re using flow-control statements rather than polymorphism; second, you''re checking types explicitly rather than delegating responsibility to whatever object you happen to be handed. This is not to say that you should never, ever do this, but you may want to ask yourself what you''ve suddenly turned so procedural, because it sure makes life harder for people later on when they try to use inheritance and polymorphism to override your code''s behaviour. 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