Search code examples
rubysinatra

Weird behavior of params hash in Sinatra


After doing a lot of debugging, I have isolated a problem but I cannot explain it. Here is a bit of Sinatra Code:

post '/modify' do

# at this point params contains the action key as I expect it.

  puts "1: action is #{params["action"]}"
  if (params["action"] == "del")
    puts "#{params}" 
    puts "delete"
  end

# At this point params still contains the "action" key as I expect it
  puts "2: action is #{params["action"]}"
  if (params["action"] == 'create') 
    puts "#{params}" 
    puts "create"

#### THE NEXT LINE IS THE PROBLEM!
    params = { :n => @number }
    redirect '/'
  end

# At this point params is nil
  puts "3: ????? #{params}"
  foo = params["action"]
  #puts "3: action is #{params["action"]}"
  if (foo == "refresh")
    puts "***: #{params}"
    redirect '/'
  end
  puts "3: #{params}"
  puts "3: action is #{params["action"]}"
end

This code as written, when params["action"] is not equal to del or create, will give an error that [] is not valid on a nil value.

I conclude that by assigning a value to params inside a conditional that IS NOT EXECUTED the params hash (which is really a method from Sinatra) becomes nil.

Even if there was something with variable scoping in Ruby that doesn't recognize a if/then as a new scope, still that line is not executed.

Can you see it?


Solution

  • You seem to confuse the method params for the local variable params.

    Let's take a look at a simplified example:

    def params
      {"action" => "some-action"}
    end
    
    # the line below refers to the method #params
    p params # {"action" => "some-action"}
    
    # the variable params is created, but not assigned
    params = {n: 1} if false
    
    # any further reference to params will use the variable
    p params # nil
    

    If you now have the question "why is the variable created?" the simple answer is: Because the documentation says so.

    The local variable is created when the parser encounters the assignment, not when the assignment occurs:

    a = 0 if false # does not assign to a
    
    p local_variables # prints [:a]
    
    p a # prints nil
    

    To solve your issue either use the #params= setter (assuming there is one) or retrieve the method result to a variable to start with, this way all references to params refer to the variable.

    # assign using the setter method
    self.params = { :n => @number }
    
    # extract the contents to a variable at the start
    # of the `post '/modify' do` block
    params = self.params # or params()