为什么我在 Rails 5 的 Rails 控制台中遇到严重参数错误?

pix*_*rth 0 ruby-on-rails strong-parameters awesomeprint rails-activerecord

我想在我的控制台中调用它(ap是很棒的打印宝石):

ap Purchase.last(10)
Run Code Online (Sandbox Code Playgroud)

但我收到此错误:

ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
Run Code Online (Sandbox Code Playgroud)

它的工作原理如下:

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
}
Run Code Online (Sandbox Code Playgroud)

没有ap这样的:

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         |
Run Code Online (Sandbox Code Playgroud)

但不是有争论和ap

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

事实证明我无能为力:

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
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?

mu *_*ort 5

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
Run Code Online (Sandbox Code Playgroud)

If you were digging a Hash out of params:

some_hash = params.require(:x).permit(some_hash: ...)
Run Code Online (Sandbox Code Playgroud)

and serializing that in a model:

class M < ApplicationRecord # Or ActiveRecord::Base in the past
  serialize :h, Hash
end
#...
m.h = some_hash
Run Code Online (Sandbox Code Playgroud)

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

--- !ruby/object:ActionController::Parameters
...
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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).