Rails 5.2.3
In my routes.rb, I have:
get '/books/:author', to: 'books#index'
get '/books/:author/show', to: 'books#show'
post '/books/:author/create', to: 'books#create'
Which, when running rake: routes, gives me:
GET /books/:author(.:format) books#index
GET /books/:author/show(.:format) books#show
POST /books/:author(.:format) books#create
When a route helper is not provided by rake: routes, can I assume that when I create a link to it in a view, I can use use the model name, like: books_path or books_url? something like:
= link_to books_path(:author => #author), :method => :post
I tried it, but I am getting an error message:
undefined method books_path
So, I am either doing something wrong in routes.rb, or I am not referencing it correctly?
You are creating an unnamed route. I think what you want in your particular situation is:
resources :books, only: [:index, :show, :create], param: :author
That will give:
Prefix Verb URI Pattern Controller#Action
books GET /books(.:format) books#index
POST /books(.:format) books#create
book GET /books/:author(.:format) books#show
This does not seem RESTful to me though. I think what you really want is a nested resource between books and authors. Check out the Rails guides on routing for more information: https://guides.rubyonrails.org/routing.html
Just to pile on, I suggest you use nested routes:
resources :authors do
resources :books, shallow: true
end
resources :books, only: [:index, :create, :new]
Which will give you (amongst other things):
author_books GET /authors/:author_id/books(.:format) books#index
POST /authors/:author_id/books(.:format) books#create
new_author_book GET /authors/:author_id/books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
authors GET /authors(.:format) authors#index
POST /authors(.:format) authors#create
new_author GET /authors/new(.:format) authors#new
edit_author GET /authors/:id/edit(.:format) authors#edit
author GET /authors/:id(.:format) authors#show
PATCH /authors/:id(.:format) authors#update
PUT /authors/:id(.:format) authors#update
DELETE /authors/:id(.:format) authors#destroy
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
That ought to give you everything you need to manage authors and books.
I don't get how post '/books/:author/create', to: 'books#create' gives you POST /books/:author(.:format) books#create.
If you want to name your custom routes add the name with the :as option: get 'something', to: 'controller#action', as: 'something' to get "something_path" as a valid helper.
Note that you are going away from RESTful routes, if you want rails to do the things it usually do (like the named routes helpers) you should stick with it's conventions: https://guides.rubyonrails.org/routing.html
It's books_author_path, most likely.
Just remember what you're doing is not good practice at all. (It's actually beyond terrible for a coc framework like Ruby).
First of all, nested new routes are fine but nested create routes are generally considered bad.
Second, why is books/author_id/create creating a book?
It should be authors/author_id/books/new to books/create
Go into your console and type in "rails routes" and it'll tell you what all the helper urls are
Related
What is the difference between
namespace :alpha do
resources :posts
end
and
resources :alpha do
resources :posts
end
Check out the difference using rake routes.
This definition with a namespace:
namespace :alpha do
resources :posts
end
results in the following routes:
Prefix Verb URI Pattern Controller#Action
alpha_posts GET /alpha/posts(.:format) alpha/posts#index
POST /alpha/posts(.:format) alpha/posts#create
new_alpha_post GET /alpha/posts/new(.:format) alpha/posts#new
edit_alpha_post GET /alpha/posts/:id/edit(.:format) alpha/posts#edit
alpha_post GET /alpha/posts/:id(.:format) alpha/posts#show
PATCH /alpha/posts/:id(.:format) alpha/posts#update
PUT /alpha/posts/:id(.:format) alpha/posts#update
DELETE /alpha/posts/:id(.:format) alpha/posts#destroy
As you can see, the only thing that is different from a plain resources route set is the addition of /alpha prefix.
Now for the two-level resources routes definition:
resources :alpha do
resources :posts
end
which results in:
Prefix Verb URI Pattern Controller#Action
alpha_posts GET /alpha/:alpha_id/posts(.:format) posts#index
POST /alpha/:alpha_id/posts(.:format) posts#create
new_alpha_post GET /alpha/:alpha_id/posts/new(.:format) posts#new
edit_alpha_post GET /alpha/:alpha_id/posts/:id/edit(.:format) posts#edit
alpha_post GET /alpha/:alpha_id/posts/:id(.:format) posts#show
PATCH /alpha/:alpha_id/posts/:id(.:format) posts#update
PUT /alpha/:alpha_id/posts/:id(.:format) posts#update
DELETE /alpha/:alpha_id/posts/:id(.:format) posts#destroy
alpha_index GET /alpha(.:format) alpha#index
POST /alpha(.:format) alpha#create
new_alpha GET /alpha/new(.:format) alpha#new
edit_alpha GET /alpha/:id/edit(.:format) alpha#edit
alpha GET /alpha/:id(.:format) alpha#show
PATCH /alpha/:id(.:format) alpha#update
PUT /alpha/:id(.:format) alpha#update
DELETE /alpha/:id(.:format) alpha#destroy
As you can see, alpha becomes a top-level resource with all 8 RESTful routes. posts, in turn, become second-level resource, accessible only through the route to a specific alpha object.
Read more in Rails Routing from the Outside In.
You might also find interesting the scope option. Read about the difference between scope and namespace in Scoping Rails Routes blog post.
The new action normally doesn't require parameters, since it creates a new resource from scratch.
In my application whenever i create a certain type of resource say a book i need to provide a template, that is the id of another book. So my new route always has a parameter.
I don't know how to represent this fact into routes.rb file.
Since i don't even know whether it is feasible, just in the case it isn't, then i will create a new_wp, a "new with parameter" action.
I tried to add it to my
resources :books, :only => [:edit, :update, :show, :new] do
member do
get 'new_wp/:template_id', :action => 'new_wp'
end
end
but rake routes say that it isn't quite what i want:
GET /books/:id/new_wp/:template_id(.:format) books#new_wp
that is, it has two params.
I do this often, the easiest way, I believe is to just adjust the path_names. That way your route names don't get messed up. Let me explain.
Scenario 1 - Standard Rails
Code
resources :books
Output
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
Scenario 2 - Chris Heald Version
Code
resources :books do
get "new/:template_id", to: "books#new_wp", on: :collection
end
# You can also do, same result with clearer intention
# resources :books do
# get ":template_id", to: "books#new_wp", on: :new
# end
Output
GET /books/new/:template_id(.:format) books#new_wp
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
Scenario 3 - My preferred and noted by steakchaser above
Code
resources :books, path_names: {new: 'new/:template_id' }
Output
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new/:template_id(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
You will notice that in scenario 2 you are missing a path name, which means you would want to add an as: :new which would generate new_new_book. Fixing this you can change from get ":template_id" ... to get path: ":template_id"... which will generate new_book
My preference is scenario 3 if all you want to do is pass arguments for new. If you want to change the action then you would want to consider using scenario 2 but exclude :new from the resource or in your case don't add :new to the only: argument.
Try:
resource ...
get "new/:template_id", :to => "Books#new_wp", :on => :collection
end
# GET /books/new/:template_id(.:format) Books#new_wp
Edit: I've seen a number of these but couldn't find an answer to this so I'm attempting to document it as best I can and asking this question.
I have a model-less rails app (calling an API) with a nested comments resource. I am able to post a comment against a story if I go directly to the comments#new or comments#index action and accordingly post to the comments#create action.
However I'd like very much to be able to post a comment on the same page as the #show action of the parent resource: (opusses#show)
I've tried using the rails url_helper path from rake routes as opuss_comments_path and explicitly stating the controller and action. In both cases I still get this message:
No route matches {:controller=>"comments", :action=>"create"}
Here is my routes db:
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :osessions, only: [:new, :create, :destroy]
resources :authors do
member do
get :following
get :followed
post :follow
end
end
resources :opusses do
resources :comments
member do
get :like
get :authorfeed
post :repost
end
end
And my Rake Routes:
DELETE /authors/:id(.:format) authors#destroy
opuss_comments GET /opusses/:opuss_id/comments(.:format) comments#index
POST /opusses/:opuss_id/comments(.:format) comments#create
new_opuss_comment GET /opusses/:opuss_id/comments/new(.:format) comments#new
edit_opuss_comment GET /opusses/:opuss_id/comments/:id/edit(.:format) comments#edit
opuss_comment GET /opusses/:opuss_id/comments/:id(.:format) comments#show
PUT /opusses/:opuss_id/comments/:id(.:format) comments#update
DELETE /opusses/:opuss_id/comments/:id(.:format) comments#destroy
&&
like_opuss GET /opusses/:id/like(.:format) opusses#like
authorfeed_opuss GET /opusses/:id/authorfeed(.:format) opusses#authorfeed
repost_opuss POST /opusses/:id/repost(.:format) opusses#repost
opusses GET /opusses(.:format) opusses#index
POST /opusses(.:format) opusses#create
new_opuss GET /opusses/new(.:format) opusses#new
edit_opuss GET /opusses/:id/edit(.:format) opusses#edit
opuss GET /opusses/:id(.:format) opusses#show
PUT /opusses/:id(.:format) opusses#update
DELETE /opusses/:id(.:format) opusses#destroy
When I call the code below from comments#index page it works perfectly. However it's quite common to post to another form from a different controller and when I call this code from the opusses#show page it fails with the error above.
On the off chance it had to do with the URL helper, I tried specifying the controller and action explicitly and that still didn't work - generated the same error.
Classic newbie mistake, but for others benefit =>
I had rake routes and I had the path correct, what I wasn't doing was submitting a the id of the parent resource. So POST to the path and include the object in question. In my case this mean opuss_comments_path(#opuss["xyz"]) where xyz was the id of my object.
opuss_comments GET /opusses/:opuss_id/comments(.:format) comments#index
POST /opusses/:opuss_id/comments(.:format) comments#create
Ah.. learning. :)
Based on your routes, You shouldn't have to use a url helper. but you do have to make sure that you have a handle on the Opuss object in the controller. so do something like this ;
#opuss = Opuss.find(params[:id]) #or your equivalent finder code
#comment = #opuss.comments.build
and then in your view;
<%= form_for([#opuss, #comment]) do |f| %>
.... rest of form
<% end %>
I am having problems create the right route. I want to pass in the id of the element that i am working on but it does not look right.
my route looks like
resources :accounts
match 'account-audit' => 'accounts#audited',:as => :accountaudit
and when i do rake routes i get
accounts GET /accounts(.:format) accounts#index
POST /accounts(.:format) accounts#create
new_account GET /accounts/new(.:format) accounts#new
edit_account GET /accounts/:id/edit(.:format) accounts#edit
account GET /accounts/:id(.:format) accounts#show
PUT /accounts/:id(.:format) accounts#update
DELETE /accounts/:id(.:format) accounts#destroy
accountaudit /account-audit(.:format) accounts#audited
when i go to the page the link looks
localhost:3000/account-audit.3
and it should look like
localhost:3000/account/3/audit
how do i make my route do what i need it to do?
You need to declare routes like this
resources :accounts do
get :audit, on: :member, as: :accountaudit
end
This will generate links like localhost:3000/accounts/account_id/audit. Check this stackoverlfow question to learn about member and collection routes.
What it looks like you are trying to do is a nested routes this will give you the restful routes for audit inside of accounts
resources :accounts do
resources :audit
end
What is the difference between collection routes and member routes in Rails?
For example,
resources :photos do
member do
get :preview
end
end
versus
resources :photos do
collection do
get :search
end
end
I don't understand.
A member route will require an ID, because it acts on a member. A collection route doesn't because it acts on a collection of objects. Preview is an example of a member route, because it acts on (and displays) a single object. Search is an example of a collection route, because it acts on (and displays) a collection of objects.
URL Helper Description
----------------------------------------------------------------------------------------------------------------------------------
member /photos/1/preview preview_photo_path(photo) Acts on a specific resource so required id (preview specific photo)
collection /photos/search search_photos_path Acts on collection of resources(display all photos)
Theo's answer is correct. For documentation's sake, I'd like to also note that the two will generate different path helpers.
member {get 'preview'} will generate:
preview_photo_path(#photo) # /photos/1/preview
collection {get 'search'} will generate:
search_photos_path # /photos/search
Note plurality!
1) :collection - Add named routes for other actions that operate on the collection. Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete, an array of any of the previous, or :any if the method does not matter. These routes map to a URL like /users/customers_list, with a route of customers_list_users_url.
map.resources :users, :collection => { :customers_list=> :get }
2) :member - Same as :collection, but for actions that operate on a
specific member.
map.resources :users, :member => { :inactive=> :post }
it treated as /users/1;inactive=> [:action => 'inactive', :id => 1]
Short Answer:
Both the member and collection blocks allow you to define additional routes for your resources than the seven standard routes that Rails will generate for you.
A member block creates new routes on a single member of the resource.
A collection block creates new routes for a collection of that resource.
Long Answer
Rails provides the member and collection blocks so you can define custom routes for both the resource collection and the individual resource.
Here's how you'd typically define routes for the article resource.
resources :articles
This creates the following routes.
➜ bin/rails routes -g article
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
But let's say you are writing your articles in markdown, and need to see a preview of the article as you write it.
You could create a PreviewController and display the article's preview using its show action, but it's convenient to add a preview action on the ArticlesController itself.
Custom Member Routes
Here's how you define the preview route on the ArticlesController using the member block.
resources :articles do
member do
get 'preview'
end
end
It adds a new route that directs the request to ArticlesController#preview action. The remaining routes remain unchanged. It also passes the article id in params[:id] and creates the preview_article_path and preview_article_url helpers.
➜ bin/rails routes -g article
Prefix Verb URI Pattern Controller#Action
preview_article GET /articles/:id/preview(.:format) articles#preview
... remaining routes
If you have a single member route, use the short-hand version by passing the :on option to the route, eliminating the block.
resources :articles do
get 'preview', on: :member
end
You can go one step further and leave out the :on option.
resources :articles do
get 'preview'
end
It generates the following route.
➜ bin/rails routes -g preview
Prefix Verb URI Pattern Controller#Action
article_preview GET /articles/:article_id/preview(.:format) articles#preview
There are two important differences here:
The article's id is available as params[:article_id] instead of params[:id].
The route helpers changes from preview_article_path to article_preview_path and preview_article_url to article_preview_url.
Custom Collection Routes
To add a new route for the collection of a resource, use the collection block.
resources :articles do
collection do
get 'search'
end
end
This adds the following new route. It will also add a search_articles_path and search_articles_url helper.
search_articles GET /articles/search(.:format) articles#search
If you don't need multiple collection routes, just pass :on option to the route.
resources :articles do
get 'search', on: :collection
end
This will add the same route as above.
Conclusion
Rails allows you to break out of its convention of using seven resourceful routes using the member and collection blocks. Both allow you to define additional routes for your resources than the standard seven routes.
A member block acts on a single member of the resource, whereas a collection operates on a collection of that resource.
Source: Define New Routes Using the Member and Collection Blocks