meso_2600 meso_2600 - 4 months ago 10
Ruby Question

Reading and writing Sinatra params using symbols, e.g. params[:id]

My form receives data via POST. When I do

puts params
I can see:

{"id" => "123", "id2" => "456"}


now the commands:

puts params['id'] # => 123
puts params[:id] # => 123

params['id'] = '999'
puts params # => {"id" => "999", "id2" => "456"}


but when I do:

params[:id] = '888'
puts params


I get

{"id" => "999", "id2" => "456", :id => "888"}


In IRB it works fine:

params
# => {"id2"=>"2", "id"=>"1"}

params[:id]
# => nil

params['id']
# => "1"


Why can I read the value using
:id
, but not set the value using that?

Answer

Hashes in Ruby allow arbitrary objects to be used as keys. As strings (e.g. "id") and symbols (e.g. :id) are separate types of objects, a hash may have as a key both a string and symbol with the same visual contents without conflict:

irb(main):001:0> { :a=>1, "a"=>2 }
#=> {:a=>1, "a"=>2}

This is distinctly different from JavaScript, where the keys for objects are always strings.

Because web parameters (whether via GET or POST) are always strings, Sinatra has a 'convenience' that allows you to ask for a parameter using a symbol and it will convert it to a string before looking for the associated value. It does this by using a custom default_proc that calls to_s when looking for a value that does not exist.

Here's the current implementation:

def indifferent_hash
  Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
end

However, it does not provide a custom implementation for the []=(key, val) method, and thus you can set a symbol instead of the string.