What are nested routes for in Rails? - ruby-on-rails

I am new to learning Rails and have just encountered nested routes. The example I am looking at involves blog articles and comments. I am trying to undestand what the benefit of nested routes are in Rails.
As far as I can tell all the information contained in a nested route for a comment such as /articles/:article_id/comments/:id(.:format) is all contained in the comment object itself so it does not communicating additional information to the Action.
Why not just have unnested routes such as /comments/:id(.:format)?
There is obviously a very good reason for using nested routes but I havent been able to work it out. The only benefit I can see so far is it gives a better illustration of the relation between articles and comments when reading the URL but all this information is contained in the comment object anyway.
Could someone explain this?

In your model you would have setup this association
class Article< ActiveRecord::Base
has_many :comments
end
class Comment< ActiveRecord::Base
belongs_to :article
end
So each comment is associated with an article and you need some logic to find corresponding article for a comment
This is where nested route comes in and lets you find article for that comment in your controller action. If you look at that route again
/articles/:article_id/comments/:id(.:format)
This is the comment controllers show action and this route allows you to find both article and your comment inside show action
def show
#article = Article.find(params[:article_id])
#comment = Comment.find(params[:id])
# if you are not using nested routes then you can find out associated article by
#article = #comment.article # but you'll have to query your database to get it which you can simply find if you are using nested route
end
More than the show action(where you can use some other logic to find article associated with that comment) you need nested route for your new action where you have to find that article and then build a comment for that article by something like
def new
#article = Article.new
#comment = #article.comments.build
end
As #August pointed out you can separate out actions for which you want your route to be nested by using shallow nesting, you can do:
resources :articles do
resources :comments, shallow: true
end
Checkout nested routes for more information

Correct, having the article in the path is redundant when dealing with a preexisting comment (because you can get the article from the comment). To avoid this, you can use shallow routes:
#routes.rb
resources :articles, shallow: true do
resources :comments
end
# or use a `shallow` block
shallow do
resources :articles
resources :comments
end
end

Related

Combine Route Resources

I'm fairly new to Rails so bear with me.
I have a post resource and a comment resource. I can access the following routes:
domain.com/post/new
Or
domain.com/post/2
Or
domain.com/post/2/edit
I can access the exact same routes for comments too.
The question is, how can I make a route for adding a comment to a post directly? Like such:
domain.com/post/2/comment/new
This route should just render the existing comment form but already have the post ID. Is there an easy way to achieve this?
You want to read up on has_many associations, nested routes and accepts_nested_attributes_for. Your route will be
resources :posts do
resources :comments
end

How can i create the following route

Rails beginner, so please don't bite. I've taken over the maintenance/development of a rails an app, but still learning the ropes
I'd like to generate the following route :
/events/1/Project/2/Pledge
Where 1 is the eventId and 2 is the Project Id.
I have a project controller and and events controller. The pledge action is on the Project controller
EDIT: In answer to #wacko's comment below.
a)Ignore the casing and pluralization of the url i asked for (I realise that invalidates the original question somewhat...)
An event has multiple projects, The pledge action will take the user to a page where they can enter multiple pledges for a particular project.
Perhaps instead the Pledge action should be on the Events Controller instead?
and the URL something like 'events/1/pledge/2' (Where 2 is the projectId)
What you are looking for is called a nested resource, that is to say that there is a parent child relationship between two resources.
resource :events do
resource :projects do
get :pledge, :on => :member
end
end
For this to work, your models would look something like this
class Event < ActiveRecord::Base
has_many :projects
end
And
class Project < ActiveRecord::Base
belongs_to :event
end
The following should work
get '/events/:event_id/projects/:id/pledge' => 'projects#pledge'
In your controller action you can get the event_id and project_id from the params hash as params[:event_id] and params[:id] respectively
resources :events do
resource :projects do
resources :pledge
end
end
this will give you the ability to set the scope in your controllers and have access to all 7 REST verbs
resources :events do
resources :projects do
member do
get :pledge
end
end
end
You can change get to the http method you want.
You can use collections if you need a route like /events/1/projects/pledge
collection do
get :pledge
end
run rake routes from the project root folder to see a list of routes generated
jsut use this way
resources :events do
resource :projects do
get '/pledge'
end
end

Rails 3 Nesting

I have a 2 level nesting objects that i need help with
My routes look like this to normalise the url abit. instead of having a url that looks like this /projects/1/tasks/3/comments/3.
resources :projects do
resources :tasks
end
resources :tasks do
resources :comments
end
Model has the 'has_many', belongs_to methods.
I can create comments under each task and display them under the tasks, but on the 'show' template of the comments i would like to display a link back to the tasks, which i get an error because the tasks controller is asking for a project_id?
How would this normally done when dealing with 2 level nesting?
I would do
resources :projects, :shallow => true do
resources :tasks do
resources :comments
end
end
Which is basically what you're doing except you can't generate a projects_task member path(ie projects/1/tasks/1) anymore they'd all just be task member paths(ie '/tasks/1').
Member paths include show, update, delete, edit
But the project_tasks collection paths(ie projects/1/tasks) would still be available.
Collection paths include index, create, new
comment paths wouldn't change. All comment paths would still include the task/:task_id prefix.
Checkout the resources documentation for more info on that (also more info on member and collection also on that page.)
But to actually solve your problem
You need to look up the project_id when you link back to the project_tasks index. So you would need to do
<%= link_to "Project Tasks Index", project_tasks_path(#task.project) %>
That way the Task#index knows where the parent project is. This is the solution for both implementations.
Check out the UrlFor documentation for more info on that.
If you want access to a #project variable in a Comment view
Then you just need to look up the project in the controller instead of at a view level. So basically
class CommentsController < ApplicationController
def show
#task = Task.find(params[:task_id])
#comment = #task.comments.find([:id])
#project = #task.project
end
end

Rails routes for article/234 and article/index

I need to setup the following urls:
/article (article#index)
/articles/1234 ( article#show with id 1234)
/articles/new (article#new)
Can I define this using:
resources :article do
???
end
If we look very closely at your question, it appears that you want the index to be at /article instead of the default Rails REST convention, which is /articles
It doesn't make any apparent sense to model your routes that way, but if that is surely what you want to do, then you could add one more route line in addition to the call to resources
resources :articles
match '/article', :to => 'articles#index'
It sounds like you're just learning rails. I'd suggest generating an article scaffold. It will set up a route like so for you:
resources :article
And you'll get RESTful routes setup for you automagically by rails
GET /articles index display a list of all articles
GET /articles/new new return an HTML form for creating a new article
POST /articles create create a new article
GET /articles/:id show display a specific article
GET /articles/:id/edit edit return an HTML form for editing an article
PUT /articles/:id update update a specific article
DELETE /articles/:id destroy delete a specific article
You can then dig into this and learn how rails does things.
Here's the official rails routing guide.
If you want those URLs and nothing else, you should put the following in routes.rb:
resources :article, :only => [:index, :show, :new]
In case you haven't stumbled upon the official Rails 3 routing guide, you should definitely take a look at it!

Nested resources - How to avoid redundant routes?

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

Resources