tl;dr: Do not use merge!
for session hashes. Use update
instead.
Outline
Let's assume you're modifying the Rails session. For simplicity, let's also assume your session is empty when you start (same effect when there is data):
# In our example, we're in a Rack middleware
request = Rack::Request.new(env)
request.session.merge! :hello => 'Universe'
request.session
=> {}
Wat? Show archive.org snapshot
Even worse: When you inspect your request.session
like above (e.g. in a debugger shell, or just in your code) any subsequent request.session.merge!
will work! Don't stop reading yet, this is not the fix. ;)
What's going on
The culprit is the ActionController::Session::AbstractStore::SessionHash
class (session
is of that type in your Rails application, but also in Rack middleware):
class SessionHash < Hash
# ...
def update(hash)
load_for_write!
super
end
As you can see: While
merge!
Show archive.org snapshot
is the same as
update
Show archive.org snapshot
on a regular Hash
(they are not aliased), update
has been changed on SessionHash
.
SessionHash#load_for_write!
calls load!
internally which prepares some data in the session hash. The same happens when you say inspect
(which uses load_for_read!
) which is why it might seem to be working
while looking at the issue
Show archive.org snapshot
.
I'm not sure why SessionHash#merge!
is not implemented in a similar fashion, and I don't dare monkey-patching it. Be aware just not to use it -- SessionHash#update
is what you are looking for.
Note that this happened for me in a Rails 2 application's Rack middleware. While using #merge!
might work on regular Rails applications, it is only because the hash was loaded before; but you don't really have a guarantee that this happened.
On Rails 4 applications the issue is similar. Assigning Symbol keys via merge!
will not work, while String work. To avoid that, use update
on Rails 4, too.