简体   繁体   中英

Accessing Hash Value in Test

I'm following a Hanami tutorial and I can't work out what's going wrong in this test:

describe Web::Controllers::Books::Create do
  let(:action) { Web::Controllers::Books::Create.new }
  let(:params) { Hash[book: { title: 'Confident Ruby', author: 'Avdi Grimm'  }] }

  it 'creates a new book' do 
    action.call(params)

    action.book.id.wont_be_nil
    action.book.title.must_equal params[:book][:title]
  end
end

rake test results in a failure because "Confident Ruby" does not equal nil.

I can puts params[:book] after the action.call , but sure enough params[:book][:title] is nil . I've tried to access title via other means but can't seem to manage it. It does seem though that params[:book][:title] should be correct.

However, when I use params[:book]['title'] , it appears to work. If I try to create a params hash in IRB, params[:book][:title] works, and params[:book]['title'] doesn't, so I remain confused.

I have upgraded to Ruby 2.3.0, but still experience the same behaviour.

params[:book].has_key?(:title) is false , params[:book].has_key?('title') is true . Cross-referencing with the documentation on how to access hash elements, I just don't get it.

What is happening here?

This is by no means a newbie question, as many experienced Rubyists struggle with this topic in various ways. The short answer is that this is not normal hash behavior, but is a somewhat known issue with Rails hash manipulation. Your irb experience with Hash is real, actual Ruby behavior, and it's consistent with your expectations.

So, the real question is, "why does Rails muck with hashes to make them misbehave?" It's a long, long series of decisions made to support quite a large number of use cases, including support for params hashes, session hashes, and ActiveRecord field names (eg mass assignment). The ultimate root of this functionality is the Rails class HashWithIndifferentAccess .

What HashWithIndifferentAccess does is provide equivalence of hash keys for symbols (eg :book , :title ) and strings (eg 'book', 'title'), so that you don't need to know which type of key is required. Hence, the indifferent access . In reality, symbol keys are internally converted to strings at the point of access. It does solve an actual problem, particularly for programmers new to Ruby and Rails. However, there have been side effects that crop up in some of the strangest places. RSpec happens to be one of those strange places, and there have been a variety of documented examples of these side effects. What appears to be happening is that the original hash gets converted to a HashWithIndifferentAccess , which is itself derived from Hash . Once you have an instance of HashWithIndifferentAccess , you can generally treat it like a Hash , but you have to be careful to not convert or merge it to another Hash (or any other derivative of Hash ). The issue is with the indifferent keys, and code that doesn't explicitly support HashWithIndifferentAccess will incidentally copy, clone, or merge the string form of the keys, losing concept of the symbol keys in the process.

The problems with HashWithIndifferentAccess are most frequently encountered with nested hashes, demonstrating the exact behavior that you're experiencing. In short, the top-level hash keys can still be accessed with symbols, while the nested hashes can only be accessed with the string keys.

You have already identified the solution to your problem, and that's to use the string keys to access the nested hash from within RSpec; this answer simply serves to tie together the reasons why. It's probably a good idea, and less confusing to boot, to define your RSpec hash keys as strings, rather than as symbols, so that you know that string keys are the correct keys to use.

For more information, you might be interested in reading some of these articles:

I'm sorry, but that was a bug that is now fixed. All the params can be accessed like:

params[:book][:title]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM