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.
Related
I have a simple problem where in a routes/url name is determined by a user role. Currently the route displayed is /new_admin/dispensaries. If the user has a role of either manager or executive then the named route should be '/dashboards/dispensaries'.
It's kind of simple but the hard part is that in my routes.rb:
namespace :new_admin do
resources :vendor_templates
resources :markdown_docs
resources :email_lists
namespace :moderation do
resources :reported_reviews
end
resources :users do
member do
get :user_bans
post :ban_unban, to: 'user_bans#create'
delete :ban_unban, to: 'user_bans#destroy'
end
end
# TODO - this should be written generically to support dispensary/doctors/whatever
get '/dispensaries/reviews', :to => "reviews#all", :as => :all_reviews
get '/dispensaries/pictures', :to => "pictures#all", :as => :all_pictures
get '/dispensaries/videos', :to => "videos#all", :as => :all_videos
get "/dispensaries/autocomplete", to: "dispensaries#autocomplete"
resources :vendors do
resources :ownership_transfers, only: [:new, :create]
end
...
I'm kind of stuck since if I change the new_admin routes, so many other routes will be affected. Any idea guys?
We've actually done something like this. It's not pretty, but this solution worked for us:
Slugs
You're basically alluding to a type of your routes called Slugs. This is where you use a name instead of an ID, allowing you to make a user-friendly route (such as /delivery/today). The problem is that in order to create these routes, you have to define them individually in the routes file
There are two Gems you can use to handle your slugged routes -- FriendlyID & Slugalicious. Both of these allow you to create slugged routes, but FriendlyID basically just changes the ID, whilst Slugalicious is a totally independent system
We used Slugalicious for the code below, however, you'll probably want FriendlyID (there's a RailsCast for it here):
Routing
The problem you have is that routes are outside the scope of the RESTful controller interface, which means you'll have to call all the routes exclusive of your resources references in the routes.rb file
If you use Slugalicious, it has its own Slugs database, which means we can use it to create the routes on the fly, like this:
#Slugs
begin
Slug.all.each do |s|
begin
get "#{s.slug}" => "#{s.sluggable_type.downcase.pluralize}#show", :id => s.slug
rescue
end
end
rescue
end
This is live code, and outputs all the slugs in the routes file dynamically. The way we managed to get this to update programmatically was to use an Observer Class like this:
class SlugObserver < ActiveRecord::Observer
def after_save(slug)
Rails.application.reload_routes!
end
def after_destroy(slug)
Rails.application.reload_routes!
end
end
I appreciate you may have your answer already, but as you're a beginner, I felt I could help out by explaining the slug stuff for you
I'm almost certain that someone has asked this question before, but I can't seem to hit on the right series of words to find it.
I have a resource, Games, with all of the normal resource-y paths. Create, Edit, etc.
I've created a new action within GamesController called json that I want to be able to access at mydomain.com/games/json but the routing keeps picking up 'json' as the ID and routing it to the 'show' action instead.
Presumably this is because of the default route:
match ':controller(/:action(/:id))'
I've tried a number of things, but no matter what I do it keeps routing to 'show.' I've been attempting to figure it out using this guide, but for someone that's pretty new to rails its quite a bit to take in and apply.
I'd like to say that for any controller /json would take you to the json action (instead of show with id 'json'), but I'd settle for having to specify it for every controller individually.
Any help is greatly appreciated. (Even if that's just pointing me to the already answered question.) In all cases I've been placing the route I'm attempting to create above the default route.
My routes:
root :to => 'home#index'
resources :events, :players, :sets, :matches, :characters, :videos, :games, :event_instances, :game_instances
resource :user_session
resource :account, :controller => "users"
resources :users
match '/login', :to => 'user_sessions#new', :as => 'login'
match '/logout', :to => 'user_sessions#destroy', :as => 'logout'
match '/register', :to => 'users#create', :as => 'register'
match '/games/json', :to => 'games#json', :as => 'gameList'
match ':controller(/:action(/:id))'
Ok so what you need to do is put your custom json route above your other resources routes and make it default like so:
get '/:controller/json(/:id)', action: 'json'
resources :events, :players, :sets, :matches, :characters, :videos, :games, :event_instances, :game_instances
...
Your confusion was linked to the way the routes are built and urls are parsed
Think of your routes.rb file as a set of consecutive filters to be applied to the url requested by the client.
If you put a default route above another it will catch all request that match the given pattern => this means that when you were adding all your resources routes above your json route, all urls matching the /#{resources}/:id pattern (and /games/json is one of them) were caught before they could reach the json custom route.
By putting your custom route above the others, you make it catch any request matching the pattern, that means in that case all requests with
/#{controller_name or resource_name)}/json(/:id)
NB:
I am not quite sure this is the best design pattern to use for your routes and I'd rather go with conventional REST routes with basic CRUD actions that would implement the respond_to pattern to answer specific json requests.
See docs for this: http://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to
Adding such 'catch all' routes on top of all your resources may become risky when your app grows.
But you may have your reasons to go this way, in that case, I think my answer matches what you need.
resources :games do
collection do
get :json
end
end
if you also wanted to add an action that would take an :id path segment and return, say, json representation of a specific game, you'd do that with
resources :games do
collection do
get :json
end
member do
get :json
end
end
I'd add that when I was brand new to Rails (less than 1.5 years ago), the routing dsl was really hard for me to get my head around for some reason. But it's worth studying that guide, despite the fact that it's a bit overwhelming at first. Otherwise you'll end up with a bunch of match '/route/to/somewhere' => 'controller#action', :as => :some_helper, which ends up being a pain to read & maintain and will make it harder to have a consistent "grammar" for your urls.
Edit: looking at your now-posted routes file, the problem is that you declare the games/json route after the resources :games declaration. Route-matching precedence proceeds from the top of the file down, so the resourceful route for games matches first, and the specific route declared later is never checked. If you use the syntax above to make your json route a part of the resource declaration, this won't happen.
I am playing with Rails 4 in a test application. I have an arbitrary URL that isn't standard like a resources :foo type URL. Ideally the end result I'd like is to be able to go to:
/contests/:id/enter
In views, it would be great if I can then set a link using a named helper such as:
edit_contests_enter(:id)?
What would be the best way to define the route above so I can use the helper path with an arbitrary URL like the one above? It doesn't necessarily have to be edit_contests_enter(:id) but as long as the helper path leads to the URL as suggested above, that would be fantastic.
I assume that your contest is a resource, and when your visitor goes to /contests/:id/enter you want them to create an object user <=> contest. Let's call it participation.
Now participation is exactly like any other resource in your Rails app, so you'd have a routes.rb file looking like
resources :contests do
resources :participations
end
You don't want people to do anything other than create a participation, like edit or destroy them. And perhaps you want a nice URI like /contests/:id/enter. All you have to do is
resources :contests do
resources :participations, :only => [:new, :create]
get "enter" => "participations#new"
end
Doing such will give you a routes helper named contest_enter. In your participations#new form, you'll POST as usual to /contests/:id/participations.
If you have a resources block for :contests, you could just define a new "member" route on the ContestsController using:
resources :contests do
member do
get :enter
end
end
And that would automatically generate you a named member route, the name of which you could find by running rake routes.
Let's say I have a receipts model, and I want to offer a controller action to print one... The un-restful way would be to do:
# receipt_controller.rb
def print
...
end
#routes.rb
resources :receipts do
get :print, :on => :member
end
... The restful way would be:
# receipt_printings_controller.rb
def create
...
end
#routes.rb
resources :receipts
resources :receipt_printings, :only => :create
My question is..... Let's say I wanted to have the following structure:
/app
/controllers
receipts_controller.rb
/receipt
printings_controller.rb
That would mean my class would look like:
class Receipt::PrintingsController < ActiveRecord::Base
def create
...
end
end
But I don't know how to properly route in this context because I still need to be able to do:
receipt_printings_path(123) to get /receipts/123/printings
The only way I know how to accomplish this is to do:
#routes.rb
match "/receipts/:id/printings" => "receipt/printings#create", :as => :receipt_printings
resources :receipts
But, I am wondering if there is a better way?
I think you can do something like this:
resources :receipts do
resources :printings, :controller => "receipt/printings", :only => :create
end
It will generate :
receipt_printings POST /receipts/:receipt_id/printings(.:format) receipt/printings#create
Then to access to your route :
receipt_printings_path(:receipt_id => #receipt.id)
I hope it helps
If i'm right, you need a nested resource, have look in this rails guide
You can use nest routes, but the way I read your question it sounds to me like you want namespaces. Namespaces might look like the following:
resources :receipts
namespace :receipts do
resources :printings
end
This would route /receipts/printings/:id to app/receipt/printings_controller.rb with an id for the printing (not the receipt).
You might really want nested routes. If you want to use the receipt id, and have only one print action (per receipt), you could use a singular resource.
resources :receipts do
resource :printing
end
This will route /receipts/:id/print to app/printings_controller.rb as show.
To organize the printings controller in a namespace, I would leave it out of the routes, because that will try to insert another receipts namespace in the URL. Instead, use,
resources :receipts do
resource :printing, :controller => "receipt/printings"
end
This is how to be RESTful. However, you might not have a RESTful case. Is printing really doing a create? Is it really doing a show or update? If it's a service which doesn't fit into a CRUD operation, then it's time to deviate from the golden path, and go ahead and use a non-RESTful verb.
I'm working on an app where users can share photos. The photos can optionally belong to a collection, but don't have to.
Currently users can look through all photos via: photos/id. I think it would also make sense if they could browse through the photos for a particular collection through collections/id/photos
So, this would mean that photos were both a top level resource and a nested resource. I suppose I could set this up in the routes like so:
resources :photos
resources :collections do
resources :photos
end
Is this a good idea, or is there a better way to reuse the photo model while also allowing it to act as nested under collections when appropriate? I'd very much appreciate suggestions as to the "rails way" of handling this kind of scenario.
Thanks!
The routes you've suggested work perfectly fine. You do need to watch out in your Photos controller actions, though. Because they can be called for an individual photo OR a collection, you need to conditionally find photos based on what params are available.
Also, I'd suggest being more specific about which actions are available for each route:
resources :photos
resources :collections do
resources :photos, :only => [:index, :create, :destroy]
end
# index => show photos in a collection
# create => add a photo to a collection
# destroy => remove a photo from a collection
You don't really need to be able to edit/update/show a photo as a member of a collection from the information you provided.
Another option is to use a namespaced route:
namespace :collection, :path => '/collection', :as => :collection do
resources :photos, :only => [:index, :create, :destroy]
end
That will allow you to separate your Collection::Photos from your Photos…
controllers/photos_controller.rb
controllers/collections/photos_controller.rb
And if you really want, Rails lets you do the same to your views. Another benefit of using the namespace is that it sets up some really nifty route helpers:
photo_path(#photo) #=> /photos/1
collection_photos_path #=> /collections/1/photos
etc.