web-dev-qa-db-fra.com

Routage des ressources imbriquées dans Rails 3

J'ai un cas assez commun pour les routes imbriquées, je pense, qui ressemble à ceci (dans une sorte de pseudonotation):

'/:username/photos' => Show photos for User.find_by_username
'/photos' => Show photos for User.all

En un mot: J'ai des utilisateurs. Ils ont des photos. Je veux pouvoir montrer leurs photos sur leur page. Je souhaite également pouvoir afficher toutes les photos, quel que soit l'utilisateur. J'aimerais garder mes routes RESTful et utiliser les méthodes intégrées resource me semble être la bonne façon de le faire.


Option 1 pour cela, il est nécessaire que PhotosController # index utilise une condition pour vérifier quels paramètres sont fournis, obtenir la liste des photos et définir la vue (différente pour les photos d'un utilisateur que pour toutes les photos). C'est même facile de l'acheminer:

resources :photos, :only => [:index]
scope ':/username' do
  resources :photos
end

Boom. Semblerait comme si Rails avait été configuré pour cela. Après les itinéraires, les choses se compliquent. Ce retour conditionnel dans l'action d'index PhotosController # commence à être de plus en plus gonflé et fait énormément de délayage. À mesure que l'application grandit et le nombre de façons dont je veux afficher des photos, la situation ne fera que s'aggraver.

Option 2 pourrait être d'avoir un utilisateur :: PhotosController pour gérer les photos de l'utilisateur, et un PhotosController pour gérer l'affichage de toutes les photos.

resources :photos, :only => [:index]
namespace :user, :path => '/:username' do
  resources :photos
end

Cela génère les itinéraires suivants:

           photos GET    /photos(.:format)                    {:action=>"index", :controller=>"photos"}
      user_photos GET    /:username/photos(.:format)          {:action=>"index", :controller=>"user/photos"}
                  POST   /:username/photos(.:format)          {:action=>"create", :controller=>"user/photos"}
   new_user_photo GET    /:username/photos/new(.:format)      {:action=>"new", :controller=>"user/photos"}
  edit_user_photo GET    /:username/photos/:id/edit(.:format) {:action=>"edit", :controller=>"user/photos"}
       user_photo GET    /:username/photos/:id(.:format)      {:action=>"show", :controller=>"user/photos"}
                  PUT    /:username/photos/:id(.:format)      {:action=>"update", :controller=>"user/photos"}
                  DELETE /:username/photos/:id(.:format)      {:action=>"destroy", :controller=>"user/photos"}

Cela fonctionne assez bien, je pense, mais tout est sous un module utilisateur et je sens que cela pourrait finir par causer des problèmes si je l'intègre à d'autres éléments.

Des questions

  • Est-ce que quelqu'un a de l'expérience avec quelque chose comme ça?
  • Quelqu'un peut-il partager une meilleure façon de gérer cela?
  • Des avantages et des inconvénients supplémentaires à envisager avec l'une ou l'autre de ces options?

Mise à jour : Je suis allé de l'avant en mettant en œuvre l'option 2, car elle semble plus propre, ce qui permet à la logique de Rails de fonctionner plutôt que de la dépasser. Jusqu'à présent, les choses se passent bien, mais je devais aussi renommer mon espace de noms en :users et ajouter un :as => :user pour l'empêcher d'entrer en conflit avec mon modèle User. J'ai également remplacé la méthode to_param sur le modèle User pour renvoyer le nom d'utilisateur. Les auxiliaires de chemin fonctionnent toujours de cette façon aussi.

J'apprécierais tout de même les retours sur cette méthode. Est-ce que je fais les choses comme prévu ou est-ce que j'utilise mal cette fonctionnalité?

33
coreyward

La meilleure façon de procéder dépend de l'application, mais dans mon cas, il s'agit bien de l'option B. À l'aide de routes espacées, je peux utiliser un module pour séparer les problèmes en différents contrôleurs de manière très propre. J'utilise également un contrôleur spécifique à un espace de noms pour ajouter une fonctionnalité partagée à tous les contrôleurs d'un espace de noms particulier (en ajoutant, par exemple, un before_filter pour vérifier l'authentification et l'autorisation de toutes les ressources de l'espace de noms).

6
coreyward

Avez-vous envisagé d'utiliser un itinéraire imbriqué peu profond dans ce cas?

Imbrication dans une route peu profondeParfois, les ressources imbriquées peuvent générer des URL fastidieuses. Une solution à ce problème consiste à utiliser l’imbrication d’itinéraires peu profonds:

resources :products, :shallow => true do
  resources :reviews
end

Cela permettra la reconnaissance des itinéraires suivants:

/products/1 => product_path(1)
/products/1/reviews => product_reviews_index_path(1)
/reviews/2 => reviews_path(2)
7
MattMcKnight

J'ai fait quelque chose de similaire à cela dans l'une de mes applications. Vous êtes sur la bonne voie. Ce que j'ai fait a été de déclarer les ressources imbriquées et de construire la requête en utilisant la syntaxe flexible basée sur arel d'Active Record dans Rails 3. Dans votre cas, cela pourrait ressembler à ceci:

# config/routes.rb
resources :photos, :only => :index
resources :users do
  resources :photos
end

# app/controllers/photos_controller.rb
def index
  @photos = Photo.scoped
  @photos = @photos.by_user(params[:user_id]) if params[:user_id]
  # ...
end
1
Jimmy Cuadra
Example::Application.routes.draw do
  resources :accounts, :path => '' do
    resources :projects, :path => '', :except => [:index]
  end
end

J'ai eu l'exemple de: http://jasoncodes.com/posts/Rails-3-nested-resource-slugs

Juste appliqué cela dans mon projet actuel.

0
Arthur Corenzan

Vous pouvez définir un itinéraire distinct pour obtenir les photos d'un utilisateur, comme suit:

get '(:username)/photos', :to => 'photos#index'

Mais je vous conseillerais simplement d'utiliser la ressource imbriquée que Jimmy a publiée ci-dessus, car c'est la solution la plus flexible.

0
Martijn