views:

530

answers:

2

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.

A: 

Perhaps one way to do this, is to create to has-one associations in Foo, for Bar and Baz. Then create a method called assignment and assignment= which can be the sole way to access Bar and Baz. You can check which of the two has_ones is not nil in the get method and return that one. In the assignment method, you can-check what is the type of the variable passed in and set the correct has-one relationship to that object and set the other to nil. That ought to cover all your bases without being too complicated.

AJ
+4  A: 

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.

Bill Karwin
Good point, however sometimes we aren't given full freedom in how databases are designed or how the data is related. Especially when working with a product or library produced by someone else. The two has_one relationships will work well; thank you for your insight.
The Wicked Flea