The GitHub Repo The Heroku App
Setup
The setup for this episode because it had a has_many :through association. This is useful to find a record from an instance of the first model via a third model. The way this association was done in this episode was by creating three models, product, category, and categorization. These are the relevant lines of code:
class Product < ActiveRecord::Base
has_many :categorizations
has_many :categories, through: :categorization
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, through: categorization
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
Just like when you have a has_many
and belongs_to
relationship, you must define the foreign key within the table that has the belongs_to
method. In this case it is the Categorization model. The schema would look like this.
create_table "categorizations", force: true do |t|
t.integer 'category_id'
t.integer 'product_id'
t.datetime 'created_at'
t.datetime 'updated_at'
end
This setup allows us to call @product.categories
. @product.categories
returns an array of category objects associated through the categorization table. This is similar to how you would create followers and followed_users for a user.
Form
The main part of the episode was about how you can create checkboxes to select the categories. First obvious things to do are to loop through all the categories and display them in the _form
partial. The tricky part comes with choosing a check_box
or a check_box_tag
. You want to choose a check_box_tag
so you can display each category, not just one.
Next you want to fill in the value of checked or not checked. The second argument passed into the check_box_tag
is the category.id
. The third is the value of checked or not checked. The way you designate the value is by passing in true or false. The way it is done in the episode is by finding the @product.category_ids
and checking if the category.id
from the block is included within the @product
‘s category_ids
.
Now if you refreshed the page, the value is correctly displayed. A hidden_field_tag is also created in order to make sure that the form is submitted with a default value of nil. The reason you want to include this nil
category_id
field is to make sure that if no checkboxes are checked, then an empty array is passed as a parameter.
Ryan adds a usability enhancement by allowing users to click on the text to check and uncheck boxes. He first creates a unique id for each checkbox with dom_id
. Next he adds a label_tag to the text. Oddly enough the dom_id
method has no description within the Ruby on Rails API. It just has the source code. dom_id
seems to be creating a unique string from the object’s model and id values. In the categories example, the id was defined as category_1
and so on.
The pertinent code:
<%= hidden_field_tag "product[category_ids][]", nil %>
<% Category.all.each do |category| %>
<%= check_box_tag "product[category_ids][]", category.id, @product.category_ids.include?(category.id), id: dom_id(category) %>
<%= label_tag dom_id(category), category.name %><br>
<% end %>
Gotcha
One gotcha you have to watch out for when following this episode and creating a Rails 4 app is Strong Parameters. I was able to find a post on CoderWall that almost translated one-to-one to what I needed to do. You have to define the category_id
param as an array. The product_params method definition in ProductsController
:
def product_params
params[:product].permit(:name, :price, category_id: [])
end
It’s category_id and not category_ids?