更改连接表中的属性值时,Rails 5 正在插入而不是更新

Posted

技术标签:

【中文标题】更改连接表中的属性值时,Rails 5 正在插入而不是更新【英文标题】:Rails 5 is inserting instead of updating when changing the value of attribute in the join table 【发布时间】:2020-08-22 16:24:15 【问题描述】:

我有一个使用 has_many :through 关联的 HABTM 关系。我在更新连接表中的属性时遇到问题,因为它不是更新记录,而是在表中插入一条新记录,从而创建重复项。

我在创建索引和添加验证时尝试使用 UNIQUE 约束,但现在当我更新记录时出现验证错误或错误: BatchesController#update 中的 ActiveRecord::RecordNotUnique mysql2::Error: 重复条目

提供一些上下文:

我有 4 个表:manufacturing_orders、order_products、batches 和 batches_order_products(连接表)。 一个制造订单有许多订单产品。 还有一个manufacturing_order 有很多批次。 批次有很多 order_products。 当我创建一个批次时,我复制了属于同一个制造订单的所有订单产品,然后我可以在表格中为每个订单分配一个数量。

所以创建看起来工作正常,但是当我更新任何数量时,它只是再次插入整个关系,而不是更新现有的。

型号manufacturing_order.rb

class ManufacturingOrder < ApplicationRecord
  has_many :batches, inverse_of: :manufacturing_order, dependent: :destroy
  has_many :order_products, inverse_of: :manufacturing_order, dependent: :destroy

  accepts_nested_attributes_for :order_products
  accepts_nested_attributes_for :batches
end

型号order_product.rb

class OrderProduct < ApplicationRecord

  belongs_to :manufacturing_order

  has_many :batches_order_products
  has_many :batches, :through => :batches_order_products
end

模型batch.rb

class Batch < ApplicationRecord
  belongs_to :manufacturing_order

  has_many :batches_order_products
  has_many :order_products, :through => :batches_order_products

  accepts_nested_attributes_for :batches_order_products
end

模型batches_order_product.rb

class BatchesOrderProduct < ApplicationRecord
  belongs_to :batch
  belongs_to :order_product

  validates :batch_id, uniqueness:  scope: :order_product_id 
end

控制器batches_controller.rb

class BatchesController < ApplicationController
  def new
    manufacturing_order = ManufacturingOrder.find(params[:manufacturing_order_id])
    order_products = manufacturing_order.order_products
    @batch = Batch.new(
      manufacturing_order: manufacturing_order,
      order_products: order_products
    )
  end

  def create
    @batch = Batch.new(load_params)
    if @batch.save
      flash[:notice] = crud_success
      redirect_to action: :index
    else
      flash[:error] = @batch.errors.full_messages.to_sentence
      render action: :new
    end
  end

  def edit
    @batch = Batch.find(params[:id])
  end

  def update
    @batch = Batch.find(params[:id])
    if @batch.update_attributes(load_params)
      flash[:notice] = crud_success
      redirect_to action: :index
    else
      flash[:error] = @batch.errors.full_messages.to_sentence
      render action: :edit
    end
  end

  private
  def load_params
    params.require(:batch)
    .permit(:name,
      :date,
      :manufacturing_order_id,
      :status,
      order_products: [],
      order_products_ids: [],
      batches_order_products_attributes: [:id, :quantity, :order_product_id]
      )
  end
end

这是分批的形式:

  = bootstrap_form_for([@batch.manufacturing_order, @batch]) do |f|
    = f.hidden_field :manufacturing_order_id
       = f.text_field :name, label: 'Name'
        = f.text_field :date
    table
      thead
        tr
          th= "Product"
          th= "Quantity"
    tbody
      = f.fields_for :batches_order_products do |bop|
        = bop.hidden_field :order_product_id
      tr
        td
          = bop.object.order_product.name
        td
          = bop.text_field :quantity

  = f.submit 'Save'

