Checkboxes with has_many :through
Let's assume that you want to store the information about a user, who is enrolled in several courses, and you also want to list all users enrolled in a particular course, then you can use many-to-many relationship in Rails. Here is an article by Aihui about why you should prefer has_many :through association over _has_many_and_belongs_to_many _(HMBM) association.
This is how the has_many :through
implementation may look like:
First, we have the classes User
and Course
(which I generated with two scaffolds):
# rails generate scaffold User name:string
class User < ActiveRecord::Base; end
# rails generate scaffold Course name:string
class Course < ActiveRecord::Base; end
Now we generate a new class Enrollment
to store the user-to-course relationship:
# rails generate migration CreateEnrollments course:belongs_to user:belongs_to
class CreateEnrollments < ActiveRecord::Migration
def change
create_table :enrollments do |t|
t.belongs_to :course, index: true
t.belongs_to :user, index: true
end
add_foreign_key :enrollments, :courses
add_foreign_key :enrollments, :users
end
end
# /app/models/enrollment.rb
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
And we update our User
and Course
classes with:
class User < ActiveRecord::Base
has_many :enrollments, dependent: :destroy
has_many :courses, through: :enrollments
end
class Course < ActiveRecord::Base
has_many :enrollments, dependent: :destroy
has_many :users, through: :enrollments
end
dependent: :destroy
indicates that the existence of the enrollments is dependent on the existence of the user/course. Now let's try to edit a user and enroll him/her in some courses with the help of checkboxes. We want the result to look like this: To achieve that, we first change the _edit _template from user's controller by adding this to _form.html.erb
:
<!-- /app/views/users/_form.html.erb -->
<%= form_for(@user) do |f| %>
// ...
<%= f.label :name %>
<%= f.text_field :name %>
<%= hidden_field_tag "user[course_ids][]", nil %>
<% Course.all.each do |course| %>
<%= check_box_tag 'user[course_ids][]', course.id, @user.courses.include?(course), id: dom_id(course) %>
<%= label_tag dom_id(course), course.name %>
<% end %>
<%= f.submit %>
<% end %>
The hidden_field_tag
allows us to update the user with all courses unchecked. With dom_id(course)
we can (un)check a course by simply clicking on the checkbox's label. To update the user, we have to whitelist course_ids
in our user's controller:
class UsersController < ApplicationController
# ...
def update
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit
end
end
private
def user_params
params.require(:user).permit(:name, {:course_ids=>[]})
end
end
Finally, we update the view to list user's courses:
<!-- /app/views/users/show.html.erb -->
<ul>
<% @user.courses.each do |course| %>
<li><%= course.name %></li>
<% end %>
</ul>
Here is the source code. The implementation for listing all users from a particular course is equivalent.
Sources: this article, this video and this book.