Join table for has_many through in Rails
In Rails there are two ways to do many-to-many relationships:
has_and_belongs_to_many
sets up a many to many relationship without an intervening model.
class Category < ActiveRecord::Base has_and_belongs_to_many :productsendclass Product < ActiveRecord::Base has_and_belongs_to_many :categoriesend
This is a good choice if you know that you will not need to store any additional data about the relationship or add any additional functionality - which in practice is actually really rare. It uses less memory since it does not have to instantiate an extra model just to do product.category
.
When using has_and_belongs_to_many
the convention is that the join table is named after the two entities in plural.
Category + Product = products_categories
The order does not seem to matter.
has_many through:
as you already have guessed uses an intermediate model.
class CategoryProduct < ActiveRecord::Base belongs_to :product belongs_to :categoryendclass Category < ActiveRecord::Base has_many :category_products has_many :products, through: :category_productsendclass Product < ActiveRecord::Base has_many :category_products has_many :categories, through: :category_productsend
The advantage here is that you can store and retrieve additional data in the join table which describes the relationship. For example if you wanted to store who added a product to a category - or when the relationship was created.
In order to for Rails to be able to correctly find the ProductCategory class the has_many though
the naming convention is:
model 1(singular) + model 2(plural) Product + Category = category_products
This is due to the way that rails infers the model class based on the table name. Using categories_products
would case rails to look for Category::CategoriesProduct
as plural words are interpreted as modules. However this is really just a lazy naming convention and there is often a noun which better descibes the relation between A and B (such as Categorization
).
Many to Many in forms and controllers.
As IvanSelivanov already mentioned SimpleForm has helper methods for creating selects, checkboxes etc.
But instead of overriding the .to_s
method in your model you may want to use the label_method option instead.
f.assocation :categories, as: :checkboxes, label_method: :name
Overriding .to_s
can make debugging harder and in some cases give confusing test error messages.
To whitelist the params in your controller you would do:
class ProductsController < ApplicationController def create @product = Product.new(product_params) if @product.save redirect_to @product else render :new end end def product_params params.require(:product) .permit(:name, :categories_ids, ...) endend
You also have to add CategoryProduct to each model:
class Product < ActiveRecord::Base has_many :category_products has_many :categories, through: :category_product
It is very simple using gem simple form
. All you have to do is to add:
t.association :categories
in a form for product and add :category_ids => []
to a list of permitted parameters in your products controller
If you prefer checkboxes instead of multi-select list, you can do
t.association :categories, as: check_boxes
And the last thing, to display categories in human-readable format, you need to define a to_s
method in your category model, i. e.:
class Category < ActiveRecord::Base ... def to_s name end end