Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symbols used as Hash keys get converted to Strings when serialized

When I assign an Array or Hash to an attribute of a Mongo document, it gets properly serialized except for Symbols when they are used as Hash keys. Simple example:

irb>MyMongoModel.create :some_attr => {:a => [:b,:c]} 
=> #<MyMongoModel _id: 4d861c34c865a1f06a000001, some_attr: {:a=>[:b, :c]}> 

irb>MyMongoModel.last 
=> #<MyMongoModel _id: 4d861c34c865a1f06a000001, some_attr: {"a"=>[:b, :c]}> 

Please, note that some_attr is retrieved as {"a"=>[:b, :c]}, not as {:a=>[:b, :c]}

This also happens for nested Hashes (e.g., inside of Arrays or other Hashes). Is there a way to preserve Symbols in such cases?

Solution

I'm using YAML to manually serialize some_attr - YAML.dump (or Object#to_yaml) before storing, and YAML::load after reading the attribute. YAML preserves the serialized object better. ActiveRecord is using YAML to implement its serialize class method on ActiveRecord::Base.

like image 399
mxgrn Avatar asked Mar 20 '11 18:03

mxgrn


People also ask

Can hashes have symbols?

Symbols are most commonly used as placeholders in Hashes. We have used symbols already in Rails, for example the Rails params hash associates any the values of any parameters passed in by the user (from a form or in the url) with a symbol representing the name of that value.

Why can you safely use a string as a hash key even though a string is mutable?

According to the specification, strings that are used as a key to a hash are duplicated and frozen. Other mutable objects do not seem to have such special consideration. For example, with an array key, the following is possible. On the other hand, a similar thing cannot be done with a string key.

How do I convert a symbol to a string in Ruby?

Converting between symbols to strings is easy - use the . to_s method. Converting string to symbols is equally easy - use the . to_sym method.


1 Answers

More than likely this has to do with the ORM you are using to provide the persistance layer for the model. You can probably wrap some_attr with a method that returns it in the form of a HashWithIndifferentAccess which you can then access with either strings or arrays. Since you are using Rails, this functionality can be activated by calling the with_indifferent_access method on the Hash object. (If you have an array of Hash objects, you'll need to call it on each one of course) The method will return the same hash, but then symbol lookups will work.

From your code:

new_hash = MyMongoModel.last.some_attr.with_indifferent_access
new_hash[:a] # Will return the same as new_hash['a'] 

Hope this helps!

like image 167
ctcherry Avatar answered Nov 04 '22 00:11

ctcherry