Roteamento Rails de fora para dentro – parte 3
3.8 Recursos aninhados
É comum ter recursos que são logicamente filhos de outros recursos. Por exemplo, suponha que a sua aplicação inclua os seguintes modelos:
class Magazine < ActiveRecord::Base has_many :ads end class Ad < ActiveRecord::Base belongs_to :magazine end Cada ad é logicamente subordinado a uma magazine. Rotas aninhadas permite que você captura o relacionamento no seu roteamento. Neste caso, você deve incluir nas suas rotas a declaração:
map.resources :magazines do |magazine| magazine.resources :ads end Além dessas rotas para magazines, esta declaração criará rotas para ads, cada um da qual exige a especificação de uma magazine na URL:
| verbo HTTP | URL | controlador | ação | usado por |
|---|---|---|---|---|
| GET | /magazines/1/ads | Ads | index | mostra a lista de todas as ads para uma magazine específica |
| GET | /magazines/1/ads/new | Ads | new | retorna um formulário HTML para criação de um novo ad pertencente a um magazine específico |
| POST | /magazines/1/ads | Ads | create | cria um novo ad pertencente a uma magazine específica |
| GET | /magazines/1/ads/1 | Ads | show | mostra um específico ad pertencente a uma magazine específica |
| GET | /magazines/1/ads/1/edit | Ads | edit | retorna um formulário HTML para edição de um ad pertencente a uma magazine específica |
| PUT | /magazines/1/ads/1 | Ads | update | atualiza um específico ad pertencente a uma magazine específica |
| DELETE | /magazines/1/ads/1 | Ads | destroy | apaga um ad específico pertencente a um magazine específico |
Além disso criará helpers de roteamento como magazine_ads_url e edit_magazine_ad_path.
3.8.1 Usando :name_prefix
A opção :name_prefix sobrescreve um prefixo automaticamente gerado nos helpers das rotas aninhadas. Por exemplo,
map.resources :magazines do |magazine| magazine.resources :ads, :name_prefix => 'periodical' end Isso irá criar helpers de roteamento como periodical_ads_url e periodical_edit_ad_path. Você pode mesmo utilizar :name_prefix para esconder o prefixo completamente:
map.resources :magazines do |magazine| magazine.resources :ads, :name_prefix => nil end Isso irá criar helpers de roteamento como ads_url e edit_ad_path. Note que continua exigindo que você forneça um article id:
ads_url(@magazine) edit_ad_path(@magazine, @ad) 3.8.2 Usando :has_one e :has_many
As opções :has_one e :has_many fornece uma notação sucinta para simples rotas aninhadas. Use :has_one para aninhar um único recurso, ou :has_many para aninhar um recurso no plural:
map.resources :photos, :has_one => :photographer, :has_many => [:publications, :versions] Isso tem o mesmo efeito que as seguintes declarações:
map.resources :photos do |photo| photo.resource :photographer photo.resources :publications photo.resources :versions end 3.8.3 Limites para os aninhamentos
Você pode aninhar recursos com outros recursos aninhados se você quiser. Por exemplo:
map.resources :publishers do |publisher| publisher.resources :magazines do |magazine| magazine.resources :photos end end Entretanto, sem a utilização de name_prefix => nil, os recursos extremamente aninhados se tornarão pesado. Neste caso, por exemplo, a aplicação reconheceria URLs como
/publishers/1/magazines/2/photos/3
O helper correspondente a rota seria publisher_magazine_photo_url, exigindo que você especifique objetos para todos os níveis da árvore. Esta situação é confusa o suficiente para que um popular artigo por Jamis Buck propõe uma regra de ouro para um bom design em Rails:
Recursos nunca devem ser aninhados mais do que 1 nível de profundidade.
3.8.4 Aninhamento Superficial
A opção :shallow fornece uma solução elegante para as dificuldades de rotas extremamente aninhadas. Se você especificar esta opção a qualquer nível de roteamento, então os caminhos para recursos aninhados que referência a um membro específico (isto é, aqueles com o parâmetro :id) não usará o caminho ou o nome do prefixo. Para ver o que isso significa, considere a seguinte configuração de rotas:
map.resources :publishers, :shallow => true do |publisher| publisher.resources :magazines do |magazine| magazine.resources :photos end end Isso permitirá o reconhecimentos (entre outros) dessas rotas:
/publishers/1 ==> publisher_path(1) /publishers/1/magazines ==> publisher_magazines_path(1) /magazines/2 ==> magazine_path(2) /magazines/2/photos ==> magazines_photos_path(2) /photos/3 ==> photo_path(3)
Com o roteamento superficial, você somente precisa fornecer informações suficiente para identificar unicamente o recurso que você precisa para trabalhar com ele. Se você quiser você pode combinar o aninhamento superficial com as opções :has_one e :has_many:
map.resources :publishers, :has_many => { :magazines => :photos }, :shallow => true 3.9 Geração de rotas a partir de Arrays
Além de utilizar a os helpers geradores de roteamento, Rails também pode gerar rotas RESTful de um array de parâmetros. Por exemplo, suponha que você tem a configuração de rotas geradas com essas entradas no routes.rb:
map.resources :magazines do |magazine| magazine.resources :ads end Rails gerará helpers como magazine_ad_path que você pode usar na construção de links:
<%= link_to "Ad details", magazine_ad_path(@magazine, @ad) %> Outra forma para referir a mesma rota com um array de objetos:
<%= link_to "Ad details", [@magazine, @ad] %> Este formato é especialmente útil quando você não sabe em tempo de execução os vários tipos de objetos que podem ser usados em um link particular.
3.10 Recursos em Namespaces
É possível fazer algumas coisas muito complexas pela combinação de :path_prefix e :name_prefix. Por exemplo, você pode usar a combinação dessas duas opções para mover recursos administrativos para sua própria pasta na sua aplicação:
map.resources :photos, :path_prefix => 'admin', :controller => 'admin/photos' map.resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags' map.resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings' A boa notícia é que se você se encontrar com este nível de complexidade, você pode parar. Rails suporta recursos namespaced para mover recursos de lugares para suas próprias pastas com um estalo. Aqui é uma versão namespaced da mesma árvore de rotas:
map.namespace(:admin) do |admin| admin.resources :photos, :has_many => { :tags, :ratings} end Como você pode ver, a versão namespaced é muito mais sucinta do que do outro modo – mas ainda cria as mesmas rotas. Por exemplo, você vai ter admin_photos_url que espera encontrar um Admin::PhotosController e ainda corresponde com admin/photos, e admin_photos_ratings_path corresponde com /admin/photos/_photo_id_/ratings, esperado para usar Admin::RatingsController. Mesmo que você não está especificando explicitamente path_prefix, o código de roteamento calculará o apropriado path_prefix do roteamento aninhado.
3.11 Adicionando mais ações RESTful
Você não está limitado as sete rotas que o roteamento RESTful cria por padrão. Se você quiser, você pode adicionar mais membros de rotas (aqueles que se aplicam para uma única instância do recurso), novas rotas adicionais (aqueles que você aplica para a criação de novos recursos), ou uma coleção de rotas adicionais (aqueles que se aplicam para a coleção de recursos como um todo).
3.11.1 Adicionando Membros de Rotas
Para adicionar um membro de uma rota, use a opção :member:
map.resources :photos, :member => { :preview => :get } Isso habilitará o Rails para reconhecer URLs como /photos/1/preview usando o verbo HTTP GET, e rotea-las para a ação preview do controlador de Photos. Isto irá criar o helper de rota preview_photo.
Dentro da hash de membros de rotas, o nome de cada rota específica um verbo HTTP que irá ser reconhecido. Você pode usar :get, :put, :post, :delete, ou :any. Além disso você pode especificar um array de métodos, se você precisar de mais do que um, mas se você não quiser permitir qualquer um:
map.resources :photos, :member => { :prepare => [:get, :post] } 3.11.2 Adicionando uma Coleção de Rotas
Para adicionar uma coleção de rotas, usar a opção :collection:
map.resources :photos, :collection => { :search => :get } Isso habilitará o Rails a reconhecer URLs como /photos/search usando o verbo HTTP GET, e a rota para a ação search no controlador de Photos. Isso criará o helper search_photos para esta rota.
Assim como os membros das rotas, você pode especificar um array de métodos para uma collection da rota:
map.resources :photos, :collection => { :search => [:get, :post] } 3.11.3 Adicionando Novas Rotas
Para adicionar novas rotas (um que cria novos recursos), use a opção :new:
map.resources :photos, :new => { :upload => :post } Isto habilitará o Rails para reconheceria URLs como /photos/upload usando o verbo HTTP POST, e a rota para a ação upload no controlador de Photos. Isso criará o helper upload_photos para esta rota.
Se você precisa redefinir os métodos aceitáveis um por um para ações padrão, você pode fazer um mapeamento explícito para a ação. Por exemplo:
map.resources :photos, :new => { :new => :any } Isto permitirá que a ação new seja invocada por qualquer requisição para photos/new, não importa o verbo HTTp que você use.
3.11.4 Uma nota de cuidado
Se você está adicionando muitas rotas extras para uma rota RESTful, é hora de parar e se perguntar se você está disfarçando a presença de outro recurso que seria melhor utilizar o seu próprio. Quando o :member e :collection se tornam um lixo, então as rotas RESTful perdem a vantagem da facilidade de legibilidade, que é um de seus pontos fortes.

