ruby-on-railsactiverecordmodelassociationspolymorphic-associations

Rails should relation be polymorphic?


I have 3 tables Injury, Sport, Exercise. I'm trying to wrap my head around if I should create a relation between them. I want to be able to list Injuries in the Sports and Exercises tables; this might expand to more tables in the future, so I'm thinking using a Polymorphic relation. However, that would create a multiple Injuries for both tables, i.e. Injurable_Type would be Exercises or Sports each with there own Injurable_id; if my understanding is correct.

Example:

Injury_id : 1
name = acl tear
injurable_id = 1
type = Sport

Injury_id: 2
name = acl tear
id = 1
type = Exercise

Ideally(though this might be impossible)

Injury_id : 1
name = acl tear
injurable_id = 1 
injurable_type = Sport and Exercise

I could keep them separate and store Injury_ids into columns on both Exercises and Sports tables but that will lead to querying issues/headaches down the line I believe. Further more, my understanding of polymorphic associations is to get rid of the Injury_id column.

How should I approach this problem? Leave the tables separate and deal with the possibility of multiple queries down the road or make them polymorphic or something else?

My thought process is this:

Sport / Exercise table would have access to all injuries in Injury table

What I have though of so far:

Injury
belongs_to :injurable, polymorphic: true

Sport
has_many :injuries, as: :injurable

Exercise
has_many :injuries, as: :injurable

Or just leave them separate
Injuries_id: 1, 2 ,3 etc.

Sports table has column
injuries_id: 1, 2

Exercises table has column
injuries_id: 1, 3

Then query them (polymorphic) like:

Sports controller:

sport = Sport.includes(:injuries).find(1)

Exercises controller

exercise = Exercise.includes(injuries).find(1)

Then query them (Separate way) like:

Sports controller:
    sport = Sports.find(1)
    injuries = sport.pluck(:injuries_id)
    injury_name = Injuries.find(injuries).pluck(:name)

Exercises controller:
    exercise = Exercises.find(1)
    injuries = exercise.pluck(:injuries_id)
    injury_name = Injuries.find(injuries).pluck(:name)

Something like that.


Solution

  • My concern with the polymorphic approach is that you won't be able to share the same injury "definition" across different activities. You'll have to duplicate the same injury to attach it to other sports and exercises; this may cause more troubles down the line(and goes against database normalization rules). For example, answering a question such as "how many total ACL tear injuries across all activities?" would not be quickly done(you'll end up matching by text -- that's not ideal!). Also, what if you decided to change the name of the ACL tear injury? You'll end up updating multiple rows in the Injury table.

    I would suggest a third approach(similar to your 2nd approach): Injuries should not be directly linked to sports or exercises. The Injury model should do one thDefinehould do it well: Defining what an injury is. This will serve you better as you expand your data schema and add new use cases. Now, associating a sport or exercise activity can be done with a bridge model, i.e.:

    class SportInjury
      belongs_to :injury
      belongs_to :sport
    end
    
    class ExerciseInjury
      belongs_to :injury
      belongs_to :exercise
    

    Then, you're able to associate a Sport model to an Injury model as the following:

    class Sport
      has_many :injuries, through: :sport_injuries
    end
    

    Ruby on Rails should be smart enough to produce optimized SQL when accessing a sport's list of injuries when you call sport.injuries.

    This approach has a couple of benefits: