Rails generate update form with post instead of put - ruby-on-rails

I am using Rails 4.
I have a stream model which has the following routing code in routes.rb:
namespace :admin do
resources :streams, param: :stream_id
end
I get the following routes:
admin_streams GET /admin/streams(.:format) admin/streams#index
POST /admin/streams(.:format) admin/streams#create
new_admin_stream GET /admin/streams/new(.:format) admin/streams#new
edit_admin_stream GET /admin/streams/:stream_id/edit(.:format) admin/streams#edit
admin_stream GET /admin/streams/:stream_id(.:format) admin/streams#show
PATCH /admin/streams/:stream_id(.:format) admin/streams#update
PUT /admin/streams/:stream_id(.:format) admin/streams#update
DELETE /admin/streams/:stream_id(.:format) admin/streams#destroy
For new stream there is no problem, rails generates the correct form attributes for the create method.
My problem is when I try to generate a form for update. As mentioned in this answer, I code of the form is:
<%= form_for #stream do |f| %>
:
:
<% f.button %>
<% end %>
However, this is what rails generates:
<form accept-charset="UTF-8" action="/streams/xxxx" class="edit_stream" id="edit_stream_4" method="post">
As you can see, from some reason the method Rails choose is post instead of put.
I know I can override the method manually, but I find it hard to believe that this is what I need to do. Any suggestions?

That is totally correct. Take a look at this:
http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-patch-put-or-delete-methods-work-questionmark
It's just a Rails workaround to the fact that not all browsers support PUT method. So Rails emulates POST method but knows it is really a PUT.
If you better check your form, you'll find a hidden field like this:
<input name="_method" type="hidden" value="put" />

HTML form tag takes only get or post as values for method attribute.
See Documentation for form tag.
In order to support RESTful routes other than GET and POST(already supported by form) i.e., PATCH, PUT and DELETE requests, Rails uses a work around by creating a hidden input field in the form which it later tracks down to decide the type of HTTP request.
For eg:
<input name="_method" type="hidden" value="patch" />

By default the form_for helper uses POST. But the form_for method can detect if the object passed to it has been persisted or not. If it has persisted, then it recognizes that you are doing an edit and specifies a PATCH method on the form.If not it automatically uses POST! I hope this helps!

Related

Rails :method=>:patch doesn't work

So, I have this form declaration:
<%= form_for 'students_list', {:url => update_students_list_stream_url(#stream), :method=>:patch} do |students_list_form| %>
Just as described in API docs, but this leads me to error:
No route matches [POST] "/streams/26/edit-students-list"
So it still tries to post, even though my HTML input has:
<input type="hidden" name="_method" value="patch" />
From Rails guide:
Rails works around this issue by emulating other methods over POST
with a hidden input named "_method", which is set to reflect the
desired method:
I'm quite confused
I was looking for an answer on why rails loaded method patch as post in the rendered form.
If you ended up here looking for that like I did, this is the answer you are looking for:
https://stackoverflow.com/a/46699512/5750078
From https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-patch-put-or-delete-methods-work-questionmark:
Rails use Rack::MethodOverride middleware that tweaks the HTTP methods PUT/PATCH to POST for supporting old browsers.
This can happen on Rails API application to unload the middleware for performance. In some cases where you want to call a casual PUT/PATCH request using form_with tag in the views, simply add
# config/application.rb
config.middleware.insert_after Rack::Runtime, Rack::MethodOverride
You'll be better doing this:
<%= form_for #stream do |student_form_list| %>
If you've set up your routes using the standard resources directive, you'll have the following routes:
Of these routes, the update path should just be students_list_stream_path -- not the update_students_list_stream_path you have now.
If you've set up the form_for to use the correct object, it will automatically set the path & method for update.

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

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

submitting params with form_tag to update method gives undefined route error

I have a form like this:
= form_tag item_path(#item) do
# some inputs
This gives following HTML:
<form method="post" action="/items/1" accept-charset="UTF-8">
<!-- some inputs -->
</form>
When I submit it - I get a following error:
No route matches [POST] "/items/1"
However when I use SimpleForm for the same item - HTML is almost the same and it works:
# code:
= simple_form_for #item do |f|
...
# output:
<form id="edit_item_1" novalidate="novalidate" method="post" action="/items/1" accept-charset="UTF-8">
...
Do you see any mistakes in my code?
How do I make sure that my form_tag submits params to the item update method?
I am not sure why simple_form_for works. But normally Rails form_tag methods generates form with http method POST.
<form method="post"
So in your case if you want to submit the form to update method then http method should be PUT. You can explicitly specify the http method in rails form_tag.
form_tag item_path(#item), :method => :put do |f|
Normally people will always prepare form_for instead of form_tag and they will reuse the form template for create and update. Because form_for will set correct http method based on record status.If the record is new it will set the http method to POST else PUT.
Did you define the path in your routes.rb file?
resources :items, only: :create
or post "/items/:id" => "items#create"
I suggest the first one

convert search field input to url

Let's say I need a simple search form:
<form action="<%= search_path %>" methode="GET">
<input type="text" placeholder="where?" name="place_name" />
</form>
I need to generate a url based on the input field so I can show:
/search/some-value
I tried:
match 'search(/:place_name)', to: 'some_controller#some_action', as: 'search'
Any suggestions why it's not working or what I'm doing wrong here?
If you submit a form, the parameters set via inputs within the form will be appended to the URL using query parameters — in your case that would look something like this:
/search?place_name=some-value
That’s the basic form behavior and there’s no easy way around this unless you want to use some Javascript to handle the request (which I would advice against in this case).
To use the default behavior however, your route would need to look like this:
match 'search', to: 'some_controller#some_action', as: 'search'
The parameter place_name will then be available in the controller using:
params[:place_name]
Hope this helps.
The following code supports unicode and additional parameters:
get "search/*place_name" => "store#search"
get "search" => redirect {|_, req|
params = req.query_parameters
query = Rack::Utils.escape params.delete(:place_name){"%"}
"/search/#{query}#{params.to_query.prepend('?') if params.any?}"
}, as: :search

Resources