Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoR: has_one "or the other"? (Or, polymorphism without the inheritance.)

Hey all, I have something of an interesting requirement for my project. I need a has_one relationship where it is either one class or the other, but without inheritance. I could get away with inheritance if it is the only way, but the two associate records have completely different data and aren't related at all.

What I need to figure out is something like the following.

# 1. Foo never belongs to anything.
# 2. Foo MUST have one assigned sub-record for validity.
# 3. Foo can only have either Bar or Baz assigned.
# 4. Bar and Baz have only ONE common property, and aren't
#    related in either data or implementation.

class Foo < ActiveRecord::Base
  # Attributes: id, name, value
  has_one :assignment, :foreign_key => 'assigned_to', :readonly => true
          # Could really use an :object_type for has_one here...
end

class Bar < ActiveRecord::Base
  # Attributes: name,...
end

class Baz < ActiveRecord::Base
  # Attributes: name,...
end

Where Foo has one assignment, of type either Bar or Baz; they only share one common column, so perhaps I can make a parent object from that. However, if I make them inherit from a common object (when the data they contain really is oranges and apples) must I make a table for the record? Can I perhaps get away with it if the record is an abstract record, but the children aren't?

I suppose by now you can see my difficulty. I'm rather new to RoR but loving it so far. I'm sure there's a way around this, but I'll be darned if I can't figure out what it is.

like image 841
Robert K Avatar asked Dec 17 '08 16:12

Robert K


1 Answers

You're trying to model something that doesn't fit the relational database paradigm. All references in SQL have one origin and one target.

FWIW, Polymorphic Associations is also an anti-pattern because it breaks this rule. It should be a clue that it's a broken design when the documentation says you must forgo a referential integrity constraint to make it work!

You need Foo to have two has_one relationships: one to Bar and one to Baz. Then implement some class logic to try to ensure only one reference is populated in any instance of Foo. That is, of the references to Bar and Baz, one must have a value and the other must be nil, but this is something for your code to check for and enforce.

like image 69
Bill Karwin Avatar answered Nov 04 '22 11:11

Bill Karwin