任何帮助将不胜感激。谢谢!

更新: 这些是提交编辑表单时传递的参数。有什么线索吗?

"utf8"=>"✓",
 "_method"=>"patch",
 "batch"=>
  "manufacturing_order_id"=>"8",
   "name"=>"MAS",
   "date"=>"07/05/2020",
   "batches_order_products_attributes"=>
    "0"=>"order_product_id"=>"12", "quantity"=>"77777777", "id"=>"",
     "1"=>"order_product_id"=>"13", "quantity"=>"9.0", "id"=>"",
     "2"=>"order_product_id"=>"14", "quantity"=>"7.0", "id"=>"",
 "commit"=>"Guardar",
 "manufacturing_order_id"=>"8",
 "id"=>"7"

编辑 2:我更新了嵌套表单以将 id 包含在隐藏字段中,如下所示:

= f.fields_for :batches_order_products do |bop|
  = bop.hidden_field :order_product_id
  = bop.hidden_field :id, value: @batch.id

  = bop.object.order_product.name
  = bop.text_field :quantity, label: ''

但是现在 Rails 在更新时抱怨这个:

ActiveRecord::StatementInvalid in BatchesController#update

Mysql2::Error: Unknown column 'batches_order_products.' in 'where clause': SELECT `batches_order_products`.* FROM `batches_order_products` WHERE `batches_order_products`.`batch_id` = 9 AND `batches_order_products`.`` IN ('9', '9', '9', '9', '9') 

我不知道为什么 Rails 在 SQL 查询中添加了最后一个奇怪的部分。

【问题讨论】:

通常发生这种情况是因为缺少 id 属性。但是这里的代码看起来不错 - 您正在编写属性。我将首先覆盖 setter 并检查传递给模型 def batches_order_products_attributes=(hash); byebug; super; end 的内容,如果这不能帮助您通过控制器返回表单。 谢谢你。我用提交编辑表单时传递的参数更新了问题。有什么线索吗? 这就解释了骗子。但我真的不知道它为什么会发生 - 它也是一个空白字符串,这真的很奇怪。检查表单呈现的 html 以及 @batch.batches_order_products 返回的记录是否有 id。 谢谢@max!我尝试添加bop.hidden_field :id, value: @batch.id,现在 id 与参数一起传递。但仍然抱怨,而不是说:ActiveRecord::StatementInvalid in BatchesController#update Mysql2::Error: Unknown column 'batches_order_products.' in 'where clause': SELECT 'batches_order_products'.* FROM 'batches_order_products' WHERE 'batches_order_products'.'batch_id' = 9 AND 'batches_order_products'.'' IN ('9', '9', '9', '9', '9') 。我仍在试图弄清楚为什么它会在 SQL 查询中添加最后一部分。 【参考方案1】:

所以我终于想通了。

问题是连接表需要一个 ID 列来引用。该表有一个 batch_id 和 order_product_id 的索引,但由于某种原因它不起作用,ActiveRecord 正在寻找一个 ID。添加它解决了问题。

感谢@max 提供了一些参考。

class AddIndexToBatchesOrderProductsJoinTable < ActiveRecord::Migration[5.2]
  def change
    # add_index :batches_order_products, [:batch_id, :order_product_id], unique: true
    add_column :batches_order_products, :id, :primary_key
  end
end

【讨论】:

以上是关于更改连接表中的属性值时,Rails 5 正在插入而不是更新的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在不先阅读的情况下更改rails中的记录

如何在Rails 5中获取具有匹配ID的连接表中的记录数

带有 NSManagedObject 的 EXC_BAD_ACCESS

Knockout JS,如何在更改可观察数组中的值时更改样式属性

在 Rails 中连接关联的按钮不起作用。所有权转让

SQL怎样把A表中的int字段插入到B表中的varchar型字段(不能更改原来A。B表中的字段原有属性)