rails: param data types should not be converted to strings in rails 5

Steps to reproduce

During a test, my parameters being sent to the controller method are

patch :update, params: params

example params below:

{:id=>10848000,
 :save_and_confirm=>false,
 :confirmation=>
  {:confirmer_email=>"test@example.com",
   :confirmer_first_name=>"First Name",
   :confirmer_last_name=>"Last Name",
   :hide_confirmer_name=>false,
  }
}

Inside the controller the params object is represented as below

 <ActionController::Parameters {"confirmation"=>{"confirmer_email"=>"test@example.com", "confirmer_first_name"=>"First Name", "confirmer_last_name"=>"Last Name", 
 "hide_confirmer_name"=>"false"}, "save_and_confirm"=>"false", "id"=>"10848000", "controller"=>"confirmations", "action"=>"update"} permitted: false>

Notice how all of the values are now strings instead of their respective data typs (Booleans, integers).

This makes expressions like if params[:save_and_confirm] always run even when the value is 'false'.

Expected behavior

The expected behavior should be that all values passed in retain their data types and not be converted to a string. Rails 4.2.7 works like this.

So the params object should look like:

 <ActionController::Parameters {"confirmation"=>{"confirmer_email"=>"test@example.com", "confirmer_first_name"=>"First Name", "confirmer_last_name"=>"Last Name", 
 "hide_confirmer_name"=>false}, "save_and_confirm"=>false, "id"=>10848000, "controller"=>"confirmations", "action"=>"update"} permitted: false>

Actual behavior

All parameters are converted to a string.

System configuration

5.0:

2.3.1:

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 44
  • Comments: 16 (12 by maintainers)

Commits related to this issue

Most upvoted comments

@att14 your problem isn’t the same as @logicminds - it’s the problem in #26212 that @matthewd linked to and the TL;DR is using as: :json will work in Rails 5.0.1. In the meantime you can manually set request.content_type = 'application/json' in your test.

This is frustrating. I’m having an issue on a legacy Rails 4.2.10 app and its converting my test param hashes into a structure that I know is not happening in the production system. I copied real life params off of my staging server and am copying them into my test suite.

Why doesn’t this go into my request as is? This seems silly. All of my Integers are converted into strings!

let(:task_rescheduled_webhook) {
    {
      "TITLE": "Webhook Test #2 rescheduled",
      "EVENT_ID": 5707274949492736,
      "REPORTER_NAME": "Daniel Rice",
      "OBJECT_TYPE": "TASK",
      "REPORTER_ID": 6333028408229888,
      "OBJECT_ID": 5663741160980480,
      "HAS_ATTACHMENT": false,
      "OBJECT_DATE": "2018-03-15T11:30:30-06:00",  #NOTE THE LOCALIZED TIMESTAMP, THIS IS NOT UTC TIME!!!!
      "EVENT_TYPE": "TASK_RESCHEDULED",
      "EVENT_TIMESTAMP": 1521127755,
      "MESSAGE": "Webhook Test #2 rescheduled for 03/15 11:30 AM by Daniel Rice.", "EXTRA_FIELDS": {"task_color": "#0693E3"}
    }.deep_stringify_keys!
  }

When I pry into my controller inside of the test I wrote, I see the following:

[1] pry(#<Webhooks::ArrivyController>)> params["arrivy"]
=> {"TITLE"=>"Webhook Test #2 rescheduled",
 "EVENT_ID"=>"5707274949492736",
 "REPORTER_NAME"=>"Daniel Rice",
 "OBJECT_TYPE"=>"TASK",
 "REPORTER_ID"=>"6333028408229888",
 "OBJECT_ID"=>"5663741160980480",
 "HAS_ATTACHMENT"=>false,
 "OBJECT_DATE"=>"2018-03-15T11:30:30-06:00",
 "EVENT_TYPE"=>"TASK_RESCHEDULED",
 "EVENT_TIMESTAMP"=>"1521127755",
 "MESSAGE"=>"Webhook Test #2 rescheduled for 03/15 11:30 AM by Daniel Rice.",
 "EXTRA_FIELDS"=>{"task_color"=>"#0693E3"}}

Grrrrr… 😡

This is frustrating because this isn’t how the params are being received in the Production app. So the assertions above about making a test case behave more like a real production app is not true. At least in my case its not.

@jrochkind I had wondered the same and remembered I saw the Rails code that handled the as: :json and format: :json parts. Basically as: :json does the same as format but also sets the content type: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/test_case.rb#L480

See also #610, a version of this bug that was in rails 3. I mention to link the two issues, in case anyone ends up there by googling.

as: :json seems to fix the problem in Rails 5.0.1. I completely don’t understand what as: :json is doing exactly vs format: :json, if anyone wants to link to relevant docs here in this issue it would probably help others who end up here.

I’d consider this a bug fix more than anything less. That your controller tests convert params to strings is a good thing, it reflects what happens in development and production.

Try sending a request to your controller with curl then you should see those “boolean” fields look a lot like strings 😁

So I assume in earlier versions of rails these params were converted to their respective data types automatically and now we must convert manually. When doing mass assignments are the values converted automatically?

Action Controller has never converted string types to anything else, however, Active Record does accept strings and will make sure to type cast those params. So if all you’re doing is @post.update(post_params) your controller will be fine. However, if you do:

if params[:frame_marked_as] != 8
  flash.alert = 'You are entering a world of pain!'
end

You will always be entering a world of pain, because the param won’t be an integer it will be a string. Hope that helps!

This doesn’t appear to respect the format argument anymore. I’ve tried both:

patch route, params: parameters.merge(format: :json)
patch route, params: parameters, format: :json

I am using rspec-rails, but these methods should still come from ActionController::TestCase::Behavior. Am I missing something?

So it turns out the reason it works in Rails 4.2 is that to_param for TrueClass and FalseClass return self for reason that aren’t clear (original commit: b20c575). In Rails 5.0 we now actually convert the params into a query string and pass them through the request object so they reflect reality. It wasn’t documented because we weren’t aware of there being a change and the original intention was to make a request look as much like a real request as possible. Since NilClass#to_param also returns self I suspect they will also be different.

We could add a line to the upgrading guide but since an app will already be broken because it’s assuming non-string values I doubt it’ll help anyone.

@logicminds is this a controller or integration test?

99% sure that 4.2.7 would’ve converted booleans to strings as well in controller tests since I worked on the code. Fairly sure the same applies to integration tests too but I could be wrong since I don’t remember working on such a feature at any point.

The fact that your tests convert booleans to strings is a good thing - trust me 😄

The reason being that the requests you get for real from browsers will all be strings so relying on tests returning boolean values won’t accurately reflect reality.

This is a controller test using ActionController::TestCase. Where is this change documented? I don’t see anybody talking about it on the web. And its a big gotcha when conditional logic is performed on what would have been a boolean value.

So I assume in earlier versions of rails these params were converted to their respective data types automatically and now we must convert manually. When doing mass assignments are the values converted automatically?