Rails: Many to many polymorphic relationships

2019-03-07 19:46发布

See comments for updates.

I've been struggling to get a clear and straight-forward answer on this one, I'm hoping this time I'll get it! :D I definitely have a lot to learn still with Rails, however I do understand the problem I'm facing and would really appreciate additional help.

  • I have a model called "Task".
  • I have an abstract model called "Target".
  • I would like to relate multiple instances of subclasses of Target to Task.
  • I am not using single table inheritance.
  • I would like to query the polymorphic relationship to return a mixed result set of subclasses of Target.
  • I would like to query individual instances of subclasses of Target to obtain tasks that they are in a relationship with.

So, I figure a polymorphic many to many relationship between Tasks and subclasses of Targets is in order. In more detail, I will be able to do things like this in the console (and of course elsewhere):

task = Task.find(1)
task.targets
[...array of all the subclasses of Target here...]

But! Assuming models "Store", "Software", "Office", "Vehicle", which are all subclasses of "Target" exist, it would be nice to also traverse the relationship in the other direction:

store = Store.find(1)
store.tasks
[...array of all the Tasks this Store is related to...]
software = Software.find(18)
software.tasks
[...array of all the Tasks this Software is related to...]

The database tables implied by polymorphic relationships appears to be capable of doing this traversal, but I see some recurring themes in trying to find an answer which to me defeat the spirit of polymorphic relationships:

  • Using my example still, people appear to want to define Store, Software, Office, Vehicle in Task, which we can tell right away isn't a polymorphic relationship as it only returns one type of model.
  • Similar to the last point, people still want to define Store, Software, Office and Vehicle in Task in one way shape or form. The important bit here is that the relationship is blind to the subclassing. My polymorphs will initially only be interacted with as Targets, not as their individual subclass types. Defining each subclass in Task again starts to eat away at the purpose of the polymorphic relationship.
  • I see that a model for the join table might be in order, that seems somewhat correct to me except that it adds some complexity I assumed Rails would be willing to do away with. I plea inexperience on this one.

It seems to be a small hole in either rails functionality or the collective community knowledge. So hopefully stackoverflow can chronicle my search for the answer!

Thanks to everyone who help!

7条回答
欢心
2楼-- · 2019-03-07 20:13

This may not be an especially helpful answer, but stated simply, I don't think there is an easy or automagic way to do this. At least, not as easy as with simpler to-one or to-many associations.

I think that creating an ActiveRecord model for the join table is the right way to approach the problem. A normal has_and_belongs_to_many relationship assumes a join between two specified tables, whereas in your case it sounds like you want to join between tasks and any one of stores, softwares, offices, or vehicles (by the way, is there a reason not to use STI here? It seems like it would help reduce complexity by limiting the number of tables you have). So in your case, the join table would also need to know the name of the Target subclass involved. Something like

create_table :targets_tasks do |t|
  t.integer :target_id
  t.string :target_type
  t.integer :task_id
end

Then, in your Task class, your Target subclasses, and the TargetsTask class, you could set up has_many associations using the :through keyword as documented on the ActiveRecord::Associations::ClassMethods rdoc pages.

But still, that only gets you part of the way, because :through won't know to use the target_type field as the Target subclass name. For that, you might be able to write some custom select/finder SQL fragments, also documented in ActiveRecord::Associations::ClassMethods.

Hopefully this gets you moving in the right direction. If you find a complete solution, I'd love to see it!

查看更多
登录 后发表回答