I searched the website deeply to find a clean and easy way to handle attribute initialization in the has_many :through relation join model, but I did not find a better solution for my need.
In the example below, I should automatically set the role attribute of the Training connection model when creating or updating the Course object.
This is my model:
QUALIFICATIONS = ["Theoretical Instructor", "Practical Instructor"] class Course < ActiveRecord::Base has_many :trainings, dependent: :destroy has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Theoretical Instructor" } accepts_nested_attributes_for :theoretical_instructors has_many :practical_instructors, through: :trainings, source: :trainer, conditions: { "trainings.role" => "Practical Instructor" } accepts_nested_attributes_for :practical_instructors end class Trainer < ActiveRecord::Base has_many :trainings, dependent: :destroy has_many :courses, through: :trainings end class Training < ActiveRecord::Base belongs_to :trainer belongs_to :course
The rationale for this model is that I want to store Training objects in one table. I do not want to create models for joining TheoreticalInstructor and PracticalInstructor (potentially explosive numbers of tables) to solve this problem.
This view provides a form for submitting a new Course :
<%= form_for @course do |course_form| %> <%- # fields for course attributes, as usual... %> <%= course_form.label :theoretical_instructor_ids %><br /> <%= course_form.select :theoretical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %> <%= course_form.label :practical_instructor_ids %><br /> <%= course_form.select :practical_instructor_ids, Trainer.all.map { |x| [[x.name, x.surname].join(" "), x.id] }, { }, { multiple: true } %> <%= course_form.submit %> <% end%>
Question: what can I do to make @course = Course.new(params[:course]) only line of code in the Course controller that is necessary to maintain this association when submitting the previous form?
Unlike this question, I do not want to create new Trainer objects when creating a new Course : I want to select them from existing ones in the database (via the input field in multimode mode).
I need something like @course.theoretical_instructor_ids = [1, 2] create two Training objects with the role attribute set to Theoretical Instructor
I am thinking of the after_initialize on Training , which sets the role based on the relation name ( :theoretical_instructors and :practical_instructors ), but I really don't know how to do this. Any advice? Am I missing a point?
Thanks guys!
EDIT 1 of oli-g
This question deals with a similar problem: the difference is that I do not want to create Trainer objects when creating a new Course , but I just want to associate existing Trainer objects with the new Course .
EDIT 2 of oli-g
Based on this (5 years post) and this on blogs, I modified the Course model as follows:
class Course < ActiveRecord::Base has_many :trainings, dependent: :destroy has_many :theoretical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Theoretical Instructor"] do def <<(theoretical_instructor) Training.send(:with_scope, create: { role: "Theoretical Instructor" }) { self.concat theoretical_instructor } end end accepts_nested_attributes_for :theoretical_instructors has_many :practical_instructors, through: :trainings, source: :trainer, conditions: ["trainings.role = ?", "Practical Instructor"] do def <<(practical_instructor) Training.send(:with_scope, create: { role: "Practical Instructor" }) { self.concat practical_instructor } end end accepts_nested_attributes_for :practical_instructors end
This code allows me to do something like this
:001 > c = Course.first => #<Course id: 1> :002 > t1 = Trainer.first => #<Trainer id: 1, name: "Tom"> :003 > c.theoretical_instructors << t1 => #<Trainer id: 1, name: "Tom"> :004 > Training.all => [#<Training id: 1, role: "Theoretical Instructor", trainer_id: 1, course_id: 1>]
This is an acceptable workaround, even if in my controller I still canβt do only @course = Course.new(params[:course]) , but I need to create Training objects, iterate over params[:course][:theoretical_instructor_ids] and params[:course][:practical_instructor_ids] .
But I'm curious, so the question remains open: what can I do so that @course = Course.new(params[:course]) can create Training objects along with Course ?
Now ... I guess I found an error in Rails:
:005 > c.practical_instructors => []
I think I'll talk about this in github questions ...
EDIT 3 oli-g
Github error reported