Has_many通过Rails的联接表

问题描述 投票:8回答:2

我是编程和Rails的新手,有些事情我还不太了解。我正在使用

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

如果我理解正确,我需要创建一个具有product_idcategory_id的联接表products_categories。首先,我还需要该表的模型吗?如果是的话,我想它将看起来像这样:

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表的迁移文件:

类CreateTableCategoriesProducts

  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

我使用以下迁移文件重命名了上一个表:

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

所以我想我的协会至今还算不错

ruby-on-rails forms model jointable
2个回答
12
投票

在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时的惯例是,联接表以两个实体复数命名。按制造顺序:

Category + Product = products_categories

has_many through

您已经猜到使用中间模型。

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

这里的优点是,您可以在描述关系的联接表中存储和检索其他数据。例如,如果您想存储将产品添加到类别的人员-或创建关系的时间。

为了使Rails能够正确找到ProductCategory类,has_many though命名约定的连接表是

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

这是由于rails根据表名推断模型类的方式。使用categories_products将使rails查找Category::CategoriesProduct

表格和控制器中的许多对许多。

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

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

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

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

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

t.association :categories

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

如果您希望使用复选框而不是多选列表,则可以

    t.association :categories, as: check_boxes

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

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

2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.