This is part 4 of the series; part 3 is here.
I just finished another 14-hour Rails coding session and things are still going fairly well. The data structures that were originally implemented using my custom PHP tuple code have translated into about 50 SQL tables stitched together using the Rails ActiveRecord framework.
I've only encountered one design issue so far with Rails, which I've posted to the Rails forum. Here's the issue:
I have a School object and an Address object which are mapped to SQL tables called "schools" and "addresses", respectively. Since a School can have an Address, the schools table contains an address_id column which references a row in the addresses table. So far, so good: this is standard database design.
In Rails, if table A contains a reference to table B, then the Ruby class A is marked with a "belongs_to: B" and the Ruby class B is marked with "has_one: A". Then, if you turn dependencies on, Rails will automatically destroy A if B is destroyed. In the Rails tutorials, they often use A=Invoice and B=Order, so if an Order is destroyed, its Invoice is destroyed as well.
The problem is, in my case A=School and B=Address, and it makes no sense for the School to be destroyed when the Address is destroyed; it should be the other way around.
At first glance, it looks to me that ActiveRecord is assuming that if a table A contains a reference to a table B then table B is the parent of the relationship. Based on my simple use example, this is often not the case; indeed, it's often the other way around.
The net result so far is that I'm unable to use the built-in dependency mechanism to do automatic propagation of destruction and will have to write custom code instead. This sucks. My hope is either that I'm missing something in the documentation or that Rails is fixed to deal with this common use case.
Now that I've got all the entity-relationships implemented, it's time to write some application logic.
Part 5 of this series is here.
Let me preface this by saying that I've only coded a "Hello World" RoR app...but I have done some reading.
It sounds like you have your belongs_to and has_one reversed. I have read examples that lead me to believe the has_one goes with the parent. As in (and pardon the formatting since it appears near impossible to add whitespace):
class Order < ActiveRecord::Base
has_one :invoice
end
class Invoice < ActiveRecord::Base
belongs_to :order
end
However, the table structure is not as I'd do it:
orders
id
name
invoices
id
order_id
I would have put an invoice_id within orders. And as I've read, you can do it this way, but the save behavior is slightly different.
As its complex, I'd rather have you research the save behavior. I'm getting this from the book, "Agile Web Development with Rails" by Dave Thomas and David Heinemeier Hansson. I would consider this book a must-have for what you are trying to achieve.
Hope this helps.
Bill
Posted by: Bill Eisenhauer | Nov 26, 2005 at 07:46 AM
Hi Bill,
I've using the book you recommend for the last few days, which is one of the reasons for my concern.
As far as I can tell, the belongs_to must go with the class whose table contains the foreign key. So the design approach that you would take is not supported by Rails. Ditto for the table design that I would use in my own example.
Let me know if you find out anything to the contrary.
Cheers,
Graham
Posted by: Graham Glass | Nov 26, 2005 at 12:06 PM
Rails would very much prefer you to model things as "school has_one address" and "address belongs_to school", which means moving the keys around so that the address table has a school_id column. That gets you an auto-save when you assign an address to a school, and cascaded deletes and updates will the way you want. Hibernate is a lot more flexible with this sort of thing.
Posted by: Christopher St. John | Nov 27, 2005 at 01:01 AM
But "school has_one address" and "address belongs_to school" is, in any case, the right way around to do this; you shouldn't jump through hoops to get Rails to cope with your weird associations, just do it the more natural way.
Get rid of address_id in the schools table, put a school_id column in the addresses table, switch the associations around, and be happy when everything just works.
Posted by: Tom | Nov 27, 2005 at 07:31 AM
Hi Tom,
I also need a Student to have an address, so your solution does not work. Besides, it is poor database design to have a low-level concept such as address have a reference back to a higher-level concept such as school. I should not have to warp my database design to fit Rail's current limitations. I've solved the problem in my code by adding a few lines of custom code into my School model that destroy the Address automatically using the before_destroy hook, but hopefully Rails will be improved in the future to make this unnecessary.
Regards,
Graham
Posted by: Graham Glass | Nov 27, 2005 at 12:12 PM
Hi Graham,
17 hours? Now that's a bender of mythical proportions.
I think the underlying issue is that table-with-fk-is-child is a RDBMS convention as well as a Rails convention, and ActiveRecord is more of an ORM tool and less of a general purpose DAO layer.
As we know, cascade (in rails, :dependent) is provided to manage parent/child constraints, but if you want the reverse, you normally need to use triggers (in rails, callbacks). You could use a cascade set null fk constraint (if schools are allowed to have no address) combined with a trigger to delete the referenced address when the school is deleted. I guess the rails analogue would be the use of callbacks. These are both workarounds in a sense that they don't allow you to represent your constraint 'out of the box'.
So to play devil's advocate, the constraint isn't natural to represent in the underlying relational data store (I think--I'm not a database guy). That being said, there's no reason why ActiveRecord must strictly mimic RDBMS semantics--functionality could (and probably in your opinion, should) be added to handle this type of constraint automagically.
No help here,
Ed
Posted by: Edward Frederick | Nov 27, 2005 at 02:01 PM
Hi Ed,
Actually, table-with-fk-is-child is *not* an RDBMS convention; see the IBM article at http://www-128.ibm.com/developerworks/web/library/wa-dbdsgn1.html and check out the section called "Complex Datatypes" which describes my use case.
I'm going to post some ideas tomorrow on how to improve and simplify ActiveRecord. Let me know what you think!
Regards,
Graham
Posted by: Graham Glass | Nov 27, 2005 at 02:27 PM
I don't know if this will help, but have you tried has_many? I was using has_one and in my limited grasp of all of this resorted to using has_many. This method sort of makes sense in that if you will be using the address table for student addresses as well, then an address definitely needs has_many students. Just my $.02
Posted by: Shane | Jan 05, 2006 at 11:37 PM
Well in English, the phrase "school belongs to address" does not sound like what you want anyway ("belongs_to" would seem to clearly denote ownership. AR correctly deletes anything you declare *owned* by a parent object when the parent is deleted).
I wonder what happens if you do "has_one" on either side?
Posted by: ben | Sep 25, 2006 at 06:06 PM
This is the exact problem I ran into last night. It took me forever to figure out why it wasn't working and when I finally got it to work (how you did) it no longer made sense to me.
There needs to be some better support for these one-to-one type relationships.
Posted by: Dan | Oct 03, 2006 at 03:55 AM