Nested Hash Params with Named Routes in Ruby on Rails 3
I've never (as of rev 5522) been able to pass a nested hash as parameters to url_for and have it flatten it properly on the generated URL. I encountered the problem again the other day and, having had just about enough, decided to try and nip it in the bud.
The problem I was having occurred when tried to make an HTTP request to a named route such as:
people_url(:person => { :name => 'Bob', :profession => 'Developer' })
When I've encountered this problem in the past, I could never find any information from someone who's actually solved the problem. There are a few patches submitted as tickets to the Rails devs, but one seems to be a duplicate of the other, and neither seems to fix this problem on Edge, at least not for my particular use case.
The root of this particular problem lies in a method in the action_controller/routing.rb file within actionpack/actioncontroller, in the options_as_params of the RoutingSet class. The method, without comments, looks like this:
1 2 3 4 5 6 7 def options_as_params(options) options_as_params = options[:controller] ? { :action => "index" } : {} options.each do |k, value| options_as_params[k] = value.to_param end options_as_params end
value is a Hash. In that case, the to_param method simply converts the Hash instance into its String representation, which is just a pile of keys and values mashed together, as in my people_url example.
To solve this problem, I changed the method to look like this:
def options_as_params(options) options_as_params = options[:controller] ? { :action => "index" } : {} options.parameterize do |param_key, param_value| options_as_params[param_key] = param_value.to_param end options_as_params end
parameterize method is an extension I made to the Hash class that essentially flattens a hash like:
{ :person => { :name => 'Bob', :profession => 'Developer' } }
{ :'person[name]' => 'Bob', :'person[profession]' => 'Developer' }
class Hash def flatten(superkey) flattened_hash = {} self.each do |key, value| flattened_hash["#{superkey}[#{key}]".to_sym] = value end flattened_hash end def parameterize self.each do |k, value| if value.is_a?(Hash) value.flatten(k).each { |fk, fv| yield fk, fv } else yield k, value end end end end
Trackbacks
Use the following link to trackback from your own site:
http://kinderman.net/trackbacks?article_id=35

I did the same thing (1.2.3 still shows this problem). Instead mine was to do something like this
def flatten_interior_hashes_to_singles(params)
end
To avoid having to rewrite ruby code shared among developers. Cheers!
link_to 'whatever', flatten_interior_hashes_to_singles(params).merge({:page => 3})
Note that this doesn't convert them to :symbols (so it's deficient in that way).
oops I meant
def flatten_interior_hashes_to_singles(params) out = {} for key, possible_combined_poorly_value in params value = params[key] if value.class.ancestors.include? Hash for key_inner, value_inner in value out["#{key}[#{key_inner}]"] = value_inner # they had better not be 3dd or we're stuck again. Fix when necessary end else out[key] = value end end out endused like
link_to 'something', flatten_interior_hashes_to_singles(params).merge({:new_param => new_setting})or what have you.I think you should be able to achieve the same result by just overriding Hash#to_param