在 Rails 中加入 table has_many

Join table for has_many through in Rails

我是编程新手 & rails,有些事情我不完全理解。 我正在使用

创建一个应用程序
product has_many categories
category has_many products

如果我理解正确,我需要创建一个连接 table products_categories,它有一个 product_id 和一个 category_id。首先,我还需要这个 table 的模型吗?如果是,我想它看起来像这样:

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

和 product.rb 中的其他模型:

 class Product < ActiveRecord::Base
  has_many :category_products
  has_many :categories, through: :category_product
  has_attached_file :picture,
    styles: { medium: "300x300>", thumb: "100x100>" }

  validates_attachment_content_type :picture,
    content_type: /\Aimage\/.*\z/
  validates :price,               presence: { message: "Merci d'indiquer le prix du produit" }
  validates :name,                presence: { message: "Merci d'indiquer le nom du produit" }
  validates :weight,              presence: { message: "Merci d'indiquer le poids du produit" }
  validates :description,         presence: { message: "Merci d'écrire une description du produit " }
end

并在 category.rb

    class Category < ActiveRecord::Base
  has_many :category_products
  has_many :products,         through: :category_product
  validates :name,                presence: { message: "Merci d'indiquer le nom de la catégorie" }
end

现在假设我想创建一个产品,在创建它的同时,从类别列表中为该产品指定尽可能多的类别。

到目前为止,这是我的 Product/new.html.slim 观点:

  div class="container marged-top"
    div class= "col-xs-12 col-md-offset-3 col-md-5 bigmarge"
      div class="panel panel-default"
        div class= "panel-heading"
          h4 Création Produit
        div class= "panel-body"
          =simple_form_for @product, html: { multipart: true } do |t|
            = t.error_notification
            = t.input :name, label: 'Nom'
            = t.input :description, label: 'Description', required: true
            = t.input :price, label: 'Prix', required: true
            = t.input :weight, label: 'Poids', required: true
            = t.label :picture
            = t.file_field :picture
            = t.association :categories, as: :check_boxes
            = t.button :submit, value: "Valider",  class: "btn-success marge-bas"

这是我的 Product 实例的一个简单表单。我想我现在需要一个 CategoryProduct 的表格吗? 如果我希望用户在创建产品时能够向产品添加他想要的任意多个类别,我该如何更改它?

这是我的 category_product table 的迁移文件:

class CreateTableCategoriesProducts < ActiveRecord::Migration

  def change
    create_table :categories_products do |t|
      t.references :product, index: true
      t.references :category, index: true
    end
    add_foreign_key :categories_products, :categories
    add_foreign_key :categories_products, :products
  end
end

我用以下迁移文件重命名了之前的 table :

class RenameTableCategoriesProducts < ActiveRecord::Migration
  def self.up
    rename_table :categories_products, :category_products
  end

 def self.down
    rename_table :category_products, :categories_products
 end
end

我在 product/new.html.slim 中的 simple_form 上收到以下错误:

undefined method `klass' for nil:NilClass

代码在这里中断:

 = t.association :categories, as: :check_boxes

所以我想我的联想还是不太正确

您还必须为每个模型添加 CategoryProduct:

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

使用gemsimple form非常简单。您所要做的就是添加:

t.association :categories

在产品表单中,并将 :category_ids => [] 添加到产品控制器中允许的参数列表中

如果您更喜欢复选框而不是多select列表,您可以

    t.association :categories, as: check_boxes

最后一件事,要以人类可读的格式显示类别,您需要在类别模型中定义一个 to_s 方法,即。即:

class Category < ActiveRecord::Base
  ...
  def to_s
    name
  end 
end

在Rails中有两种方式建立多对多关系:

has_and_belongs_to_many

在没有干预模型的情况下建立多对多关系。

class Category < ActiveRecord::Base
  has_and_belongs_to_many :products
end

class Product < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

如果您知道您不需要存储任何关于关系的附加数据或添加任何附加功能,这是一个不错的选择——这在实践中实际上很少见。它使用更少的内存,因为它不必为了 product.category.

而实例化额外的模型

使用 has_and_belongs_to_many 时,惯例是连接 table 以两个实体的复数形式命名。

Category + Product = products_categories

顺序似乎并不重要。

has_many 通过:

正如您已经猜到的那样,它使用了中间模型。

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

这里的优点是您可以在描述关系的连接 table 中存储和检索附加数据。例如,如果你想存储谁将产品添加到类别 - 或者何时创建关系。

为了Rails能够正确找到ProductCategory class has_many though命名约定为:

model 1(singular) + model 2(plural) 
Product + Category = category_products

这是由于 rails 根据 table 名称推断模型 class 的方式所致。使用 categories_products 将大小写 rails 查找 Category::CategoriesProduct,因为复数词被解释为模块。然而,这实际上只是一个懒惰的命名约定,通常有一个名词可以更好地描述 A 和 B 之间的关系(例如 Categorization)。

表单和控制器中的多对多。

正如 IvanSelivanov 已经提到的,SimpleForm 具有用于创建选择、复选框等的辅助方法

但是您可能希望改用 label_method 选项,而不是覆盖模型中的 .to_s 方法。

f.assocation :categories, as: :checkboxes, label_method: :name

覆盖 .to_s 会使调试更加困难,并且在某些情况下会给出令人困惑的测试错误消息。

要将控制器中的参数列入白名单,您可以这样做:

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