Rails parameter parsing can be hard to understand, but it goes beyond that.
Just understanding the “params” object in your controller actions is a little tricky. Ever notice how params[:bob] and params[“bob”] both work? The magic type behind that is called HashWithIndifferentAccess. Yeah, it’s a mouthful. I wish I were making that up, but no.
There’s a lot of interesting code in Rails with interesting Ruby tricks - so let’s de-magic the params object, find some bugs with it and get better at Ruby, all at once.
The Source
I like going straight to the source, so here is the exact version of the file I’m looking at in ActiveSupport. You can read here or follow along on GitHub.
It’s a nice readable 176 lines.
What’s It Do?
In Rails you can say params[“people”] or params[:people], and either way you get the same thing back. Simple enough, right? Well, mostly.
You can see on line 7 that it inherits from Hash — Ruby is convenient that way.
The biggest magic is on line 159, in fact, in convert_key(). It just converts any key it sees to a string.
So HashWithIndifferentAccess is just a regular hash, but any symbol key is converted to a string. If you put in a key that was, say, nil, nothing would change. It only converts symbols, not NilClass (which is nil’s type.)
Fun Ruby Tricks
This class shows one fun ruby trick - aliasing and replacing a method. The old hash update and assignment are renamed to regularupdate and regularwriter.
You can also see just how many methods you need to override to make your own hash… Default(), brackets, assignment, update(), key(), fetch(), dup(), merge() and many more. Making your own variant type is a bad idea unless you spend some quality time with the core language documentation and see what actually changes.
But if you do, it can be really useful, like the Rails params object!
What Can Go Wrong?
There are some oddities, like stringifykeys! and symbolizekeys! – what do you do for a HashWithIndifferentAccess? They do nothing… Fair enough.
But right next to convertkey is convertvalue()… And it’s interesting - if you assign an array to the hash, you can see that they do a .map! and change the array before assigning it.
That means if you said:
my_array = [ :a, :b, { :c => :d } ]
params[:item] = my_array
Your array will actually change! That hash inside will suddenly have indifferent access and it will point to a whole different object!
(Why? I spent awhile with git log trying to answer that. Looks like it’s because just mapping the Array won’t give you the right subclass, so they rewrite the innards instead! Map into an unknown Array subclass is an unsolvable problem in Ruby, actually…)
Up To Date?
Nothing stays the same forever. In Rails, not much stays the same for long. So here is a link to the latest HashWithIndifferentAccess in GitHub, whenever you go look for it.
Enjoy Getting Your Hands Dirty?
Do you enjoy code spelunking to learn about Ruby metaprogramming and the internals of Rails? I wrote a book about that. The first couple of chapters are free and awesome – you can download them in exchange for your email address.
Comments