views:

1162

answers:

2

I need to create a pretty route for an action that is expecting ugly nested parameter names (this is set and not something I can change at this time).

So instead of

http://domain.com/programs/search?program[type]=leader&program[leader_id]=12345

I want

http://domain.com/programs/search/leader/12345

The problem is that Rails route definitions won't handle complex parameter names.

map.programs 'programs/:program[type]/:program[leader_id]', :controller=>..., :action=>...

Again, I'm not able to change the controller - it's set up to expect these parameter names based on a set of pre-existing search forms. I just want to create a more readable route for some pre-defined searches.

Surely there must be a way to create a route that passes input data to parameters with names that are more complex than single-word-downcase-alpha.

Thanks

+1  A: 

From what I gather of your post, you want a url like

http://domain.com/programs/search/leader/12345

to produce a params hash like

:params => { programs => {:type => "search", :leader_id => "12345"}}

I don't think this can be done. So far as I can tell you cannot produce a nested hash from a named_route. There are workarounds, but each of them require modifying the controller in some way.

I feel this is the least intrusive solution: First set up a simpler named route,

map.programs 'programs/:program_type/:program_leader_id',
  :controller=>..., :action=>...

I would then want to make a link_to or url_for wrapper available as a helper so it could be passed a program object and make the correct url.

def link_to_programs name, prog
  link_to name, programs_url(prog, 
     :programs =>{:program_type => prog[:type], :prog[:program_leader_id]})
end

Then I would need some way of tricking the controller into thinking it was passed a deeper params hash.

In programs_controller:

def fix_bad_params
  params.merge!({:programs => {:type => params[:program_type],
    :leader_id => params[:program_leader_id])
end

before_filter :fix_bad_params, :only => :action_in_named_route

NB. The link_to wrapper I listed is incomplete. I used it for a simple demonstration. If you you plan on passing other options to it or even to use it in the rest way of doing things (ie: link_to (@project), you will need to rewrite it. Look at the link_to source to understand the best way to do it.

EmFi
Well, now that you point out the nested-hash example, I'm wondering if it can be done with nested routes.Otherwise your example in the controller looks like a reasonable option. Though ideally I'd find a similar solution that could be handled entirely in the route definition and stay completely out of the controller. Something like: map.programs 'programs/:type/:id', :remap=>{:type=>'programs[type]', :id=>'programs[leader_id]'}Fortunately, I don't have to worry about route helpers and wrappers. This particular case is for a customer to embed readable links in their static html.
wbr
I only suggested the wrapper to allow you to use common rails syntax to dynamically generate your links. Nested routes won't work because they will still only create a shallow params hash. As for a :remap option. It's possible, but you'd need to modify the code that handles routes.rb. Rails source code can be difficult to decipher.
EmFi
+1  A: 

The built-in routing mechanisms won't do quite what you're looking for, I'm afraid. You certainly could monkey-patch the routing mechanism, but that seems like a lot of work and could easily cause other bugs to appear.

Have you thought about a before-filter in conjunction with a prettier route? For example:

# in config/routes.rb:
map.connect '/programs/:program_type/leader/:program_leader_id', ...

# in config/initializers/translate_pretty_program_routes.rb
module TranslatePrettyProgramRoutes
  def self.included(base)
    base.send :include, TranslatePrettyProgramRoutes::InstanceMethods
    base.send :prepend_before_filter, :translate_pretty_program_route_params
  end

  module InstanceMethods
    def translate_pretty_program_route_params
      params[:program] ||= {}
      params[:program][:type] ||= params[:program_type]
      params[:program][:leader_id] ||= params[:program_leader_id]
    end
  end
end

# in app/controllers/programs_controller.rb:
class ProgramsController < ApplicationController
  include TranslatePrettyProgramRoutes
end

The meta-programming in TranslatePrettyProgramRoutes is to get around problems with class-reloading in development mode. If it weren't for class-reloading, you could just define the method and add it as a before_filter right there in the initializer.

James A. Rosen