Pop quiz: your browsing some ruby code (which you often do in your spare time) and see the following:

    party_like[:its] = "1999"
    puts party_like[:its]

What will be put to the console when these lines of ruby execute? You might be saying that since party_like seems to be some kind of Hash or a Hash-like-thingie, you should get...

    #=> 1999

At least 99% of the time you'd be right, unless the code is actually the cookies method in a Rails Controller:

    cookies[:its] = "1999"
    puts cookies[:its]

    #=> nil

BUWAHHHHHH? Cookies isn't a Hash, although the cookies method does return a CookieJar object, which extends Hash. CookieJar mangles the expected Hash methods of [] and []= with these gotchas:

  • The CookieJar object does not represent one set of cookies, it represents 2 sets: the incoming cookies from the browser, and the outgoing cookies that will be sent back to the client
  • cookies[:key]gets you the incoming cookies from the client
  • cookies[:key]= value sets outgoing cookies that will be sent back to the client
  • ... all of which mean that cookies[:its] = "1999" will not allow you to retrieve that value with cookies[:its].

But wait, it gets more confusing. In a test you do the following:

    def test_cookies
      @request.cookies[:its] = "1999"
       get :index
    end

And in your controller:

    class CookiesController < ApplicationController
      def index
        puts cookies[:its]           #=> nil
        puts cookies.inspect    #=> {:its=>"1999"}
        render :text => ""
      end
    end

The cookies.inspect confusingly returns {:its=>"1999"}, which seems impossible: I put the value in and get nil when I ask for it, but if I inspect the object to see what's up, it's there! Right? Not, not really. The incoming cookies are displayed, not the outgoing. In a typical test-fail-debug scenario, this is a head-scratcher.

For the love of SANITY: do not override methods on core objects to do completely unexpected stuff, and especially don't mangle the expected functionality of operators! I realize that with languages like ruby you can play God, overriding and changing the implementation of anything you please, but some things are sacred: +,-, [], []=, etc. + should add stuff. - should remove stuff. And objects that have [] and []= should let you get stuff with [key] and set them with [key]=value.


1 Comments (from old blog):

At 10/13/2006 5:33 PM, Joshua Jarman said…

I have a slightly different take on the cookies object. It is a hash, with a wormhole inside connecting it to the last and next browser requests. Essentially it doesn't exist in the now. ;-)

Cookies are decoupled. When you set the value it starts a chain of events. The server needs to send the cookie to the browser and the browser has to return the cookie. Then you can read the value.

    cookies[:its] = "1999"

    *server response
    *browser request

      p cookies[:its]
      #=> "1999"

You can use "app" in tests (or the console) to simulate user sessions and to send and receive cookies, which will allow you to get your values back out and check them.

Example:
http://clarkware.com/cgi/blosxom/2006/04/04

So maybe more twisted and misunderstood then broken.

Cheers,
Josh

4 Responses to “Rails Cookies Mangles the Hash Interface”

  1. Jeff Smick Says:

    Oh this is such a stupid bug. Here’s the deal though:

    When rails initialized cookies in the controller it creates a CookieJar and fills it with the cookies in ‘request.cookies’. However it doesn’t convert that hash to a HashWithIndifferentAccess. No, instead it just loads your cookies into a regular ol’ Hash. Then when you try and get them out, Rails uses this little gem to pull them from the Hash:

    def [](name)
      @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
    end
    

    That’s right folks, it’s converting your key to a string before asking the cookies hash for that key. Now when you’re not testing, this is all fine and dandy, cookie keys from the request are strings. However when one does test one tends to use Symbols (like any good Ruby programmer should). OOPS! Now the Symbol is the key, but Rails is asking for a String. BAD RAILS, BAD!

    So, resolution? use strings for keys when testing with cookies.

  2. Cornell Says:

    Just wanna let you know your post saved a hell of lot time! I’m semi-new to Rails and was always stumbling upon this cookies problem.

    After debugging my code like forever, I found your post on google and it brought me just on the right track: simply start sailing around that evil monster.

    As far as I can see, there’s no cool standard way of handling the cookie jar, is there?

  3. Alex N Says:

    I agree with this complete insanity. In addition, request.cookies is different from cookies:

    http://www.quarkruby.com/2007/10/21/sessions-and-cookies-in-ruby-on-rails#scinrails

    I’m still not completely clear how.

  4. joe Says:

    @Cornell and @Alex – Glad to help!

Leave a Reply