【问题标题】:Simple form with has_many through and join table带有 has_many through 和 join 表的简单表格
【发布时间】:2020-08-24 13:50:14
【问题描述】:

我有模型:Product、Parts 和 ProductParts

产品.rb

class Product < ApplicationRecord
  has_many :product_parts
  has_many :parts, through: :product_parts
  accepts_nested_attributes_for :product_parts, allow_destroy: true
end

part.rb

class Part < ApplicationRecord
  has_many :product_parts
  has_many :products, through: :product_parts
end

product_part.rb

class ProductPart < ApplicationRecord
  belongs_to :part
  belongs_to :product
end

产品可能由几个部分组成,例如 product1 = 1pcs part2 和 3pcs part 3。

我有连接表:product_parts

  create_table "product_parts", force: :cascade do |t|
    t.integer "part_id"
    t.integer "product_id"
    t.integer "quantity"
    t.index ["part_id"], name: "index_product_parts_on_part_id"
    t.index ["product_id"], name: "index_product_parts_on_product_id"
  end

我想为新产品呈现简单的表单,其中:

  • 我可以输入产品名称
  • 选择零件(从所有零件中)并定义每个零件的数量

我有这样的 _form.html.slim:

= simple_form_for(@product) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?
  = f.input :name
  = f.simple_fields_for :parts do |part|
    = f.label :part.name
    = f.input :part.quantity
  = f.button :submit

但它不渲染任何东西。你有什么正确的方法吗?

我也尝试过这样做:

_form.html.slim

= simple_form_for(@product) do |f|
  = f.input :name
  = f.simple_fields_for :parts do |part|
    == render 'part_fields'
  = f.button :submit

_part_fields.html.slim

= :name
= part.input :quantity

服务器日志输出为:

17:34:21 web.1       | Started GET "/products/new" for ::1 at 2020-08-24 17:34:21 +0200
17:34:21 web.1       | Processing by ProductsController#new as HTML
17:34:21 web.1       |   Rendering products/new.html.slim within layouts/application
17:34:21 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   CACHE Part Load (0.0ms)  SELECT "parts".* FROM "parts" LIMIT ?  [["LIMIT", 11]]
17:34:21 web.1       |   ↳ app/views/products/_form.html.slim:6
17:34:21 web.1       |   Rendered products/_part_fields.html.slim (Duration: 442.5ms | Allocations: 233999)
17:34:21 web.1       |   Rendered products/_form.html.slim (Duration: 449.1ms | Allocations: 240331)
17:34:21 web.1       |   Rendered products/new.html.slim within layouts/application (Duration: 451.5ms | Allocations: 242361)
17:34:21 web.1       | Completed 500 Internal Server Error in 454ms (ActiveRecord: 0.4ms | Allocations: 243658)
17:34:21 web.1       |
17:34:21 web.1       |
17:34:21 web.1       |
17:34:21 web.1       | ActionView::Template::Error - undefined local variable or method `part' for #<#<Class:0x00007f938cd03f28>:0x00007f938cd020b0>
17:34:21 web.1       | Did you mean?  @parts:
17:34:21 web.1       |   app/views/products/_part_fields.html.slim:2:in `view template'

谢谢大家的回答。我在 GitHub 上找到了示例: rails-recipes。现在我正在使用cocoon gem,我几乎就在我想去的地方:) 但是:

_form.html.slim

= simple_form_for(@product) do |f|
  = f.input :name
  |Parts
  fieldset#parts
    = f.simple_fields_for :product_parts do |product_part|
      == render 'product_part_fields', f: product_part
      = link_to_add_association 'Add part', f, :product_parts, class: 'btn btn-primary btn-xs'
    = f.button :submit

_product_part.html.slim:

.nested_fields.field
  = f.input :quantity
  = f.collection_select :part_id, Part.all, :id, :name
  = link_to_remove_association f

呈现表单时,我已经有两部分输入而不是一个(这应该是正确的)。 我不想创建新零件,只需从列表中选择零件并仅插入数量。 它看起来像这样: screenshot of rendered form

产品未创建,重新加载表单后,我总是有 2 个部分的字段。 这是控制台日志:

