ruby-on-railsformsnested-formsstrong-parametersnested-form-for

Why does strong params order in Rails matter?


In my product_model_controller.rb I have the following code for the strong params:

  def product_model_params
    params.require(:product_model)
          .permit(:name, :product_category_id, 
                  product_category_attributes: [:id, :name], attr_val_ids: [])
  end

In the way it is, it works fine. But, if I change the order of the params, it stops working. Example:

  def product_model_params
    params.require(:product_model)
          .permit(:name, product_category_attributes: [:id, :name],
                  :product_category_id, attr_val_ids: [])
  end

The error:

syntax error, unexpected ',', expecting => ..., :name], :product_category_id, attr_val_ids: []) ... ^

Why does this happen? I've been stuck with it for a long time now :/


product_model.rb

class ProductModel < ApplicationRecord
  validates :name, presence: true
  validates :name, uniqueness: true

  has_many :products
  has_many :product_model_attr_vals
  has_many :attr_vals, through: :product_model_attr_vals
  has_many :attrs, through: :attr_vals

  belongs_to :product_category

  accepts_nested_attributes_for :product_model_attr_vals
  accepts_nested_attributes_for :product_category
end

product_category.rb

class ProductCategory < ApplicationRecord
  validates :name, presence: true
  validates :name, uniqueness: true

  has_many :product_models
end

Solution

  • It's not an issue with strong params, but how Ruby parses method signatures and hashes. Abstracted a little bit your first example is this:

    some_method(arg1, arg2, key1: val1, key2: val2)
    

    Ruby will recognize that implicit trailing hash and internally represent it as this:

    some_method(arg1, arg2, {key1: val1, key2: val2})
    

    This only works for the right most arguments that are hash like. In your second example you've done this:

    some_method(arg1, key1: val1, arg2, key2: val2)
    

    Ruby doesn't know what to do with that. It turns the key2 argument into a hash, but then is left with an argument, what looks like a named argument, and an argument. And it doesn't like that.

    You could fix it by doing this:

    some_method(arg1, {key1: val1}, arg2, key2: val2)
    

    Or even this:

    some_method(arg1, {key1: val1}, arg2, {key2: val2})
    

    Both of which Ruby will see as argument, hash, argument, hash and is able to process.