Rails 8 Strong Parameters:双括号修复用于嵌套属性

发布: (2026年1月11日 GMT+8 16:11)
4 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将为您翻译成简体中文并保留原有的 Markdown 格式。

Problem

在升级到 Rails 8 时,你可能会开始使用 params.expect——通常是 RuboCop 的 Rails/StrongParametersExpect 提示的——以使 strong‑parameter 合约更加明确。
当嵌套属性以索引哈希的形式提交时,会出现一个棘手的问题:它们可能会被静默过滤,导致验证以不明显的方式失败。

Model

class Invoice  {
  "date" => "2026-01-11",
  "due_date" => "2026-01-12",
  "client_id" => "123",
  "line_items_attributes" => {
    "0" => {
      "description" => "Web Development",
      "quantity"    => "10",
      "unit_price"  => "150"
    },
    "1" => {
      "description" => "UI Design",
      "quantity"    => "5",
      "unit_price"  => "200"
    }
  }
}

⚠️ Important: line_items_attributes 不是数组;它是一个以动态数字字符串("0""1"、…)为键的哈希。这一区别是 bug 的根本原因。

使用 require/permit(有效)

def invoice_params
  params.require(:invoice).permit(
    :date,
    :due_date,
    :client_id,
    line_items_attributes: [:description, :quantity, :unit_price, :_destroy]
  )
end

使用 permit 时,嵌套属性被正确分配,验证通过。

使用 expect(失败)

def invoice_params
  params.expect(
    invoice: [
      :date,
      :due_date,
      :client_id,
      line_items_attributes: [:description, :quantity, :unit_price, :_destroy]
    ]
  )
end
  • 未抛出强参数错误。
  • line_items_attributes 被过滤掉,因此没有分配任何行项目。
  • validates :line_items, presence: true 失败,发票未被持久化。

检查结果:

invoice_params[:line_items_attributes]
# => #

声明 line_items_attributes: [:description, :quantity, :unit_price] 告诉 Rails 期望 一个包含这些键的单层嵌套哈希。然而,传入的数据是 一个带索引的哈希集合

修复方法:集合使用双括号

要表达“嵌套哈希的集合”,必须将内部数组再包裹在另一个数组中:

def invoice_params
  params.expect(
    invoice: [
      :date,
      :due_date,
      :client_id,
      line_items_attributes: [[
        :id,
        :description,
        :quantity,
        :unit_price,
        :_destroy
      ]]
    ]
  )
end
  • 内部数组 → 每个行项目允许的键。
  • 外部数组 → 表示重复/类似集合的结构。

这同时匹配:

  • 索引哈希("0" => {...}
  • 哈希数组([{...}, {...}]

修复前后测试

# Before fix – no line items created
expect {
  post invoices_path, params: valid_params
}.to not_change(Invoice, :count)
 .and change(LineItem, :count).by(0)

# After fix – line items created
expect {
  post invoices_path, params: valid_params
}.to change(Invoice, :count).by(1)
 .and change(LineItem, :count).by(2)

形状 vs. expect 声明 摘要

形状expect 声明
单层嵌套哈希line_items_attributes: [:description]
索引哈希 ("0"=>…)line_items_attributes: [[:description]]
哈希数组line_items_attributes: [[:description]]

如果嵌套记录消失或验证意外失败,请检查传入参数的 形状,而不仅仅是其值。

Source:

Rails 8 迁移指南

  • RuboCop 常常建议将 params.require(...).permit(...) 替换为 params.expect(...)
  • Rails/StrongParametersExpect cop 并不会自动处理 Rails 嵌套表单使用的索引哈希格式(*_attributes)。草率的重写可能会改变行为。
  • 对于 has_many 嵌套属性,始终使用双括号语法([[...]])。
  • 当参数高度动态或结构复杂时,保留 require/permit 或在局部禁用该 cop 可能更为合理。

要点

  • params.expectpermit 更严格,必须明确指定结构。
  • 对于嵌套哈希的集合使用 [[...]]
  • 验证失败可能是静默丢弃嵌套属性的唯一表现。
  • 在更改强参数处理方式时,请求规范(request specs)至关重要。

如果这帮你省了时间——或者在你发现之前让你付出了代价——你并不孤单。

Back to Blog

相关文章

阅读更多 »

发布 Gon v7.0.0

发布 v7.0.0 Gon v7.0.0 发布 https://github.com/gazay/gon/releases/tag/v7.0.0 – 这次重大版本升级引入了不兼容的更改。不兼容的更改:reque...