开发者

Routing nested resources in Rails 3

I have a pretty common case for nested routes, I feel like, that looks something like this (in some sort of pseudonotation):

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

In a nutshell: I have users. They have photos. I want to be able to show their photos on their page. I also want to be able to show all photos, regardless of the user. I'd like to keep my routes RESTful and using the built-in resource methods feels like the right way to do it.


Option 1 for doing this is to have PhotosController#index use a conditional to check which params are given and get the list of photos and set the view (different for a user's photos than for all photos). It's even easy to route it:

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

Boom. It'd seem like Rails was setup for this. After the routes, though, things get more complicated. That conditional back in the PhotosController#index action is just getting more and more bloated and is doing an awful lot of delgation. As the application grows and so do the number of ways I want to show photos, it is only going to get worse.

Option 2 might be to have a User::PhotosController to handle user photos, and a PhotosController to handle showing all photos.

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

That generates the following routes:

           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"}

This works pretty well, I think, but everything is under a User module and I feel like that might end up causing problems when I integrate it with other things.

Questions

  • Does anybody have experience with something like this?
  • Can anybody share a better way of handling this?
  • Any additional pros and cons to consider with either of these options?

Update: I've gone ahead implementing Option 2 because it feels cleaner allowing Rails' logic to work rather than overriding it. So far things are going well, but I also needed to rename my namespace to :users and add an :as => :user to keep it from clashing with my User model. I've also overridden the to_param method on the User model to return the username. Path helpers still work this way, too.

I'd still appreciate feedback on this method. Am I doing things the expected way, or am I misusing this functionality?


Have you considered using a shallow nested route in this case?

Shallow Route Nesting At times, nested resources can produce cumbersome URLs. A solution to this is to use shallow route nesting:

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

This will enable the recognition of the following routes:

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


The best way to do this depends on the application, but in my case it is certainly Option B. Using namespaced routes I'm able to use a module to keep different concerns separated out into different controllers in a very clean way. I'm also using a namespace-specific controller to add shared functionality to all controllers in a particular namespace (adding, for example, a before_filter to check for authentication and permission for all resources in the namespace).


I did something similar to this in one of my apps. You're on the right track. What I did was declare nested resources, and build the query using the flexible arel-based syntax of Active Record in Rails 3. In your case it might look something like this:

# 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


You could define a seperate route for getting the photos for one user like so:

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

But I would advise just using the nested resource that Jimmy posted above since that is the most flexible solution.


Example::Application.routes.draw do
  resources :accounts, :path => '' do
    resources :projects, :path => '', :except => [:index]
  end
end

Got the example from: http://jasoncodes.com/posts/rails-3-nested-resource-slugs

Just applied that in my current project.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