Rails 8 Strong Parameters: 중첩 속성을 위한 이중 대괄호 수정

발행: (2026년 1월 11일 오후 05:11 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

위의 링크에 포함된 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)

Problem

Rails 8로 업그레이드할 때 params.expect를 사용하기 시작할 수 있습니다—보통 RuboCop의 Rails/StrongParametersExpect에 의해 촉구됩니다—강한 파라미터 계약을 더 명확하게 만들기 위해.
인덱스 해시 형태로 제출된 중첩 속성에서는 날카로운 문제가 나타납니다: 이들은 조용히 필터링될 수 있어, 검증이 명확하지 않은 방식으로 실패하게 됩니다.

모델

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"
    }
  }
}

⚠️ 중요: line_items_attributes배열이 아니라 동적 숫자 문자열("0", "1", …)을 키로 하는 해시입니다. 이 구분이 버그의 근본 원인입니다.

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
  • Inner array → 각 라인 아이템에 허용되는 키.
  • Outer array → 반복/컬렉션과 같은 구조를 나타냅니다.

이는 다음 두 경우 모두에 매치됩니다:

  • 인덱스가 있는 해시 ("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)

Shape vs. Declaration 요약

Shapeexpect declaration
단일 중첩 해시line_items_attributes: [:description]
인덱스된 해시 ("0"=>…)line_items_attributes: [[:description]]
해시 배열line_items_attributes: [[:description]]

중첩 레코드가 사라지거나 검증이 예상치 않게 실패할 경우, 값만이 아니라 들어오는 파라미터의 shape을 확인하세요.

Guidance for Rails 8 Migration

  • RuboCop은 종종 params.require(...).permit(...)params.expect(...)로 교체하라고 제안합니다.
  • Rails/StrongParametersExpect cop은 Rails 중첩 폼(*_attributes)에서 사용되는 인덱스‑해시 형식을 자동으로 처리하지 못합니다. 순진한 재작성은 동작을 바꿀 수 있습니다.
  • has_many 중첩 속성의 경우, 항상 이중 대괄호 구문([[...]])을 사용하세요.
  • 매우 동적이거나 복잡한 파라미터를 다룰 때는 require/permit을 유지하거나, 해당 cop을 로컬에서 비활성화하는 것이 합리적일 수 있습니다.

Takeaways

  • params.expectpermit보다 더 엄격하며 명시적인 구조를 요구합니다.
  • 중첩 해시 컬렉션에는 [[...]]를 사용하세요.
  • 검증 실패는 조용히 무시된 중첩 속성의 유일한 증상일 수 있습니다.
  • 강력 파라미터 처리를 변경할 때는 요청 스펙이 필수적입니다.

이 내용이 시간을 절약했든, 찾기 전에 비용을 들였든, 당신만 그런 것이 아닙니다.

Back to Blog

관련 글

더 보기 »

Gon v7.0.0 출시

Release v7.0.0 Gon v7.0.0 릴리스 https://github.com/gazay/gon/releases/tag/v7.0.0 – 이번 메이저 버전 업데이트는 breaking changes를 도입합니다. Breaking change: reque...