Unwanted symbol to string conversion of hash key
Asked Answered
P

6

16

When I assign in my controller

@my_hash = { :my_key => :my_value }

and test that controller by doing

get 'index'
assigns(:my_hash).should == { :my_key => :my_value }

then I get the following error message:

expected: {:my_key=>:my_value},
got: {"my_key"=>:my_value} (using ==)

Why does this automatic symbol to string conversion happen? Why does it affect the key of the hash?

Premundane answered 3/12, 2010 at 17:33 Comment(1)
You really should set your Gravatar because of your awesome username.Mysticism
M
8

It may end up as a HashWithIndifferentAccess if Rails somehow gets ahold of it, and that uses string keys internally. You might want to verify the class is the same:

assert_equal Hash, assigns(:my_hash).class

Parameters are always processed as the indifferent access kind of hash so you can retrieve using either string or symbol. If you're assigning this to your params hash on the get or post call, or you might be getting converted.

Another thing you can do is freeze it and see if anyone attempts to modify it because that should throw an exception:

@my_hash = { :my_key => :my_value }.freeze
Mysticism answered 3/12, 2010 at 18:8 Comment(4)
It's indeed a HashWithIndifferentAccess. I wonder a bit why Rails does convert my standard hash into that one. I am not assigning it to anything else than mentioned above (and especially not to params). Do you maybe also know why Rails does that conversion between the controller and the view (as mentioned no params are used in the above example).Premundane
Yes, this seems a bit silly why it is being converted to a HashWithIndifferentAccessCalix
Rails automatically converts incoming parameters to the indifferent access hash so that you don't need to concern yourself with strings or symbols when referencing it. params[:foo] and params['foo'] end up being equivalent. This is probably, in part, to help PHP, Perl and Python developers that aren't used to symbol keys, and avoid having to remember what the keys are stored as.Mysticism
I don't think it's Rails, but Rspec that's actually causing the issue. I'm having the same problem. I'll try to post an answer below, since this question came up when I searched for my problem, even though it's 5 years old! ;) This article explains more: ryanoglesby08.github.io/blog/2012/12/26/…Brennabrennan
P
10

You might try calling "stringify_keys":

assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
Pogge answered 10/12, 2010 at 19:19 Comment(1)
Solved my problem.. do you know any method working with nested hashes?Unavoidable
M
8

It may end up as a HashWithIndifferentAccess if Rails somehow gets ahold of it, and that uses string keys internally. You might want to verify the class is the same:

assert_equal Hash, assigns(:my_hash).class

Parameters are always processed as the indifferent access kind of hash so you can retrieve using either string or symbol. If you're assigning this to your params hash on the get or post call, or you might be getting converted.

Another thing you can do is freeze it and see if anyone attempts to modify it because that should throw an exception:

@my_hash = { :my_key => :my_value }.freeze
Mysticism answered 3/12, 2010 at 18:8 Comment(4)
It's indeed a HashWithIndifferentAccess. I wonder a bit why Rails does convert my standard hash into that one. I am not assigning it to anything else than mentioned above (and especially not to params). Do you maybe also know why Rails does that conversion between the controller and the view (as mentioned no params are used in the above example).Premundane
Yes, this seems a bit silly why it is being converted to a HashWithIndifferentAccessCalix
Rails automatically converts incoming parameters to the indifferent access hash so that you don't need to concern yourself with strings or symbols when referencing it. params[:foo] and params['foo'] end up being equivalent. This is probably, in part, to help PHP, Perl and Python developers that aren't used to symbol keys, and avoid having to remember what the keys are stored as.Mysticism
I don't think it's Rails, but Rspec that's actually causing the issue. I'm having the same problem. I'll try to post an answer below, since this question came up when I searched for my problem, even though it's 5 years old! ;) This article explains more: ryanoglesby08.github.io/blog/2012/12/26/…Brennabrennan
B
1

AHA! This is happening not because of Rails, per se, but because of Rspec.

I had the same problem testing the value of a Hashie::Mash in a controller spec (but it applies to anything that quacks like a Hash)

Specifically, in a controller spec, when you call assigns to access the instance variables set in the controller action, it's not returning exactly the instance variable you set, but rather, a copy of the variable that Rspec stores as a member of a HashWithIndifferentAccess (containing all the assigned instance variables). Unfortunately, when you stick a Hash (or anything that inherits from Hash) into a HashWithIndifferentAccess, it is automatically converted to an instance of that same, oh-so-convenient but not-quite-accurate class :)

The easiest work-around is to avoid the conversion by accessing the variable directly, before it's converted "for your convenience", using: controller.view_assigns['variable_name'] (note: the key here must be a string, not a symbol)

So the test in the original post should pass if it were changed to:

get 'index'
controller.view_assigns['my_hash'].should == { :my_key => :my_value }

(of course, .should is no longer supported in new versions of RSpec, but just for comparison I kept it the same)

See this article for further explanation: http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html

Brennabrennan answered 21/7, 2015 at 22:7 Comment(2)
The link 404s now. :(Agnosia
If someone else is still looking for the link posted in the answer, ryanogles.by/rails/hashie/rspec/testing/2012/12/26/…Prismatoid
I
1

I know this is old, but if you are upgrading from Rails-3 to 4, your controller tests may still have places where Hash with symbol keys was used but compared with the stringified version, just to prevent the wrong expectation.

Rails-4 has fixed this issue: https://github.com/rails/rails/pull/5082 . I suggest updating your tests to have expectations against the actual keys.

In Rails-3 the assigns method converts your @my_hash to HashWithIndifferentAccess that stringifies all the keys -

def assigns(key = nil)
  assigns = @controller.view_assigns.with_indifferent_access
  key.nil? ? assigns : assigns[key]
end

https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L10

Rails-4 updated it to return the original keys -

def assigns(key = nil)
  assigns = {}.with_indifferent_access
  @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
  key.nil? ? assigns : assigns[key]
end

https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L11

Impassible answered 8/5, 2019 at 7:20 Comment(0)
C
0

You can also pass your Hash object to the initializer of HashWithIndifferentAccess.

Calix answered 8/8, 2011 at 23:2 Comment(0)
D
0

You can use HashWithIndifferentAccess.new as Hash init:

Thor::CoreExt::HashWithIndifferentAccess.new( to: '[email protected]', from: '[email protected]')
Dambro answered 19/7, 2014 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.