Strong Parameters - Can't Access the Deeply-Nested Attributes

僤鯓⒐⒋嵵緔 提交于 2019-12-24 05:05:17

问题


One StackOverflow question has asked what I need, but the self-answer didn't help me know what to do next. The scenario presented in that question (whitelisting deeply nested strong parameters in rails) is pretty much what I've got going on, but I'll post an abbreviation of mine (still very long) and hope someone--maybe even the Dave of the post--can help (I don't have enough reputation to comment and ask him). There are few links about nested strong parameters I haven't read, and I've dealt with some on many controllers and API endpoints, but this is the most complex in the app. (I'm including such a long example so you can see the full complexity.)

This is on the sales_controller, and one of the attributes we can't get to is the timezone_name, which is in the run_spans_attributes, which is in the options_attributes under sale. I've tried just about all of the different syntax approaches that match most of the nested attributes with strong parameters issues here on StackOverflow, but none of it has worked. Do I need more classes? Are there magic brackets? I need new suggestions. Please.

It should be noted that this is with the strong_parameters gem and Rails 3.2.21, but I want to get the app ready for Rails 4, so I'm hoping to avoid a short-term solution.

Sorry it's so long:

 Parameters:
    "sale"=>{
      "cloned_from"=>"",
      "type"=>"Localsale",
      "primary_contact_attributes"=>{
          "primary"=>"true",
          "first_name"=>"Fred",
          "id"=>"1712"
        },
      "contract_signed_on"=>"March 20, 2015",
      "billing_addresses_attributes"=>{
          "0"=>{
              "billing"=>"1",
              "city"=>"San Diego",
              "id"=>"29076"
            }
        },
      "other_contacts_attributes"=>{
          "0"=>{
              "first_name"=>"Fred",
              "_destroy"=>"false",
              "id"=>"170914"
            },
          "1"=>{
              "first_name"=>"Fred",
              "last_name"=>"Smith",
              "_destroy"=>"false",
              "id"=>"1798"
            }
        },
      "opportunity_detail_attributes"=>{
          "original_salesperson_id"=>"",
          "id"=>"10130"
        },
      "production_editor"=>"1868097",
      "event_sale_attributes"=>{
          "0"=>{
              "name"=>"is_super_sale",
              "value"=>"0",
              "id"=>"15326"
            },
          "1"=>{
              "name"=>"super_show_code",
              "value"=>""
            },
        },
      "scheduling_note"=>"",
      "category_ids"=>["2", "364"],
      "options_attributes"=>{
          "0"=>{
              "title"=>"T-Shirt and Bag Check",
              "event_starts_at(1i)"=>"2015",
              "event_starts_at(2i)"=>"6",
              "event_doors_open_at_attributes"=>{
                  "option_id"=>"8682604",
                  "doors_time(1i)"=>"",
                  "id"=>"278382"
                },
              "event_option_attributes"=>{
                  "0"=>{
                      "name"=>"event_duration",
                      "value"=>""
                    },
                  "1"=>{
                      "name"=>"send_pre_event_email",
                      "value"=>"1",
                      "id"=>"632546"
                    }
                },
              "language_id"=>"1",
              "run_spans_attributes"=>{
                  "0"=>{
                      "timezone_name"=>"Eastern Time (US & Canada)",
                      "_destroy"=>"false",
                      "id"=>"560878"
                    },
                  "1429320288130"=>{
                      "timezone_name"=>"Eastern Time (US & Canada)",
                      "_destroy"=>"false"
                    }
                },
              "_destroy"=>"false",
              "id"=>"8682604"
              }#ends 0 option
          },#ends options
      "coupons_per_redemption"=>"1",
      "methods_attributes"=>{
          "0"=>{
              "redemption_code"=>"0",
              "_destroy"=>"0",
              "id"=>"9797012"
            },
          "1"=>{
              "redemption_code"=>"4",
              "_destroy"=>"1",
              "vendor_provided_promo_code"=>"0",
              "promo_code"=>""
            }
        }, #ends redemption methods
      "addresses_attributes"=>{
          "0"=>{
              "street_address_1"=>"2400 Cat St",
              "primary"=>"0",
              "id"=>"2931074",
              "_destroy"=>"false"
            }
        },
      "zoom"=>"",
      "video_attributes"=>{
          "youtube_id"=>"",
        },
      "updated_from"=>"edit"
      }

Help me do this right? By the way, all kinds of .tap do |whitelisted| approaches have failed.

 private
 def_sale_strong_params
    params.require(:sale).permit(:how, :the, :heck, :do, :the_attributes =>
       [:make, themselves => [:known, :outside => [:of, :these => [:darn,
        :parentheses], :and], :brackets]])
 end

回答1:


1. Validate Your Progress in the Console

To configure Strong Parameters, it can help to work in the Rails console. You can set your parameters into an ActiveSupport::HashWithIndifferentAccess object and then start to test out what you'll get back from ActionController::Parameters.

For example, say we start with:

params = ActiveSupport::HashWithIndifferentAccess.new(
  "sale"=>{
    "cloned_from"=>"",
    "type"=>"Localsale"}
)

We can run this:

ActionController::Parameters.new(params).require(:sale).permit(:cloned_from, :type)

And we'll get this as the return value and we'll know we've successfully permitted cloned_from and type:

{"cloned_from"=>"", "type"=>"Localsale"}

You can keep working up until all parameters are accounted for. If you include the entire set of parameters that you had in your question...

params = ActiveSupport::HashWithIndifferentAccess.new(
  "sale"=>{
    "cloned_from"=>"",
    "type"=>"Localsale",
    ...
    "options_attributes"=>{
      "0"=>{
        "title"=>"T-Shirt and Bag Check",
        ...
        "run_spans_attributes"=>{
          "0"=>{
            "timezone_name"=>"Eastern Time (US & Canada)",
            ...
)

...you can get down to timezone_name with a structure that will look something like this:

ActionController::Parameters.new(params).require(:sale).permit(:cloned_from, :type, options_attributes: [:title, run_spans_attributes: [:timezone_name]])

The return value in the console will be:

{"cloned_from"=>"", "type"=>"Localsale", "options_attributes"=>{"0"=>{"title"=>"T-Shirt and Bag Check", "run_spans_attributes"=>{"0"=>{"timezone_name"=>"Eastern Time (US & Canada)"}, "1429320288130"=>{"timezone_name"=>"Eastern Time (US & Canada)"}}}}}

2. Break the Work Into Smaller Parts

Trying to handle all of the allowed attributes for each model all on one line can get confusing. You can break it up into several lines to make things easier to understand. Start with this structure and fill in the additional attributes for each model:

video_attrs = []  # Fill in these empty arrays with the allowed parameters for each model
addresses_attrs = []
methods_attrs = []
run_spans_attrs = [:timezone_name]
event_option_attrs = []
options_attrs = [:title, event_option_attributes: event_option_attrs, run_spans_attributes: run_spans_attrs]
event_sale_attrs = []
opportunity_detail_attrs = []
other_contacts_attrs = []
billing_addresses_attrs = []
primary_contact_attrs = []
sales_attributes = [
  :cloned_from, 
  :type, 
  primary_contact_attributes: primary_contact_attrs, 
  billing_addresses_attributes: billing_addresses_attrs,
  other_contacts_attributes: other_contacts_attrs,
  options_attributes: options_attrs, 
  opportunity_detail_attributes: opportunity_detail_attrs,
  event_sale_attributes: event_sale_attrs,
  methods_attributes: methods_attrs,
  addresses_attributes: addresses_attrs,
  video_attributes: video_attrs
]

Then you can just send *sales_attributes into the permitted parameters. You can verify in the console with:

ActionController::Parameters.new(params).require(:sale).permit(*sales_attributes)



回答2:


Try this out:

params.require(:sale).permit(:options_attributes => [:other_attributes, { :run_spans_attributes => [:timezone_name] }]



回答3:


Going by what I did in my controller, here's what I'd assume would work:

params.require(:sale).permit(:cloned_form, ... billing_addresses_attributes: [:id, :city, :building] ...)

Edfectively, put brackets around the attributes like "0"=> and don't forget to adf :id and :_destroy like I mentioned in my answer, as I ended up creating other models and used accepts_nested_attributes_for.




回答4:


For posterity, I want to let folks know what we finally discovered: Well, the information was out there, and in fact, I read it in Russ Olsen's Eloquent Ruby, which I thankfully remembered during a debugging session with a more advanced developer: "With the advent of Ruby 1.9, however, hashes have become firmly ordered."

Why was that important? I'd alphabetized the attributes on the controller, and because of how the run_span_attributes were set up, they NEEDED timezone_name to be in the front. And if it wasn't, then it couldn't get to it. You can only imagine how debugging this tormented us. But at least we know now: ORDER MATTERS. So, if all else fails, remember that.



来源:https://stackoverflow.com/questions/29714451/strong-parameters-cant-access-the-deeply-nested-attributes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!