64°F

Aaron Parecki

  • Articles
  • Notes
  • Photos
  • Ruby Magic, or why `params` is empty even when I don't set it

    January 8, 2017

    I learned a very subtle Ruby trick today.

    The Ruby parser will create local variables for every variable that might be set in your code before any of it is run.

    irb(main):001:0> if false; x = 1; end
    => nil
    irb(main):002:0> x.inspect
    => "nil"
    irb(main):003:0>
    

    Compare with just checking for x:

    irb(main):001:0> x
    NameError: undefined local variable or method `x' for main:Object
    from (irb):1
    from /Users/aaronpk/.rubies/ruby-2.1.3/bin/irb:11:in `<main>'

    Just to confirm what's happening:

    irb(main):001:0> local_variables
    => [:_]
    irb(main):002:0> if false; x = 1; end
    => nil
    irb(main):003:0> local_variables
    => [:x, :_]
    irb(main):004:0> x.inspect
    => "nil"
    irb(main):005:0>

    This may not seem particularly unusual at first, but has some surprising results when combined with, for example, Sinatra. Imagine you have this code that attempts to accept both a form-encoded and JSON post body.

    post '/example' do
      if request.content_type.start_with? "application/json"
        begin
          params = JSON.parse(request.env["rack.input"].read)
        rescue
          return {error: "Error parsing JSON."}.to_json
        end
      end
    
      # etc etc
      # but params is always nil, even for form-encoded requests!
    end

    What's wrong with this picture? Well, the Ruby interpreter sees params = in the code and allocates a local variable. At that point, the hash that Sinatra sets isn't accessible from inside your block, so params will be nil when you try to use it!

    The trick is to avoid setting params in the first place.

    get '/example/:id' do
      if request.content_type == "application/json"
        begin
          payload = JSON.parse(request.env["rack.input"].read)
        rescue
          return {error: "Error parsing JSON."}.to_json
        end
      else
        payload = params
      end
    
      # etc etc
      # now you can use `payload` instead of params
    end

    Thanks @donpdonp for the hint!

    Portland, Oregon
    Sun, Jan 8, 2017 2:47pm -08:00 #ruby #sinatra
    1 like 1 repost
    • Greg
    • Greg
Posted in /articles using quill.p3k.io

Hi, I'm Aaron Parecki, Director of Identity Standards at Okta, and co-founder of IndieWebCamp. I maintain oauth.net, write and consult about OAuth, and participate in the OAuth Working Group at the IETF. I also help people learn about video production and livestreaming. (detailed bio)

I've been tracking my location since 2008 and I wrote 100 songs in 100 days. I've spoken at conferences around the world about owning your data, OAuth, quantified self, and explained why R is a vowel. Read more.

  • Director of Identity Standards at Okta
  • IndieWebCamp Founder
  • OAuth WG Editor
  • OpenID Board Member

  • 🎥 YouTube Tutorials and Reviews
  • 🏠 We're building a triplex!
  • ⭐️ Life Stack
  • ⚙️ Home Automation
  • All
  • Articles
  • Bookmarks
  • Notes
  • Photos
  • Replies
  • Reviews
  • Trips
  • Videos
  • Contact
© 1999-2025 by Aaron Parecki. Powered by p3k. This site supports Webmention.
Except where otherwise noted, text content on this site is licensed under a Creative Commons Attribution 3.0 License.
IndieWebCamp Microformats Webmention W3C HTML5 Creative Commons
WeChat ID
aaronpk_tv