Rails routes generate Post request for New action in nested resources - ruby-on-rails

I have the following nested resources:
resources :listings do
resources :offers do
member do
put "accept"
put "reject"
end
end
end
In my listings/show.html.haml, I have
= button_to "Make Offer", new_listing_offer_path(#listing)
Now, when I click button, rails generate a POST request, and thus an error:
Started POST "/listings/2/offers/new" for 127.0.0.1
ActionController::RoutingError (No route matches "/listings/2/offers/new"):
If I refresh (GET request), then the page displays correctly.
I believe this incorrect routing only happens when I added two extra actions: accept and reject, which happens to be POST actions.
Is it a bug in Rails, or it is my fault? How should I prevent this error?
Thank you.

The button_to helper creates a form for you which by default will send a POST request to the URL you've specified ("/listings/2/offers/new").
The routing you've specified will not generate a route to handle a POST request to /new. You can inspect your generated routes and the verbs to which they will respond by running the "rake routes" task.
If you are looking to merely link to the form, change your "button_to" to a "link_to" and add CSS for aesthetics.
= link_to "Make Offer", new_listing_offer_path(#listing)
(this GET would route to your OfferController's new action)
If you are looking to actually POST data, you will likely need to change your usage to:
= button_to "Make Offer", listing_offers_path(#listing)
(this POST would route to your OfferController's create action.)

Related

Rails 5.1+ routing: `resources` not working but explicit route definition does

Update
This was a legacy app I inherited, and I found out that the previous developers had removed the rack code that converted browser POST requests into PUT/PATCH based on the _method param that Rails adds to your forms.
# config/application.rb
# This is the line that caused the problem...
config.middleware.delete ::Rack::MethodOverride
Once I removed that line and restarted the server, things worked as expected.
Original Question
When I post a Rails form using the standard resources in the routes file, it raises a route not found error when I'm trying to update an existing record:
No route matches [POST] "/admin/lookups/record_types/1"
The model is namespaced as app/models/lookups/record_type.rb
# model file
module Lookups
class RecordType < ApplicationRecord
# ...
end
end
# form in view file
<%= form_with model: #record_type, scope: :record_type, url: [:admin, #record_type], local: true do |form| %>
<%= form.text_field :value %>
<% end %>
# Request being sent
POST admin/lookups/record_types/1
{ record_type: { _method: "patch", value: "value" } }
# in routes .rb
namespace :admin do
namespace :lookups do
# Does not work
resources :record_types
# Works when explicitly written out
post "record_types/:id", controller: record_types, action: :update
end
end
When I explicitly write out the POST request in the routes.rb file, it works as expected.
I know that Rails is actually POSTing the request and using the _method hidden attribute to map the routes file. However, something isn't converting that request properly.
It's an application I inherited, and at one point it was exclusively an JSON API (no direct UI), so I'm wondering if there was something removed that converted the Rails _method param to the proper controller? I don't know what that would be, though.
This is the output of my rake routes:
admin_lookups_record_types
GET /admin/lookups/record_types(.:format)
admin/lookups/record_types#index
POST /admin/lookups/record_types(.:format)
admin/lookups/record_types#create
new_admin_lookups_record_type
GET /admin/lookups/record_types/new(.:format)
admin/lookups/record_types#new
edit_admin_lookups_record_type
GET /admin/lookups/record_types/:id/edit(.:format)
admin/lookups/record_types#edit
admin_lookups_record_type
GET /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#show
PATCH /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
PUT /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
DELETE /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#destroy
The problem seems to be the use of the scope argument to the form_with method.
If you take a look at the routes you'll note that the route to the update action uses PUT or PATCH whereas the route to the create action uses POST.
As the documentation of the FormHelper module states:
in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the form will be set to POST and a hidden input called _method will carry the intended verb for the server to interpret.
But nesting the special _method key inside record_type breaks this mechanism. Is the scope really necessary? I'd try removing it, it should work fine without it. The correct HTTP verb to use would be PUT or PATCH. Adding an additional POST route breaks the regular Rails structure without any real gain.

custom route caught by REST update action

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
[...]

Rails - No route matches POST

I have the following in my routes.rb file:
post 'report/mnps/generate' => 'report#mnps_generate', as: 'report_mnps_generate'
Then, in my reports/mnps.html.erb view I have this:
<%= button_to report_mnps_generate_path %>
However, this button redirects to a post method at reports/mnps. Why is this button redirecting there instead of report/mnps/generate?
EDIT
rake routes returns:
Prefix Verb URI Pattern Controller#Action
root GET / home#index
report_index GET /report(.:format) report#index
report_mnps GET /report/mnps(.:format) report#mnps
report_mnps_generate POST /report/mnps/generate(.:format) report#mnps_generate
The definition of button_to states that the first parameter is its name, which is usually used as label. See here:
http://apidock.com/rails/ActionView/Helpers/UrlHelper/button_to
To get a link to the page, you need to write the button like this
button_to('Clickme!', report_mnps_generate_path)
The reason why it loaded the page you stated is that the button is actually on that very same page and is simply reloading it since no other destination was defined in your button_to call.

How did Rails generate this path from our model and controller

Ok I kind of understand this part: CRUD verbs and actions http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
and if I go to the route file of the example I have, I also see a resources :orders in it.
But now in the view of a partial names _carts I am seeing this code:
<%= button_to "Checkout" , new_order_path, method: :get %>
What confuses me is the new_order_path ? Where did that come from? What Rails convention rule is allowing us to right this? Especially where did that "new" come from?
When you use resources :orders in the routes, Rails creates 7 routes for new, create, show, update, destroy, list, and edit. All of them are given names, and new_order_path/new_order_url is related to the new action.
These routes are described at the http://guides.rubyonrails.org/routing.html#paths-and-urls
Those path helpers are automatically generated for resources defined in your routes.rb. You can check what route helpers are available by executing rake routes at the command line. They are shown in the left-most column in the table that prints out.
The general pattern of the paths that are created are like so by default:
new_{singular form of resource}_path - Routes to new on GET
edit_{singular form of resource}_path - Routes to edit on GET
{singular form of resource}_path - Routes to show on GET, destroy on DELETE, update on PUT (Soon to be PATCH in Rails 4)
{plural form of resource}_path - Routes to index on GET and create on POST.
There's also helpers that end in _url instead of _path that provide absolute URLs instead of relative paths. The particular action that is hit in your controller depends on the HTTP verb (GET, PUT, POST, DELETE, etc.) used when visiting those URLs.

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?

Resources