0

I want to call this in my console (ap is the awesome print gem):

ap Purchase.last(10)

but I get this error:

ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash

It works like this:

irb(main):020:0> ap Purchase.last
#<Purchase:0x00007f86b792a320> {
                              :id => 28445,
                         :user_id => 10177,
                      :product_id => nil,
                    :product_type => nil,
                           :price => 9.0,
                    :gateway_code => nil,
                     :gateway_msg => nil,
                :gateway_response => nil,
                      :created_at => Fri, 18 May 2018 22:20:10 UTC +00:00,
                      :updated_at => Fri, 18 May 2018 22:20:10 UTC +00:00,
                  :checkout_total => 9.0,
                      :successful => true,
                         :cart_id => 17242,
                   :report_errors => nil,
    :transacted_value_of_products => 9.0,
            :comp_credits_applied => 0.0
}

And without ap like this:

irb(main):022:0> Purchase.last(10)
D, [2018-05-25T20:58:54.692575 #70552] DEBUG -- :   Purchase Load (0.5ms)  SELECT  "purchases".* FROM "purchases" ORDER BY "purchases"."id" DESC LIMIT $1  [["LIMIT", 10]]
+-------+---------+------------+-------------+-------+-------------+-------------+-------------+-------------+--------------+-------------+------------+---------+-------------+-------------+-------------+
| id    | user_id | product_id | product_... | price | gateway_... | gateway_msg | gateway_... | created_at  | updated_at   | checkout... | successful | cart_id | report_e... | transact... | comp_cre... |
+-------+---------+------------+-------------+-------+-------------+-------------+-------------+-------------+--------------+-------------+------------+---------+-------------+-------------+-------------+
| 28436 | 10471   |            |             | 5.0   |             | Completed   | {"mc_gro... | 2018-05-... | 2018-05-1... | 5.0         | true       | 17228   | {}          | 5.0         | 0.0         |
| 28437 | 9754    |            |             | 1.99  |             | Completed   | {"mc_gro... | 2018-05-... | 2018-05-1... | 2.48        | true       | 15273   | {}          | 1.99        | 0.0         |
| 28438 | 10472   |            |             | 9.0   |             |             | {\n  "id... | 2018-05-... | 2018-05-1... | 9.0         | true       | 17231   | {}          | 9.0         | 0.0         |
| 28439 | 10348   |            |             | 9.0   |             |             |             | 2018-05-... | 2018-05-1... | 9.0         | true       | 17235   |             | 9.0         | 0.0         |

But not with an argument and ap

irb(main):021:0> ap Purchase.last(3)
ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
    from (irb):21

It turns out I can't do much of anything:

irb(main):023:0> ap Purchase.find(28444)
ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
    from (irb):23

irb(main):024:0> ap Purchase.find(28444).gateway_response
ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
    from (irb):24

What's going on?

mu is too short
  • 426,620
  • 70
  • 833
  • 800
pixelearth
  • 13,674
  • 10
  • 62
  • 110
  • What is `gateway_response` in the database? Serialized `params` perhaps? – mu is too short May 26 '18 at 01:45
  • The reason I ask because `ActionController::Parameters` used to inherit from `Hash` so you could `serialize :stuff, Hash`, put `params` into `stuff`, and end up with end up with a blob of YAML in your database that is marked as an `ActionController::Parameters` instance. But `ActionController::Parameters` no longer inherits from `Hash` and its `to_h` method raises an exception. The result is that changing Rails versions can leave your database littered with broken data if you haven't been very careful. – mu is too short May 26 '18 at 03:51
  • @muistooshort This is it. I not all of my purchases have a gateway response, since purchases can be made in different ways. Some gateway reponses come from Authorize.net (old ones) some from Stripe, and some from Paypal. Based on my code those serialized responses have different classes, and as you say it looks like the ones come from Paypal are serialized as ActionController::Parameters. I've recently upgraded my app from 4.2 to 5.2 and it looks like I need to clean my db. Feel free to post your comment as an answer and I'll accept. – pixelearth May 26 '18 at 15:00

1 Answers1

4

What's Happening and Why

ActionController::Parameters (which is what params works with in controllers) used to inherit from HashWithIndifferentAccess which inherits from Hash. So ActionController::Parameters < Hash used to be true as would something like:

params.require(:x).permit(some_hash: %i[key1 key2]).is_a? Hash

If you were digging a Hash out of params:

some_hash = params.require(:x).permit(some_hash: ...)

and serializing that in a model:

class M < ApplicationRecord # Or ActiveRecord::Base in the past
  serialize :h, Hash
end
#...
m.h = some_hash

you could end up with some YAML like this in your database:

--- !ruby/object:ActionController::Parameters
...

rather than the expected plain YAMLized hash.

But then Rails5 comes along and ActionController::Parameters no longer inherits from Hash:

  • Make ActionController::Parameters no longer inherits from HashWithIndifferentAccess.

and calling to_h or to_hash on an ActionController::Parameters now raises an exception.

If you upgrade your code and try to load a model with serialized data in it:

serialize :h, Hash

then the model will load the text from h, parse the YAML to get an ActionController::Parameters instance, and call to_h on it to make sure it has a Hash, and you get an exception.

What To Do About It

There are a couple things you need to do:

  1. Fix your controllers to make sure they get real hashes out of params.
  2. Fix your data so that you have serialized hashes rather than ActionController::Parameters instances.

Fixing the controllers is a simple matter of calling to_unsafe_h on the parameters that aren't yet real hashes.

Fixing the data is uglier. I'd probably go through the tables using the low level database interface (i.e. no ActiveRecord anywhere), read the YAML out from each row, YAML.load it, convert it to a hash by calling to_unsafe_h on it, and then writing back the_real_hash.to_yaml text. You could use a like '--- !ruby/object:ActionController::Parameters%' filter in the WHERE clause to only deal with the broken rows.

I'd also strongly recommend that you stop using serialize while you're there. serialize is a bit of a kludge and there's no sane way to work with YAML inside the database; there's also little need for it now that both PostgreSQL and MySQL have native JSON support (but I'm not sure how well ActiveRecord supports MySQL's JSON).

mu is too short
  • 426,620
  • 70
  • 833
  • 800