0

I have an update form in my web application and I'm having trouble when permitting the params I receive from this form. This is how the params gets to the controller:

<ActionController::Parameters {"company"=>{"description"=>"description", 
"email_address"=>"contact@gmail.com", 
"hours"=>"{\"fri\":[],\"mon\":[[\"09:00\",\"16:00\"]],\"sat\":[],
\"sun\":[[\"09:00\",\"16:00\"]],
\"thu\":[[\"09:00\",\"12:00\"], [\"13:00\",\"16:00\"]],
\"tue\":[[\"08:45\",\"16:00\"]],\"wed\":[[\"09:00\",\"16:00\"]]}"}, 
"controller"=>"my_controller", "action"=>"update"} permitted: false>

They all get to the controller as strings but hours is parsed to json (using JSON.parse) resulting in something like:

{"fri"=>[], "mon"=>[["09:00", "16:00"]], "sat"=>[], "sun"=>[["09:00", "16:00"]], 
"thu"=>[["09:00", "12:00"], ["13:00", "16:00"]], "tue"=>[["08:45", "16:00"]], 
"wed"=>[["09:00", "16:00"]]}

Hours represents the opening hours of a store for example. It's separated by the days of the weeks. And each day can have no values for a day, have one value or two. After it's parsed hours is a hash of arrays and these arrays can be empty or has one or two arrays.

My problem is permitting this hour parameter, when using params.require(:company).permit(). I tried many different ways like:

  • params.require(:company).permit(:description, : email_address, :hours)
  • params.require(:company).permit(:description, : email_address, hours: {})
  • params.require(:company).permit(:description, : email_address, hours: [])
  • params.require(:company).permit(:description, : email_address, hours: { [sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: [] })
  • params.require(:company).permit(:description, : email_address, hours: [ [sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: [] ])
  • And others

but it fails or just permit it without any content, like that:

<ActionController::Parameters {"description"=>"description", 
"email_address"=>"contact@gmail.com",
"hours"=><ActionController::Parameters {"sun"=>[], "mon"=>[], "tue"=>[], 
"wed"=>[], "thu"=>[], "fri"=>[], "sat"=>[]} permitted: true>} permitted: true>

Thanks!

tadman
  • 208,517
  • 23
  • 234
  • 262
  • A) It's not clear how you're supplying this or how it's being received. B) Why Rails 4 and 5? – tadman Jan 23 '20 at 23:46
  • I removed RoR 4 and 5 tags. The project is using Rails 5.1.6.2. And I edit the begining of my question. I hope it's ok now but feel free to ask. Thanks! – Breno Nogueira Jan 23 '20 at 23:56
  • Tags are better now. Question is how is this data coming in? Like if the data's missing, how is it being received and decoded? At what point in the chain does it go missing? – tadman Jan 24 '20 at 00:06
  • Watch the typo there, presumably `: email_address` should be `:email_address`, no space. – tadman Jan 24 '20 at 00:27
  • 1
    the last example, you have an extra [ in hours attributes, can you fix it and have a try? params.require(:company).permit(:description, :email_address, hours: [ sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: [] ]) instead of params.require(:company).permit(:description, : email_address, hours: [ [sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: [] ]) – Feifei Xiong Jan 24 '20 at 00:27
  • According to [this answer](https://stackoverflow.com/questions/39986990/how-to-permit-hash-with-key-values) you should be on the right track with your first and second attempts. – tadman Jan 24 '20 at 00:28
  • 1
    Remember `hours` won't have that structure until you decode it. I'd recommend either A) supplying *all* arguments as JSON or B) supplying that using URI-encoded parameters like the rest of them. – tadman Jan 24 '20 at 00:30

1 Answers1

0

Rails does not actually have a syntax for whitelisting nested arrays as far as I know.

This would:

params.require(:company)
      .permit(:description, :email_address, hours: { mon: [] })

permit any scalar value in company[:hours][:mon]. however an array is not one of the permitted scalar types:

[ String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile ]

Which explains why the values are "discarded". I don't think rails knows what to with permit(hours: { mon: [[]] }) at all.

But this is pretty easily avoidable if you just fix the incoming JSON or transform it in Ruby.

json = if params[:company][:hours].present?
  # @todo handle JSON parsing errors
  # use each_with_object instead if your Ruby is older than 2.4
  JSON.parse(params[:company][:hours])
    &.transform_values { |v| v.empty? ? v : v.first } 
end
params.merge(
  hours: json
).permit(
   :description, :email_address, 
   hours: { sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: [] }
)

This works since it transforms "mon"=>[["09:00", "16:00"]] into "mon"=>["09:00", "16:00"] and "09:00", "16:00" are strings and thus permitted scalar values.

max
  • 96,212
  • 14
  • 104
  • 165
  • @tadman is right though in that you should really find a better solution in the first place. This is hacky as hell. – max Jan 24 '20 at 02:47