Join table for has_many through in Rails Join table for has_many through in Rails ruby-on-rails ruby-on-rails

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