Reputation: 145
I have the following use cases for creating an app that handles courses;
What's the best way to create models with many to many relationships for this app?
Should the app have a teachings
model that belongs to courses, teachers, and locations with a column for the date?
Upvotes: 3
Views: 160
Reputation: 102174
What you want is to create a model for each entity:
You then create a join model of sorts which I have choosen to call Lesson:
class Course < ActiveRecord::Base
has_many :lessons
has_many :locations, through: :lessons
has_many :teachers, through: :lessons
end
class Lesson < ActiveRecord::Base
belongs_to :course
belongs_to :teacher
belongs_to :location
end
class Teacher < ActiveRecord::Base
has_many :lessons
has_many :courses, through: :lessons
end
class Location < ActiveRecord::Base
has_many :lessons
has_many :courses, through: :lessons
has_many :teachers, through: :lessons
end
I've been playing with this structure for the models but what I noticed is that when submitting the course with a fields_for :locations and a fields_for :instructors, the associations table is creating two separate entries for course_id + instructor_id, course_id + location_id, I would expect a single entry for course_id, instructor_id, location_id. Any thoughts as to why that might happen?
ActiveRecords only ever keeps track of one assocation when you create join models implicitly. To do three way joins you need to create the join model explicitly.
<%= form_for(@course) do |f| %>
<div class="field>
<% f.label :name %>
<% f.text_field :name %>
</div>
<fieldset>
<legend>Lesson plan<legend>
<%= f.fields_for(:lessons) do |l| %>
<div class="field>
<% l.label :name %>
<% l.text_field :name %>
</div>
<div class="field">
<% l.label :starts_at %>
<% l.datetime_select :starts_at %>
</div>
<div class="field">
<% l.label :teacher_ids %>
<% l.collection_select :teacher_ids, Teacher.all, :id, :name, multiple: true %>
</div>
<div class="field">
<% l.label :location_id %>
<% l.collection_select :location_id, Location.all, :id, :name %>
</div>
<% end %>
</fieldset>
<% end %>
fields_for
and accepts_nested_attributes
are powerful tools. However passing attributes nested several levels down can be seen as an anti-pattern of sorts since it creates god classes and unexpected complexity.
A better alternative is to use AJAX to send separate requests to create teachers and locations. It gives a better UX, less validation headaches and better application design.
Upvotes: 2
Reputation: 7136
You are on the right track. Here is how I would model these relationships. Let's say you have a Teacher
model, a Course
model and a TeacherCourses
model that will be our join table between teachers and courses:
class Teacher < ActiveRecord::Base
has_many :courses, through: :teacher_courses
end
class Course < ActiveRecord::Base
has_many :teachers, through: :teacher_courses
end
class TeacherCourse < ActiveRecord::Base
belongs_to :course
belongs_to :teacher
end
Your teacher_courses
table would also have location attribute differentiating a record from the same course/teacher combo:
create_table :teacher_courses do |t|
t.integer :teacher_id
t.integer :course_id
t.string :location
t.timestamps
end
Upvotes: 2