Ruby Hash#fetch and Javascript
In a previous post I mentioned a case in which I had a Haml template in a Rails app with Javascript in it. The template was rendering Ruby values into what would become a JSON object (Javascript Hash) in the browser. In that particular case, a method call on a Null Object was returning nil and breaking the Javascript.
Well, today I encountered another variation of this problem. In this case, I had a Ruby Hash feeding values into the JSON object. It looked like this:
var options = {
"gender": "#{search_params['gender']}",
"age": "#{current_user.age}",
"language": "#{search_params['language']}",
"query": "#{search_params['query']}",
"properties": "#{search_params['properties']}"
};
Two of these fields on the ruby hash are empty: language and properties.
Notice that this isn’t a problem for most of these fields, but for one it is:
var options = {
"gender": "M",
"age": "27",
"language": "",
"query": "specials",
"properties":
};
See that? The first four fields feed into double quotes as strings. If they are empty, they are empty. It may cause an issue somewhere down the line, but the Javascript itself is valid.
The properties field, on the other hand is expecting a JSON object. That
missing bit will cause Javascript to blow up once this loads in the browser.
What’s the solution here? In this case, I turned to Hash#fetch rather than
the [] accessor. What’s the difference? There are two main differences
between the bracket accessor and the fetch method. First,
my_hash[:some_missing_key] will return nil if the key is not found in the
hash. If my_hash.fetch(:some_missing_key) can’t find a key, it will raise a
KeyError exception.
That’s not really what we are looking for, which brings me to the second
difference. Hash#fetch takes an optional second argument which specifies a
default value. So if we call my_hash.fetch(:some_missing_key, 'tacos') and
the key is not found in the hash, it will return 'tacos' instead of nil.
That’s exactly what I needed. I returned an empty JSON object {} as the
default.
var options = {
"gender": "#{search_params['gender']}",
"age": "#{current_user.age}",
"language": "#{search_params['language']}",
"query": "#{search_params['query']}",
"properties": #{search_params.fetch('properties', {})}
};
This degraded nicely in the cases in which I had no properties hash in my
search_params hash.
var options = {
"gender": "M",
"age": "27",
"language": "",
"query": "specials",
"properties": {}
};
No more broken Javascript.