42

I'm trying to update to Rails 5, I'm getting the following deprecation warning:

DEPRECATION WARNING: Method to_hash is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.0/classes/ActionController/Parameters.html (called from column_header at /Data/Projects/portal/trunk/app/helpers/application_helper.rb:114)

The line the warning is on looks like this:

    link_to(name,
            {
              action: action_name,
              params: params.merge({ order: key, page: nil })
            },
            {
              title: "Sort by this field",
            }) +

As you can see, I'm not calling to_hash. Maybe Rails is. Maybe some other gem is. I have no way to tell, because they didn't think it was worth providing a stack trace. (Pro tip - it usually is worth providing a stack trace!)

So anyway, I followed the link, planning to find a replacement, and the merge method does not appear to be deprecated, but maybe they simply forgot to document deprecated status, so I can't really be sure.

So what am I supposed to do to clear this?

Hakanai
  • 12,010
  • 10
  • 62
  • 132
  • Is it `params.merge` or `link_to` that is calling `to_hash`? – mu is too short Aug 02 '16 at 03:19
  • I had a look at params.merge at least, and it doesn't. Maybe link_to does... in which case I could potentially call this Rails' fault? I don't really know. (This is *exactly* why a stack trace would be useful - I would rather not have to dig through library sources to figure out who broke something) – Hakanai Aug 02 '16 at 03:39
  • Debugger suggests that `link_to` calls it but I stepped in to investigate and it goes pretty deep. I managed to verify that `url_for` calls it, but can't figure out where. So should my view be that they shouldn't be doing that in their own library? I mean, I have passed a Parameters object into something that generates a link from parameters. It seems like it should be OK to do this. – Hakanai Aug 02 '16 at 03:52
  • Are you sure you should specify params to `url_for` through a param named `params`, and not directly by passing the params at the top level, as in: `link_to(name, {action: action_name, order: key, page: nil}.merge(params)`? – Mladen Jablanović Aug 02 '16 at 08:49
  • If you really need to pass `{params: params}`, try `{params: params.to_h}`. – Mladen Jablanović Aug 02 '16 at 08:50
  • Then I'd be calling the method they're telling me not to call. I guess I can use to_unsafe_h though... – Hakanai Aug 02 '16 at 23:14
  • Issue about this: https://github.com/rails/rails/issues/26415. It was closed recently (in Sep 2016) using a `to_h` conversion internally. – mahemoff Oct 06 '16 at 22:54
  • If you want to know where a method is being called, one hacky way is to temporarily monkey patch the method to log `caller` to a file of your choosing and then call `super()`. – Nathan Long Apr 29 '19 at 14:45

1 Answers1

76

Use .to_h

You can call .to_h to get a safe hash, according to a comment on the Rails PR.

There are now three methods for converting parameters to a hash.

  • .to_h means "if I haven't called .permit, assume nothing is allowed."
  • .to_unsafe_h means "if I haven't called .permit, assume everything is allowed."
  • .to_hash is now ambiguous. Rails treats it like .to_unsafe_h, but prints a warning because you haven't explicitly said which of the two options above you wanted.

First, let's see what happens if you haven't called .permit. In a Rails 5.0 console:

> params = ActionController::Parameters.new({yes: "y", no: "n"})

> params.to_h
{} # empty hash because nothing has been permitted

> params.to_unsafe_h
{"yes"=>"y", "no"=>"n"} # raw values with no warning; you asked for it

> params.to_hash
# (puts deprecation warning - if you want unsafe values, say so)
{"yes"=>"y", "no"=>"n"} # returns raw values

However, if you call .permit first, there will be no way to get the non-permitted values.

> params = ActionController::Parameters.new({yes: "y", no: "n"})

> params = params.permit(:yes)
# (puts warning about unpermitted parameter :no)

> params.to_h
{"yes"=>"y"} # permitted values only

> params.to_unsafe_h
{"yes"=>"y"} # permitted values only

> params.to_hash
# (puts deprecation warning, but still safe)
{"yes"=>"y"} # permitted values only

So:

  1. Always use .permit to whitelist the values you expect
  2. Use .to_h to ensure that if you forgot step 1, nothing will get through
  3. If you really want the raw values, don't call .permit and call .to_unsafe_hash
  4. Don't call .to_hash because that's now ambiguous
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • I think with this, I will have to use to_unsafe_hash - to_h would, as you say, only return what's permitted, but I'm in library code where I don't know what params are permitted. So anything I do to use permit would require iterating all keys anyway, which seems about as bad as to_unsafe_hash. Plus, all I'm doing is generating a URL which will ultimately become params again. It's too bad that Parameters itself can't be used for url_for, but oh well. – Hakanai Aug 11 '16 at 01:30
  • Hmm. If you're in library code, where are you getting the params? If an application is calling your library, they should call `.permit` before handing you the params, right? You can check that with `params.permitted?`. I'd be nervous about building a URL with arbitrary params, especially in library code where you don't know where they came from. – Nathan Long Aug 11 '16 at 16:14
  • The params come from a controller, but the hash in question is used for filtering and sorting, and I guess I mustn't have been keen on duplicating code to permit the full list of all filters. I'll have to come up with some way to pull the list programmatically somehow. :/ – Hakanai Aug 19 '16 at 06:59
  • I don't understand why this answer was voted up since despite providing a nice explanation it doesn't answer the question at all (OP wanted to know - as I do - who/where called .to_hash in the first place – sandre89 Apr 11 '17 at 23:26
  • 2
    @sandre89 The question says "I'm getting the following deprecation warning... (details) So what am I supposed to do to clear this?" – Nathan Long Apr 12 '17 at 13:37
  • This is a very helpful, clear, and concise answer to this question. God bless you. – a2f0 Apr 27 '19 at 01:40