问题
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