Join table for has_many through in Rails

丶灬走出姿态 提交于 2019-12-04 04:33:01

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 :products
end

class Product < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

This is nice if you know that it is a simple direct relationship and you know that you will not need to store any additional data about the relationship. 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. In alfabetical order:

Category + Product = products_categories

has_many through

as you already have guessed uses an intermediate model.

class CategoryProduct < ActiveRecord::Base
  belongs_to :product
  belongs_to :category
end

class Category < ActiveRecord::Base
  has_many :category_products
  has_many :products, through: :category_products
end

class Product < ActiveRecord::Base
  has_many :category_products
  has_many :categories, through: :category_products
end

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 join table for a has_many though 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.

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, ...)
  end
end

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!