Nested resources - How to avoid redundant routes? - ruby-on-rails

I have this resource tree:
Forum
Topic
Post
I want to be able to access them independently wherever possible. I want to avoid redundant routes like /forum/:forum_id/topic/:topic_id/post/:id because I can just do /post/:id.
The ideal routes look like this:
/forums => Forums#index # Lists every forum
/forum/new => Forums#new # New forum
/forum/edit => Forums#edit # Edit forum
/forum/:id => Forums#show # Shows forum
/forum/:id/forums Forums#index # Lists nested forums
/forum/:id/topics => Topics#index # Lists topics inside forum
/forum/:id/topic/new => Topics#new # New topic
/topics => Topics#index # Lists every topic
/topic/:id => Topics#show # Shows topic
/topic/:id/posts => Posts#index # Lists posts inside topic
/topic/:id/post/new => Posts#new # New post
/posts => Posts#index # Lists every post
/post/:id => Posts#show # Shows post
What is the best way to model this situation?
Here's what I tried:
resources :forums
resources :topics
resources :posts
resources :forums do
resources :topics
end
resources :topics do
resources :posts
end
The problem is that these settings create a lot of useless routes, like:
/forums/:forum_id/topic/:id # Redundant - /topic/:id
/topics/:topic_id/post/:id # Redundant - /post/:id
/topics/new # No current forum
/posts/new # No current topic
Is there any way to specify which routes to create?
In the controllers, how do I handle multiple routes mapped to the same action? For example, inside Topics#index how do I find out if I should handle GET /forum/:id/topics or GET /topics?

Nested routes are only needed on index actions where a collection of resources is found by a parent object. Otherwise it is about SEO. Most users will not notice how their urls are getting generated nor care so it's all about search engines. I see where you are going but it's going to be more work to not generate routes as the convention in this example is listing a resource with one line of code. And of course you already know this but this is just my take on things.
a) forms_path #if you want to find all forms
b) topics_path #if you want to find all topics #possible use, maybe in a tag listing.
c) posts_path #if you want to find all posts #probably never use
You will probably never want to find all topics and especially posts, but those would be the routes to use.
d) form_topics_path(form) #find all topics of a given form
e) form_topic_path(form, topic) #only find one topic on a give form
f) topic_path #find a given topic
In the last two, e and f, the form is not needed since you know which topic you want. If you are concerned about SEO and getting your urls nice for search engines then probably want to use e.
g) form_topic_posts_path(form, topic) #I'm already confused
h) form_topic_post_path(form, topic, post) #still to deep
i) topic_posts_path(topic) #most rails people think two levels is deep enough
j) topic_post_path(topic, post) #might be good for seo
It's really a matter of SEO and keeping your urls friendly besides the nested resource that need their parent id to find the associated posts such as passing the form to find the related topics, and passing the topic to find the related posts.
If you use topic_path, topics_path post_path, post_path you are surly missing out on better urls but in terms of having better urls for engines to read but they really are unnecessary.
In terms of not generating the routes there really isn't a demand for this because it would makes this more complicated than just declaring a resource in one line where the end goal is just housekeeping.

I solved my problem by restricting what routes each resource declaration generated:
resources :forums do
resources :topics, only: [ :index, :new, :create ]
end
scope except: [ :new, :create ] do
resources :posts
resources :topics do
resources :posts, only: [ :index, :new, :create ]
end
end
As for the controller issue, I simply check if an id was passed:
# Topics#index
if forum_id = params[:forum_id]
#topics = Forum.find(forum_id).topics.paginate page: params[:page]
else
#topics = Topic.paginate page: params[:page]
end

Related

Polymorphic routes pointed to same controller and action

This is a polymorphic association!
resources :professors, :labs do
member do
resources :teaching_assistants
end
end
I want to have index for professors teaching assistants, labs teaching assistants and teaching assistants like so:
/professors/id/teaching_assistants
/labs/id/teaching_assistants
/teaching_assistants
All these routes are pointing to
teaching_assistants#index. Do i have to explicitly point the nested routes to an action in in the professors and labs controller? How would I do that/alter the routes file?
resources :professors, :labs do
member do
resources :teaching_assistants, except: :index
end
end
get '/professors/id/teaching_assistants' => professors#assistantsindex
get '/labs/id/teaching_assistants' => labs#assistantsindex
Using resources is for creating all the basic RESTful pointers (index/create/read/update/delete). It sounds like your teaching assistants are a 'one off' listing page, in which case you would simply use get to create the route.
Try this:
resources :professors, :labs do
member do
# /professors/id/teaching_assistants > professors#teaching_assistants
# /labs/id/teaching_assistants > labs#teaching_assistants
get 'teaching_assistants'
end
end
# /teaching_assistants
get 'teaching_assistants'

Dynamic named routes in Rails

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

route works one place, not others

This is kind of difficult to communicate but I'll try without pasting all my code. I have Members who have one Mailbox which has many Receipts. In the header layout I have a nav that calls
<%= link_to "Message Center", member_mailbox_path(current_user.member_id) %>
It works on most pages like trails/# , the resource pages for various models
But on other pages, seems like custom route pages, I get this error
No route matches {:action=>"show", :controller=>"mailbox", :member_id=>16}
Running rake routes shows this:
member_mailbox GET /members/:member_id/mailbox/:id(.:format) mailbox#show
Routes are confusing to me, here are my routes for this problem (show message isn't tested yet) ...
resources :members do
resources :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
The routes for the pages that are showing the error are similar to this one
match '/my_plays', :to => "trails#my_plays"
match '/my_creations', :to => "trails#my_creations"
So not sure if my routes are right. I wonder if resources :mailbox is correct since I don't have a bunch of resources for that, it's a has_one .... THX
----EDIT--- after changing route per advice:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
You may want to define a mailbox as a singular resource in your routes. Otherwise, Rails will expect you to pass in both the user id and the mailbox id for member_mailbox_path to route to mailbox#show. I believe this is why you're getting a routing error. Since each user has one mailbox, there's no need to make this extra lookup part of the route. So instead of resources :mailbox, you can do resource :mailbox:
resources :members do
resource :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
I believe this would generate the following routes:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
Notice that the lack of path names next to GET, PUT, and DELETE doesn't mean they don't exist; they're just repeats of the POST path, but each responds to different HTTP methods.
To render mailboxes#show, you'll need to add a MailboxesController with a show route, which might do a look up for the member:
class MailboxesController < ApplicationController
def show
#member = Member.find(params[:member_id])
# other mailbox code...
end
end
And you'll also create a template at app/views/mailboxes/show.html.erb to render the mailbox show page.
Also, I would recommend against deeply nesting your routes, as in third level :receipts.

what is the proper convention for restful routing via namespaces?

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.

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.

Resources