Gerando rotas com parâmetros dinâmicos no Rails de modo fácil

Posted by Rodrigo Panachi on julho 02, 2010

A API de rotas do Rails simplifica consideravelmente o desenvolvimento fornecendo um padrão de geração e utilização de URLs para toda aplicação. Porém algumas necessidades especificas e relativamente simples podem gerar dores de cabeça se forem implementadas incorretamente.

Um caso bastante comum são URLs compostas que sempre apontam para um mesmo recurso. Por exemplo, um blog que possua rotas para seus posts no formato /posts/autor/categoria/permalink provavelmente terá uma rota mapeada como map.post "posts/:author/:category/:permalink" gerando automaticamente os helpers post_path e post_url.

Muito bom, porém para usufruirmos desta facilidade precisamos fornecer os valores dos parâmetros dinâmicos nos controllers e views:

post_path(:author => @post.author, :category => @post.category, :permalink => @post.permalink)

E mesmo que você forneça os parâmetros em um array, vai dar muito trabalho além de deixar muito código repetido pela aplicação.

Entendi! Mas como resolvo este problema?

Para este caso, apenas implementar o método to_param do model não vai servir. Uma solução seria reescrever o método post_path (que é gerado automaticamente) no respectivo helper (posts_helper.rb):

def post_path(post, options = {})
  super(post, :author => post.author, :category => post.category, :permalink => post.permalink)
end

Outra solução seria sobrescrever o método default_url_options no controller para retorna os parâmetros padrões da rota:

def default_url_options(options = {})
  options.merge!(:author => @post.author, :category => @post.category, :permalink => @post.permalink) if options[:action] == "show"
end

A má notícia é que você terá que fazer isto para todos seus controllers e respectivos models.

A terceira solução (e mais elegante) é padronizar a maneira com que os parâmetros opcionais da rota são obtidos a partir do controller e seu respectivo model. Basta adicionar os seguintes métodos no seu ApplicationController:

def default_url_options(options = {})
  if (model = controller_model(options))
    dynamic_route_params(options).each do |param|
      options[param] = model.send(param) if model.respond_to?(param)
    end
  end
  options
end
 
def controller_model(options = {})
  clazz = (options[:controller].singularize.camelize.constantize rescue ActiveRecord)
  options.select { |key, value| value.is_a?(clazz) }.first.second
rescue
  nil
end 
 
def dynamic_route_params(options = {})
  returning [] do |dynamic_params|
    matched_routes = ActionController::Routing::Routes.routes.select do |route|
      route.matches_controller_and_action?(options[:controller], options[:action])
    end
    dynamic_segments = matched_routes.map(&:segments).flatten.each do |segment|
      dynamic_params << segment.key if segment.is_a?(ActionController::Routing::DynamicSegment)
    end
  end
end

Assim, os valores defaults dos parâmetros da rota serão obtidos diretamente do model. Caso queiram contribuir, este código está disponível no github.

Referências
Rails Routing from the Inside Out
Rails Guides: Routing

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

Comments