custom route caught by REST update action - ruby-on-rails

I have a Hotel resource in my Rails app. I added a few custom, non-RESTful actions for some additional functionality.
I have the following routes.rb file:
resources :hotels do
post 'sort', on: :collection
post 'update_hotel_settings', on: :collection
end
and here is the output from rake routes:
....
sort_hotels POST /hotels/sort(.:format) hotels#sort
update_hotel_settings POST /hotels/update_hotel_settings(.:format) hotels#update_hotel_settings
hotels GET /hotels(.:format) hotels#index
POST /hotels(.:format) hotels#create
new_hotel GET /hotels/new(.:format) hotels#new
edit_hotel GET /hotels/:id/edit(.:format) hotels#edit
hotel GET /hotels/:id(.:format) hotels#show
PUT /hotels/:id(.:format) hotels#update
DELETE /hotels/:id(.:format) hotels#destroy
The update_hotel_settings action is where some general settings are saved to the DB.
Here is the beggining of the form being sent to that action:
<%= simple_form_for(#store, url: update_hotel_settings_hotels_path, html: {class: 'form-horizontal', id: 'hotel-settings-form'}) do |f| %>
giving:
<form accept-charset="UTF-8" action="/hotels/update_hotel_settings" class="simple_form form-horizontal" id="hotel-settings-form" method="post" novalidate="novalidate">...
However after submitting the form, the POST request is caught by the hotels' REST update action, with an exception about net being able to find hotel with id="update_hotel_settings".
Obviously this is an unwanted result, and I can't seem to find it's cause.
Shouldn't the update action be triggered by PUT/PATCH methods, and not POST? I also tried using another name for the route+action, instead of "update_hotel_settings", with no luck.
What's most annoying is that the sort action, listed right above, works fine! (only difference is the 'sort' action is fired via ajax, but not sure that should matter)
EDIT:
Contrary to what you might think, nested route mappers actually take precedent over the resource's 7 default routes.
EDIT 2: Another Clue, Another Question!
At first I was embarrassed to notice I forgot the method: :post option in the simple_form_for's arguments, and when I added it - Kapow! the form routes to the right action! weird isn't it? especially when there was already a method="post" attribute in the form tag!
WHAT IS GOING ON?

Routes are caught top to bottom. Meaning, if there is a match, there it stops. So, all you have to do is take the update_hotel_settings route out of the resource and before it.
match '/hotels/update_hotel_settings', to: 'hotels#update_hotel_settings', via: 'post', as :update_hotel_settings_hotels
resources :hotels do
[...]

Related

rails named route puts '.' instead of '/' in URL on updating [duplicate]

This question already has an answer here:
Rails dot instead of slash in URL
(1 answer)
Closed 5 years ago.
Update #3 ... fixed! Solution was moving the new name space code (get 'dash', to: 'dashes#show') to the bottom of the routes.rb file just above the root "campaigns#index" entry.
Update #2 ... it's not a pluralization as it's called singularly, shown below & removing the named route automatically corrects all issues.
Update #1 ... found reason why no one had listed period in place of slash in URL ... it's unix nouns, not english ... IE: if you search for 'dot' in place of '.', then you get all sorts of answers.
I'm flumoxed by this one ... made my first named route the other day, everything looked great until suddenly it appears that the edit page when called from the named route doesn't update properly, with error ...
No route matches [PATCH] "/dash.6"
Removal of the named route takes me back to normal routes & all options working. I can't find a mention of routing which uses '.' instead of '\', so I'm lost. My route file below & then the route results from rails server ...
Rails.application.routes.draw do
devise_for :users, controllers: { sessions: 'users/registrations' }
# map.login '/login', :controller => 'sessions', :action => 'new' ## 3rd try
# get '/dash', :controller => 'dashes', :action => 'show' ## 2nd try
# get 'dash', to: 'dashes#show' ## Original named route
resources :dashes
resources :campaigns
resources :players
resources :countries
root "campaigns#index"
# yank later
resources :neighborhoods
end
Rails results on server ...
Paths Matching (dashes):
dashes_path GET /dashes(.:format)
dashes#index
POST /dashes(.:format)
dashes#create
Paths Containing (dashes):
dashes_path GET /dashes(.:format)
dashes#index
POST /dashes(.:format)
dashes#create
new_dash_path GET /dashes/new(.:format)
dashes#new
edit_dash_path GET /dashes/:id/edit(.:format)
dashes#edit
GET /dashes/:id(.:format)
dashes#show
PATCH /dashes/:id(.:format)
dashes#update
PUT /dashes/:id(.:format)
dashes#update
DELETE /dashes/:id(.:format)
dashes#destroy
My form is standard rails generated & works whenever I remove the named route ... edit.html.haml renders the _form partial ...
%h1 Editing dash
= render 'form'
= link_to 'Show', #dash
\|
= link_to 'Back', dashes_path
_form.html.haml
= simple_form_for(#dash) do |f|
= f.error_notification
.form-inputs
= f.input :name
= f.association :user
= f.association :dashcampaigns
= f.association :dashplayers
.form-actions
= f.button :submit
As described in this question, typically the issue is when you confuse between collection (plural) and member (singular) controller actions.
A collection controller action is an action that does not have an ID since it does not manipulate an existing resource (dash) or since it works on a group of resources. The RESTful collection actions are: index, new, and create. Each of them has a helper method and a "verb" (method):
index: url: dashes_path, method: :get (method get is default)
create: url: dashes_path, method: :post
new: url: new_dash_path, method: :get
Note how both index and create share the same plural URL helper method dashes_path. Only the method option differentiates between them.
A member controller action is an action that has an ID of the resource it is manipulating (one particular dash). The RESTful collection actions are:
edit: url: edit_dash_path(#dash), method: :get
show: url: dash_path(#dash), method: :get
update: url: dash_path(#dash), method: :patch
destroy: url: dash_path(#dash), method: :destroy
See how except edit, all other actions use the same singular URL helper method dash_path(#dash).
You can find further details on this in the guides.
Now, the "dot instead of slash" symptom is when you mistakenly try to point to a member action in a way that should be used for collection actions. So if you try to do dashes_path(#dash) - there is no such thing. The only parameter dashes_path accepts is format, which is added to the URL in the end after a dot, that's why you're seeing a weird URL such as /dash.6.
Rails form builder form_for and extensions such as simple_form_for need to decide whether to point the form action at the #create collection action (when rendering the #new collection action) or #update member action (when rendering the #edit member action). They do so "magically" by taking a look at the object you give them when you do simple_form_for(#dash). If #dash.new_object? is true, they point the form action to #create, if it's false they point it to #update.
In your case when you used it "out of the box" it was all good. It started acting up on you when you added this line to your routes.rb:
get 'dash', to: 'dashes#show'
By default show is a member action, not a collection action. It must receive an ID. I think that this is why Simple Form is acting up on you. Instead of that alias, try this one:
get 'dash/:id', to: 'dashes#show'
Let us know if this fixes the issue.
And in general, I recommend to "work with" the RESTful routing rather then "working against" them. It is very rare to need to add named routes in routes.rb. There are exceptions, but I don't think it is justified in your case to deviate from conventions and try to use dash/1 rather than dashes/1. "Working with" Rails will get you the productivity boost it is known for, not "working against" it by trying to force it for relatively small details like this one.

ActionController::UrlGenerationError using link_to

I have a controller Posts in which I have a method:
def report_user
...
end
I have a view where I would like a link that will perform some logic (it should NOT actually take the user to a different page, only perform the logic and possibly show a dialog box after completion). The logic is contained in the report_user action in the Posts controller:
<%= link_to "Report User", :controller => :Posts, :action => :report_user %>
I would ultimately like to pass some variables also to the report_user action, however I haven't gotten that far as I've come across this error:
No route matches {:action=>"report_user", :controller=>"Posts"}
message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
raise ActionController::UrlGenerationError, message
end
I'm not sure what the issue is. There is definitely an action in the Posts controller called report_user and it is not private. I'm not sure what the missing required keys means either. I've seen on SO other people with that error, but they all have routes defined that require parameters. I do not have any routes defined for this. Possibly I"m going about the entire thing in the wrong way?
As Nils suggested, you need an entry in routes.rb. Assuming that this is a member route using a GET request, that entry would look like this.
resources :posts do
get :report_user, on: :member
end
Next, you need to update your link to use the routing helpers that Rails provides.
<%= link_to "Report User", report_user_post_path(#post), remote: true %>
I included the remote: true option b/c you mentioned that clicking the link shouldn't reload the page. The default response for this request will be app/views/posts/report_user.js.erb.
I would encourage you to read up on Rails routing at http://guides.rubyonrails.org/routing.html.

Ruby on Rails Routes Clarification

I'm not completely new to Ruby on Rails but it is not my most proficient framework so I was hoping someone could help me wrap my head around some code I'm trying to understand.
controller:
def new
#biz = Business.new
end
def apply
#biz = Business.new(business_params)
token = SecureRandom.hex(4)
#biz.verify_token = token
if #biz.save
message = #biz.sentMessages.new
message.send_verify_email
redirect_to waiting_verification_path
else
render 'new'
end
end
routes:
get 'apply', to: 'businesses#new', as: 'apply'
post 'apply', to: 'businesses#apply', as: 'applied'
view:
= simple_form_for #biz, url: apply_path, html: {autocomplete: "off"} do |f|
I understand that the first line of the routes directs a request at /apply to the new action in the controller which creates a new business object and renders the new view, which I have included a snippet from. This snippet includes the form action which directs a successful submission to the apply_path.
My understanding indicates that this apply_path is a named helper for the first routes line when I believe it should be directed to the second line, whose helper would be applied_path, and would then be handled by the apply action on the controller.
What is really causing me confusion is that this functionality works. So the submission is in fact being routed to the second routes line and being handled by the apply action in the controller. If you could explain how this is happening I would appreciate it tremendously. Thank you.
The line
= simple_form_for #bix, url: apply_path
generates something like
<form action="/apply" method="post">
..
</form>
POST is the default method on forms, so when the form is then POST-ed Rails looks at the routes.rb file and sees a match on this line.
post 'apply', to: 'businesses#apply', as: 'applied'
If however you had done it like this
= simple_form_for #bix, url: apply_path, method: :get
The form is submitted via GET and then Rails would find a match on the first line in routes.rb.
get 'apply', to: 'businesses#new', as: 'apply'
It doesn't matter what the name of the routes_helpers are, all that matters is the generated url. I hope that clears thing out, if not, just ask for more clarification.
The reason this is working is that you have two named routes that map to the same path. applied_path is /apply, and apply_path is /apply. These two named paths are IDENTICAL. The difference comes when the form performs a POST to /apply, it sees a POST and immediately goes to the second route. While a GET to that same /apply path will go to the first route.
On this note, you only really need to name one of the routes and just use that name everywhere. This is why, when you declare a resource in your routes file:
resources :users
You end up with a "POST /users" and a "GET /users" path which routes to users#create and users#index respectively, based on what kind of request was sent - BUT you only have one users_path named route, which always returns "/users"
The generated form will look like
<form action="/apply" method="post" autocomplete="off" class="simple_form new_business">
Note the method attribute: Because #biz is a new record, SimpleForm knows this should be HTTP POST'ed to the url specified (AFAIK persisted records will still create a method="post" on the form, but an additional hidden field with name="_method" and value="put" is inserted).
Since both route helpers, apply_path and applied_path, just generate the string '/apply', it does not matter which URL you define in your form. This should equally well work:
= simple_form_for #biz, url: applied_path, html: {autocomplete: "off"} do |f|
When you submit the form, your browser creates an HTTP request, like this:
POST /apply HTTP/1.1
<header information>
<post body>
and Journey (the Rails router module) will take the information POST + /apply to find the route to businesses#apply.
To make the route definition a little more concise, you could rewrite it as
scope as: :apply do
get '/apply', to: 'businesses#new'
post '/apply', to: 'businesses#apply'
end

Rails routing issue

Within a rails app I've got a special url (/profile) that I'm mapping to the user controller to allow a user to view and edit their profile. I made the following entries into my route file so that the "get" for profile will route me to the profile action in the user controller and the "post" for profile will route to the update_profile action in the user controller.
match '/profile', :to => 'users#profile', :via => "get"
match '/profile', :to => 'users#update_profile', :via => "post"
If I run rake routes I see the following two entries
profile GET /profile(.:format) {:controller=>"users", ":action=>"profile"}
profile POST /profile(.:format) {:controller=>"users", ":action=>"update_profile"}
If I try hitting the dev url http://localhost:3000/profile it brings up the profile page as expected. If I press the form button it gives me the following error:
Routing Error
No route matches "/profile"
Looking at the generated html on the initial page I see the following form tag, so it seems the action is correctly set.
<form accept-charset="UTF-8" action="/profile" class="edit_user" enctype="multipart/form-data" id="edit_user_1" method="post">
So what am I missing? By looking at the rake routes output, I was assuming this would work as is. Am I thinking about my http verbs incorrectly? Any help would be appreciated.
I see a few problems here.
First, these are actions for a specific user. So the route should probably be 'profile/:id'
Secondly, show and update are automatically generated when you scaffold a resource. What is the benefit of renaming the actions and mapping them to custom URLs? Breaking the auto-generated Rails RESTful routes is often asking for trouble. It can be done, but you should have a good reason for doing it.
Third, your routes look funny to me. There are some quotes where they shouldn't be. Is that how they appear in your console when you run rake routes?

Routing error with Rails 3 with members

I have the following route in rails 3:
resources :jobs do
member do
post :seller_job_submitted
end
end
And the following form
=form_for job, :url=>seller_job_submitted_job_path(job), :remote=>true do |f|
I know it's not very restful, but it's kind of a stop gap for now. In any case, I keep getting this error when submitting the form
Started POST "/jobs/74/seller_job_submitted" for 127.0.0.1
ActionController::RoutingError (No route matches "/jobs/74/seller_job_submitted"):
but when I run rake routes | grep seller_job_submitted, I think the correct results come up:
seller_job_submitted_job POST /jobs/:id/seller_job_submitted(.:format) {:action=>"seller_job_submitted", :controller=>"jobs"}
Any ideas about what might be going on?
Thanks!
Assuming you have defined method seller_job_submitted in model and controller.
Replace your code with
resources :jobs
match "jobs/:id/seller_job_submitted" => "jobs#seller_job_submitted", :as => "seller_job_submitted"
Then in form_for tag use :url=>seller_job_submitted_path
This should fix your problem: you did not define seller_job_submitted_job_path explicitly.
Perhaps use put instead of post? Or use :post as the method in the submit form.
You can tell if this is the issue by looking at what the REST method is for the generated form (look for the hidden field in the page source).
So in short, maybe Rails is somehow expecting a POST on that URL but it's receiving a PUT.
Yes, this is a regression bug with Rails 3.
It turns out you need to be careful about using POST in your routes.rb.
resources :jobs do
member do
post :seller_job_submitted # will not work
put :seller_job_submitted # will just work
end
This is even though the FORM method says POST.

Resources