Rails 作用域唯一性约束在更新嵌套属性时抛出错误

Rails scoped uniqueness constraint throws error when updating nested attributes

我有一个应用程序可以从一系列可用于指定页面布局的小部件创建页面,称为活动页面。我正在研究加载和编辑活动页面内容、更改内容以及更新该页面相关条目的功能。页面上特定小部件的顺序存储在名为 page_display_order 的列中。页面显示顺序在整个活动页面中是唯一的,因为两个小部件不能位于页面上的同一位置。这是更新页面时有时会抛出错误的列。

我的属于活动页面的小部件模型如下所示:

class CampaignPagesWidget < ActiveRecord::Base

  belongs_to :campaign_page, inverse_of: :campaign_pages_widgets
  belongs_to :widget_type
  has_one :actionkit_page

  validates_presence_of :content, :page_display_order, :campaign_page_id, :widget_type_id
  # validates that there are not two widgets with exactly same content on the same campaign page
  # and that the page display order integer is unique across the widgets with that campaign page id
  validates_uniqueness_of  :page_display_order, :content, :scope => :campaign_page_id
end

这是在编辑页面时请求更新时从页面读取小部件数据的方式(在 campaign_pages_controller.rb 中)。它首先找出它是哪种类型的小部件(存储在 table 和小部件模型中),然后使用页面编辑中指定的小部件的内容填充一个对象,并将其推送到一个数组中,该数组指定更新活动页面时要更新的嵌套属性:

@campaign_page = CampaignPage.find params[:id]
@widgets = @campaign_page.campaign_pages_widgets

permitted_params = CampaignPageParameters.new(params).permit
widget_attributes = []
params[:widgets].each do |widget_type_name, widget_data|
      # widget type id is contained in a field called widget_type:
      widget_type_id = widget_data.delete :widget_type
      widget = @widgets.find_by(widget_type_id: widget_type_id)
      widget_attributes.push({
        id: widget.id,
        widget_type_id: widget_type_id,
        content: widget_data,
        page_display_order: i})
      i += 1
    end

    permitted_params[:campaign_pages_widgets_attributes] = widget_attributes
    @campaign_page.update! permitted_params.to_hash
    redirect_to @campaign_page

在某些情况下,这会按预期进行更新和重定向,而在其他时候,它会抛出以下错误:

ActiveRecord::RecordInvalid in CampaignPagesController#update Validation failed: Campaign pages widgets page display order has already been taken, Campaign pages widgets is invalid

我是 Ruby 和 Rails 的新手,非常感谢您提供有关我可能做错了什么以及我应该如何做得更好的建议。我不太了解如何做事 'the Rails way',我知道我必须重构控制器中的代码。

更新:仅当有多个小部件时才会抛出错误。如果我注释掉范围内的唯一性约束,一切都会正常进行。它确实更新现有条目而不是创建新条目,所以这不是问题。

发生这种情况是因为小部件更新不会一次全部发生 - 它们 运行 一次更新一个。 Rails 通过在数据库中查询具有相同数据的现有行来检查唯一性,如果发现行的 ID 与您要保存的对象的 ID 不同,则停止。例如,假设您有

| id | campaign_page_id | page_display_order |
| 10 | 100              | 1                  |
| 20 | 100              | 2                  |

当您尝试将小部件顺序切换到 [10, 2] [20, 1] 时,Rails 开始保存小部件 10 并检查其新顺序 (2) 是否唯一。但是,它已经找到具有该顺序的小部件 20 行并引发您看到的错误。

查看 acts as list,这是一个 gem 可以为您管理的。否则,我认为您需要避开 Rails 并手动执行位置更新。