I have searched extensively for a solution to my situation, but I can't find anything.
In my application I have a Person
model that only stores data about people:
class Person < ApplicationRecord
end
Then I have a Trial
model. Trials can have many people using a has-many-through association. Additionally, in the context of a Trial, a person can be a Defendant or a Plaintiff. To achieve this, I set up my models like this:
class Trial < ApplicationRecord
has_many :trial_people
has_many :plaintiffs, class_name: 'Plaintiff', through: :trial_people, source: :person
has_many :defendants, class_name: 'Defendant', through: :trial_people, source: :person
end
class TrialPerson < ApplicationRecord
belongs_to :trial
belongs_to :person
end
class Plaintiff < Person
end
class Defendant < Person
end
I am then using Select2 JQuery plugin to add in the defendants and plaintiffs for each trial in the view. Obtaining the IDs in strong parameters:
params.require(:trial).permit(:title, :description, :start_date, :plaintiff_ids => [], :defendant_ids => [])
So that I can do achieve like the following:
trial.defendants
trial.plaintiffs
The problem is that I do not have any way of distinguishing between those classes inside the trial_people
table. I was thinking on adding a type
column to that table (STI), but I do not know how to automatically add that type to each defendant or plaintiff when saving the Trial object.
Would appreciate some insight on how to achieve this, using STI or not.
One way you can go about this without changing your associations or schema is to use a before_create
callback.
Assuming you've added a person_type
string column to trial_people
class TrialPerson < ApplicationRecord
belongs_to :trial
belongs_to :person
before_create :set_person_type
private
def set_person_type
self.person_type = person.type
end
end
Another way to approach it is to remove the person
association and replace it with a polymorphic triable
association. This achieves the same end result but it's built into the ActiveRecord API and so doesn't require any callbacks or extra custom logic.
# migration
class AddTriableReferenceToTrialPeople < ActiveRecord::Migration
def up
remove_reference :trial_people, :person, index: true
add_reference :trial_people, :triable, polymorphic: true
end
def down
add_reference :trial_people, :person, index: true
remove_reference :trial_people, :triable, polymorphic: true
end
end
# models
class TrialPerson < ApplicationRecord
belongs_to :trial
belongs_to :triable, polymorphic: true
end
class Person < ApplicationRecord
abstract_class = true
has_many :trial_people, as: :triable
end
class Trial < ApplicationRecord
has_many :trial_people
has_many :defendants, source: :triable, source_type: 'Defendant', through: :trial_people
has_many :plaintiffs, source: :triable, source_type: 'Plaintiff', through: :trial_people
end
class Plaintiff < Person
end
class Defendant < Person
end
This gives you triable_type
and triable_id
columns on your trial_people
table which are set automatically when you add to the collections
trial = Trial.create
trial.defendants << Defendant.first
trial.trial_people.first # => #<TrialPerson id: 1, trial_id: 1, triable_type: "Defendant", triable_id: 1, ... >
Note that Person needs to be an abstract class as otherwise the triable_type
will be set as "Person" for all Person objects including the Plaintiff and Defendant unless overwritten manually.