Hello, I'm building a contact management system for a small school and I'm having trouble coming up with the right data-model that works well with Rails. Here's the mysql contacts id first_name last_name ... donors id contact_id status ... faculty id contact_id hire_date directors id contact_id The contact table contains basic stuff that all contacts will have, and subsequent models extend Contact to add additional data. Some of the sub models are Donor, Faculty, Director. A single Contact can be a Donor, Faculty, and a Director, so it's what I think is called multi-table inheritance which isn't really supported in Rails. Some of the workarounds I've seen seemed overly complex. I was planning to do something like this: class Donor < ActiveRecord::Base has_one :contact end for all the sub models and I think that would work, but I can't use the syntax Donor.find(1, :include => :contact) because this requires a donor_id column in contacts, and I doesn't seem to be good to have donor_id, faculty_id, director_id, foreign key columns in contacts (maybe this isn't a problem??) Also, assuming the above, when I create one of the sub models I can't get the contact_id to save to donor automatically. @contact = Contact.new(params[:contact]) @donor = Donor.new(params[:donor]) Donor.transaction do @contact.save @donor.contact_id = @contact.id if @donor.save flash[:notice] = 'Donor was successfully created.' redirect_to :action => 'list' else render :action => 'new' end end I can get the schema above to work, but I think I'll have to do a lot of custom sql to make it work rather than using the built-in :include. Unless there's a way to "reverse" the underlying join of the has_one relationship so a contact_id in donors joins to contacts? Thanks for the help! Mark
on 2007-01-15 16:30
on 2007-01-15 16:32
Just reverse the relationship, as you suggested: class Donor < ActiveRecord::Base belongs_to :contact end class Contact < ActiveRecord::Base has_one :donor has_one :faculty has_one :director end Rails will then use the contact_id field in Donor, Faculty etc. and you won't need the donor_id and other id fields in Contact.
on 2007-01-15 17:11
Hello again, I've been thinking about this some more and I think the easiest thing to do is to put foreign key columns in contacts for each of the sub-models. While this would require a schema change in contacts for every "new" sub-model, that's okay for this project. By doing this, I'm not polluting the elegant natural language of rails. The Donor can :has_one Contact, and I can do a Donor.find(1, :include => :contact). The following code worked great: >> c = Contact.new >> d = Donor.new >> d.contact = c >> d.save >> c.donor_id => 1 >> Donor.find(1, :include => :contact) Regardless, I'm still looking forward to other ideas.
on 2007-01-15 17:14
You're completely correct, I learned that this would work after my first post, but I was conflicted in saying that a Donor belongs to a Contact, because it didn't really describe the relationship. Anyway, thanks!
on 2007-01-15 17:50
You really shouldn't get caught up with the descriptions "has_one" and "belongs_to". In rails they mean little more than "I don't have the foreign key" and "I do have the foreign key". There's no reason why the item that "belongs to" another item needs to have the foreign key. And rails will work quite happily either way. All the code you used in your example will work identically if you reverse the relationship (except for c.donor_id, obviously). In particular, the ":include => contact" will still work with the relationship reversed. I've used this "reverse relationship" in a couple of places in my current project because it simply makes more sense that way. And rails hasn't complained once.
on 2007-01-15 17:57
mveerman wrote: > You're completely correct, I learned that this would work after my > first post, but I was conflicted in saying that a Donor belongs to a > Contact, because it didn't really describe the relationship. Anyway, > thanks! You could also look into either using Single Table Inheritence (STI) as the fan out of the subclasses isn't too high, or use a polymorphic association.