12:58:14 web.1       | Started POST "/products" for ::1 at 2020-08-28 12:58:14 +0200
12:58:14 web.1       | Processing by ProductsController#create as HTML
12:58:14 web.1       |   Parameters: {"authenticity_token"=>"dLVq3gUUeHnmFgVxQ7gXATOF3wdQ75a/csjmjzYN6HFrr9duoi50M4KzrSUNj/QHCrJgXO2myJPdpkybdTB0cA==", "product"=>{"name"=>"Product #1", "product_parts_attributes"=>{"0"=>{"quantity"=>"4", "part_id"=>"134", "_destroy"=>"false"}, "1"=>{"quantity"=>"5", "part_id"=>"168", "_destroy"=>"false"}, "2"=>{"quantity"=>"", "part_id"=>"266", "_destroy"=>"false"}, "3"=>{"quantity"=>"", "part_id"=>"260", "_destroy"=>"false"}, "4"=>{"quantity"=>"", "part_id"=>"262", "_destroy"=>"false"}, "5"=>{"quantity"=>"", "part_id"=>"259", "_destroy"=>"false"}}}, "commit"=>"Create Product"}
12:58:14 web.1       |    (0.2ms)  begin transaction
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.2ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 134], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 168], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 266], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 260], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 262], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |   Part Load (0.1ms)  SELECT "parts".* FROM "parts" WHERE "parts"."id" = ? LIMIT ?  [["id", 259], ["LIMIT", 1]]
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'
12:58:14 web.1       |    (0.2ms)  rollback transaction
12:58:14 web.1       |   ↳ app/controllers/products_controller.rb:29:in `block in create'

我做错了什么?:

  • 我有两个部分可供选择,而不是新呈现形式的一个
  • 无法创建产品和 products_parts

【问题讨论】:

  • 你能告诉我们你在哪里渲染局部吗?检查您的服务器日志,它实际上正在呈现,它列出了它在输出中呈现的模板/部分
  • 我已经根据您的提问更新了问题

标签: ruby-on-rails join nested-attributes has-many-through cocoon-gem


【解决方案1】:

我想通了!:) 如果有人遇到同样的问题,我将工作代码放在下面。

产品.rb

class Product < ApplicationRecord
  has_many :product_parts
  has_many :parts, through: :product_parts
  accepts_nested_attributes_for :parts, reject_if: blank?, allow_destroy: false
  accepts_nested_attributes_for :product_parts, allow_destroy: true
end

product_part.rb

class ProductPart < ApplicationRecord
  belongs_to :part
  belongs_to :product

  accepts_nested_attributes_for :part, reject_if: all_blank
end

part.rb

class Part < ApplicationRecord
  has_many :product_parts
  has_many :products, through: :product_parts
end

products_controller.rb

# GET /products/new
def new
  @product = Product.new
  @product.product_parts.build.build_part
end

产品/_form.html.slim

= simple_form_for(@product) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?
  = f.input :name, label: false, class: 'form-control', placeholder: t('placeholders.input_product_name')
  fieldset#parts
    = f.simple_fields_for :product_parts do |product_part|
      .field
        == render 'product_part_fields', f: product_part
    = link_to_add_association 'Add part', f, :product_parts, class: 'btn btn-primary btn-xs'
    = f.button :submit 

产品/_product_part_fields.html.slim

.nested-fields.field
  .field.has-addons
    = link_to_remove_association f
    .field
      = f.input :quantity
      = f.collection_select :part_id, Part.all, :id, :name, {}, {class: 'select2'}

【讨论】:

    【解决方案2】:

    您的服务器日志的末尾显示您的部分正在寻找一个名为“部分”的变量,并引发 500 错误。

    将您的 _form.html.slim 文件更改为:

    = simple_form_for(@product) do |f|
      = f.input :name
      = f.simple_fields_for :parts do |part|
        == render 'part_fields', part: part
      = f.button :submit
    

    这会将part对象发送到partial,并清除错误。

    【讨论】:

    • 谢谢你标记它解决了服务器错误的问题并呈现了表单,但我仍然没有机会从 db 的部件列表中选择部件以与新产品同时添加到它们。
    【解决方案3】:

    fields_for 遍历关联并为关联中的每条记录执行一次块,但如果关联为空,则没有可迭代的内容。

    因此,为了显示新记录的输入,您需要在控制器中播种关联:

    class ProductsController < ApplicationController
      # ...
    
      # GET /products/new
      def new
        @product = Product.new do |p|
          3.times { p.parts.new } 
        end
      end
    
      # ...
    end 
    

    在您第一次尝试使用符号:part 的表单时,这将引发NoMethodError (undefined method 'name' for :part:Symbol)

    = f.simple_fields_for :parts do |part|
      = f.label :part.name
      = f.input :part.quantity
    

    应该是:

    = f.simple_fields_for :parts do |part|
      = f.label part.name
      = f.input part.quantity
    

    @marks 回答在您的第二次尝试中解决了缺失的本地问题。

    【讨论】:

      猜你喜欢
      • 2015-11-27
      • 1970-01-01
      • 2018-04-23
      • 2015-05-16
      • 1970-01-01
      • 1970-01-01
      • 2015-03-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多