1

I'm having trouble deciding how to model the following with rails associations.

The UML would look something like below:

----------------
|   CRITERIA   |
----------------
       |
       |*
----------------
|   CONTROLS   | <___
----------------     \
      ^               \
      |                \
-------------------    -------------------
|   SCALE CONTROL |    |  TEXT CONTROL   |      .....
-------------------    -------------------

-The various controls have quite different attributes so STI seems like a poor choice.
-A user can specify any number of controls per criteria.

I'd like to do something like the following:

Criteria
has_many :controls

ScaleControl  
belongs_to :criteria, as: control

TextControl
belongs_to :criteria, as: control

And be able to query along the lines of:

criteria.controls  
# displays all controls (text, scale, etc.) 

criteria.controls.each { ... }

What I've looked at so far:
-RailsCasts' episodes on polymorphic associations and seems like this isn't a good use case.
-Dozens of rails associations posts on here but have failed to find anything directly relevant.
-Rails docs.

Are there any common patterns for implementing something like the above in Rails?

jtgi
  • 138
  • 1
  • 7

1 Answers1

1

Your polymorphic setup is good, but this is one place where Rails cannot help you. You have two options. Write the methods yourself:

class Criteria

  def controls
    scale_controls + text_controls
  end

  def scale_controls
    ScaleControl.where(criteria_id: id)
  end

  def text_controls
    TextControl.where(criteria_id: id)
  end
end

Or you can implement a reverse polymorphic join table. It sounds scary, but it isn't too bad.

class CriteriaControl < ActiveRecord::Base
  belongs_to :criteria
  belongs_to :control, polymorphic: true # so it must include both a control_id and control_type in its table schema
end

Now both of the controls has_one :criteria_control and has_one :criteria, :through => :criteria_control.

Criteria then has_many :criteria_controls and you can define the controls method as such:

def controls
  criteria_controls.map(&:control)
end

But the real question is "why can't Rails write that?"

When Rails build associations, it is just abstractions over the underlying SQL. Here, Rails cannot gather all of the controls because it doesn't know which tables to look in. It first executes SELECT * FROM criteria_controls WHERE criteria_id = 231231. Then it can use the control_type and control_id to find the individual controls in their respective tables.

Chris
  • 11,819
  • 19
  • 91
  • 145