Linking with nested resources - ruby-on-rails

I have 3 models: posts, comments and questions. In routes.rb they look like this.
resources :posts do
resources :comments do
end
end
resources :comments do
resources :questions do
end
end
I am trying to link to the comment_questions path from a _post partial (being called in the posts index view) with the following:
<%= link_to (comment.body), comment_questions_path(post, comment) %>
It links to the comment_questions path but goes to a question belonging to the wrong comment.
Thanks!

You should be passing in the comment and question object to comment_question_path(comment, question) if you are linking to the show action of QuestionsController
The url generated for comment_question_path will be
GET comments/:comment_id/questions/:id questions#show
With what you are doing, comment_questions_path(post, comment), it will use post id for comment and comment id for question and you will go to a totally different page

Related

What are nested routes for in 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

rails custom rest route with parameter

I have a questions controller and an associated model and a number of rest routes. Here is how it's set up in routes.rb:
resources :questions
I want to add a custom route that has the format /questions/widget/ID (where ID is the id of the question for which I want to generate a widget). I want this to be processed by the "widget" action in my questions controller. I've tried a number of things such as:
resources :questions do
member do
get 'widget/:id'
end
end
But nothing is working. I'm sure I'm missing something simple. Any ideas? Thanks in advance.
You do not have to specify the id since you are inside resources. It should look like:
resources :questions do
member do
get 'widget'
end
end
You can get more information from the Rails Guide. Look at section 2.9.1.
Edit: I just noticed that you are trying to match get /questions/widget/:id. This will set up a route for get /questions/:id/widget. This is more in line with Rails convention. If you really want it the other way, you need to set up a custom match statement:
match "/questions/widget/:id" => "questions#widget"
However, I would stick with convention.
I know it is old, but looking to fix another routing problem I ended here, it is possible, to do what you are asking for, here is an example
resources :articles do
get 'by_tag/:tag' => :by_tag, on: :collection
get 'by_author/:author' => :by_author, on: :collection
resources :comments, except: :show
end
now you have /artices/by_tag/:tag . The trick was to use on:collection.
Obviously don't forget to add the by_tag action and by_author.
class ArticlesController < ApplicationController
.....
def by_tag
...
end
end
Check this route works with
melardev#local~$ rails routes
Why don't you use this routes:
resources :questions do
resources :widgets
end
it will create path like questions/:question_id/widgets/new for you to create new widget for question with specific id of question.
This is what ended up working for me:
resources :post do
get "author/:author", to: "posts#author", on: :collection, as: "author"
end
Which outputs the following route:
author_posts GET /posts/author/:author(.:format) posts#author
Then in your controller, you need to create the author action:
class PostsController < ApplicationController
def author
#roles = Post.where(author: params[:author])
render :index # to reuse the index view
end
end
Then in your view:
<%= link_to post.author, author_posts_path(post.author), data: { turbo_frame: "_top" } %>

Model Association

Hi I have a little problem with associations, I'm working with Rails 3.2, I want to create a special blog, this blog has many sections and this sections has many articles and an article belongs to a Category. So my models are:
class Article < ActiveRecord::Base
belongs_to :section
belongs_to :category
class Category < ActiveRecord::Base
has_many :articles
class Section < ActiveRecord::Base
has_many :article
So articles belongs_to section, in routes.rb :
resources :sections do
resources :articles
end
Rake Routes:
POST /sections/:section_id/articles(.:format) articles#create
new_section_article GET /sections/:section_id/articles/new(.:format) articles#new
edit_section_article GET /sections/:section_id/articles/:id/edit(.:format) articles#edit
section_article GET /sections/:section_id/articles/:id(.:format) articles#show
PUT /sections/:section_id/articles/:id(.:format) articles#update
DELETE /sections/:section_id/articles/:id(.:format) articles#destroy
sections GET /sections(.:format) sections#index
POST /sections(.:format) sections#create
new_section GET /sections/new(.:format) sections#new
edit_section GET /sections/:id/edit(.:format) sections#edit
section GET /sections/:id(.:format) sections#show
PUT /sections/:id(.:format) sections#update
DELETE /sections/:id(.:format) sections#destroy
So my question is How do I create a Categories_controller with index and show action.
to show the articles that belongs to that category, and have a link_to in views(Category#show) for the articles path.
Assuming that you do need nested routes to fit your domain model (ex. articles need to display differently based on whether they are being viewed in the context of a category or a section), then you can just create another set of nested routes like so:
routes.rb
resources :categories, only: [:index, :show] do
resources :articles
end
That will set up routes to find your categories and articles by categories, but you will have to fork your logic in your ArticlesController on params[:category_id] or params[:section_id] like so:
class ArticlesController < ApplicationController
def index
if params[:section_id]
# handle section_articles_path to display articles by section
elsif params[:category_id]
# handle category_articles_path to display articles by category
else
# handle articles_path to display all articles (assuming you have resources :articles in routes)
end
end
# ...
end
The link to show an article based on a category will then use the generated route helper methods created for you.
categories/show.html.erb
<ul>
<% #category.articles.each do |article| %>
<li><%= link_to article.title, category_article_path(#category, article) %></li>
<% end %>
</ul>
You could then, of course, refactor the view code to render the collection as its own partial rather than manually iterating, but I'll leave it at that for now...
If you don't need to handle the context differently (accessing an article through a section versus a category), I'd recommend just setting up three basic routes (:articles, :sections, :categories) and going from there.
It's Very much simple
match 'catagories' => "catagories#index" and match 'catagories/show/:id' => "catagories#show"
In show action #articles = Article.where("category_id",params[:id])
#articles = Article.where("category_id",params[:id]) This will solve your purpose

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 form helper and RESTful routes

I have a form partial current setup like this to make new blog posts
<% form_for([#current_user, #post]) do |f| %>
This works great when editing a post, but when creating a new post I get the following error:
undefined method `user_posts_path' for #<ActionView::Base:0x6158104>
My routes are setup as follows:
map.resources :user do |user|
user.resources :post
end
Is there a better way to setup my partial to handle both new posts and editing current posts?
map.resources :user do |user|
user.resources :posts
end
pluralize your model names in routes declaration. as you can see it says resourc-es, so you must use user-s and post-s too.
Controllers should be named UsersController and PostsController
Models should be named User and Post.
if above example still does not work for you, try a this one
map.resources :users do |u|
u.resources :posts
end

Resources