ruby-on-railssolidus

Strong params with permitted_attributes (in Solidus)


I'm using the Solidus eCommerce gem for Ruby on Rails. I'm creating an eCommerce website for sending mail, so I need to be able to attach addresses to individual line items, rather than just the user's billing and shipping addresses. In order to accomplish that, I'm passing address parameters in the cart form.

I'm getting Unpermitted parameter: address_attributes. Here are the parameters I'm passing in through my form:

parameters

Parameters: {"utf8"=>"✓", "authenticity_token"=>"[AUTHENTICITY_TOKEN]", "content"=>"", 
"order"=>{"address_attributes"=>[{"firstname"=>"", "lastname"=>"", "address1"=>"", "address2"=>"", "city"=>"", "country_id"=>"232", "state_id"=>"", "zipcode"=>"", "phone"=>""}]}, "variant_id"=>"1", "quantity"=>"1", "button"=>""}

Here's how I'm accessing them: address_attributes = order_params[:address_attributes].

Here's the order_params method that comes with Solidus:

orders_controller.rb

def order_params
  if params[:order]
    params[:order].permit(*permitted_order_attributes)
  else
    {}
  end
end

(I'm not sure why it uses this if else logic instead of params.require, but it's not my code.) When I put a debugger in there, and look at permitted_order_attributes this is what I see:

[:coupon_code, :email, :special_instructions, :use_billing, {:bill_address_attributes=>[:id, :firstname, :lastname, :first_name, :last_name, :address1, :address2, :city, :country_id, :state_id, :zipcode, :phone, :state_name, :country_iso, :alternative_phone, :company, {:country=>[:iso, :name, :iso3, :iso_name], :state=>[:name, :abbr]}], :ship_address_attributes=>[:id, :firstname, :lastname, :first_name, :last_name, :address1, :address2, :city, :country_id, :state_id, :zipcode, :phone, :state_name, :country_iso, :alternative_phone, :company, {:country=>[:iso, :name, :iso3, :iso_name], :state=>[:name, :abbr]}], :payments_attributes=>[:amount, :payment_method_id, :payment_method, {:source_attributes=>[:number, :month, :year, :expiry, :verification_value, :first_name, :last_name, :cc_type, :gateway_customer_profile_id, :gateway_payment_profile_id, :last_digits, :name, :encrypted_data, :existing_card_id,
  {
    :address_attributes=>[
      :id, :firstname, :lastname, :first_name, :last_name, :address1, :address2, :city, :country_id, :state_id, :zipcode, :phone, :state_name, :country_iso, :alternative_phone, :company, {:country=>[:iso, :name, :iso3, :iso_name], :state=>[:name, :abbr]}
    ]
  }
]}], :shipments_attributes=>[:special_instructions, :stock_location_id, :id, :tracking, :selected_shipping_rate_id]}, {:line_items_attributes=>[:id, :variant_id, :quantity]}]

So, there are the address_attributes. What's odd is that they're wrapped in {}. My understanding is that that means it's looking for parameters that look like this: "order"=>{["address_attributes"=>{"firstname"=>"...etc. But I'm not positive. I'm not sure why Solidus is looking for the parameters to be passed in that way, and I'm not sure how to write my form so that it passes paramaters like that. Here's my form:

_cart_form.html.erb

  <%= f.fields_for('address_attributes[][]', @address) do |form| %>
    <%= render :partial => 'spree/address/form', :locals => { form: form,
      address_type: 'shipping', address: @address } %>
  <% end %>

work around

If I change the order params method to this:

def order_params
  params.require(:order).permit(:address_attributes=>[:id, :firstname, :lastname, :first_name, :last_name, :address1, :address2, :city, :country_id, :state_id, :zipcode, :phone, :state_name, :country_iso, :alternative_phone, :company, {:country=>[:iso, :name, :iso3, :iso_name], :state=>[:name, :abbr]}])
end

then my form works. So, I have a work around, but I'd like to understand what's going on here. There are a few ways to solve this problem:

  1. Pass parameters in a form that would meet the existing order_params expectations.
  2. Modify permitted_order_attributes to accept the parameters as I'm passing them in.

Solution

  • I dumped what you are getting for permitted_order_attributes into a JSON formatting tool that I like to use, and go this:

    [
      : coupon_code,
      : email,
      : special_instructions,
      : use_billing,
      {
        : bill_address_attributes=>[
          : id,
          : firstname,
          : lastname,
          : first_name,
          : last_name,
          : address1,
          : address2,
          : city,
          : country_id,
          : state_id,
          : zipcode,
          : phone,
          : state_name,
          : country_iso,
          : alternative_phone,
          : company,
          {
            : country=>[
              : iso,
              : name,
              : iso3,
              : iso_name
            ],
            : state=>[
              : name,
              : abbr
            ]
          }
        ],
        : ship_address_attributes=>[
          : id,
          : firstname,
          : lastname,
          : first_name,
          : last_name,
          : address1,
          : address2,
          : city,
          : country_id,
          : state_id,
          : zipcode,
          : phone,
          : state_name,
          : country_iso,
          : alternative_phone,
          : company,
          {
            : country=>[
              : iso,
              : name,
              : iso3,
              : iso_name
            ],
            : state=>[
              : name,
              : abbr
            ]
          }
        ],
        : payments_attributes=>[
          : amount,
          : payment_method_id,
          : payment_method,
          {
            : source_attributes=>[
              : number,
              : month,
              : year,
              : expiry,
              : verification_value,
              : first_name,
              : last_name,
              : cc_type,
              : gateway_customer_profile_id,
              : gateway_payment_profile_id,
              : last_digits,
              : name,
              : encrypted_data,
              : existing_card_id,
              {
                : address_attributes=>[
                  : id,
                  : firstname,
                  : lastname,
                  : first_name,
                  : last_name,
                  : address1,
                  : address2,
                  : city,
                  : country_id,
                  : state_id,
                  : zipcode,
                  : phone,
                  : state_name,
                  : country_iso,
                  : alternative_phone,
                  : company,
                  {
                    : country=>[
                      : iso,
                      : name,
                      : iso3,
                      : iso_name
                    ],
                    : state=>[
                      : name,
                      : abbr
                    ]
                  }
                ]
              }
            ]
          }
        ],
        : shipments_attributes=>[
          : special_instructions,
          : stock_location_id,
          : id,
          : tracking,
          : selected_shipping_rate_id
        ]
      },
      {
        : line_items_attributes=>[
          : id,
          : variant_id,
          : quantity
        ]
      }
    ]
    

    It looks to me as if the only occurrence of address_attributes is nested inside payment_attributes > source_attributes. This sounds like your desired structure is broken somehow.

    You may be better to ignore their suggested code for your protected params, and to explicitly structure what you want in the normal way:

    def order_params
      params.require(:order).permit(:your, :choice, :of, :fields,
                                    address_attributes: [:address, :fields],
                                    other_nested_thing_attributes: [:stuff, :here])
    end
    

    You'd be 100% in control of what you want to accept from your forms. This would also help you to highlight errors in your form structure.

    HTH - comments or questions, just shout.