Rails No route matches[POST] on second update - ruby-on-rails

I have a form in Rails like so:
= form_for cart, as: :cart, url: backoffice_cart_path(cart) , method: :put, html: { autocomplete: "off", class: 'cart-quoter' } do |f|
When its rendered it shows the following header
<form action="/backoffice/carts/4" ...>
When I update some stuff in the view I am returned to the same place, however, if I try to do the same stuff again, it throws me this error
No route matches [POST] "/backoffice/carts/3"
Which is exactly the same previous address that was being used.
Any ideas on what it could be?
Note:
The cart object does exist from the beginning, so I can effectively update the object the first time, but sending it again the second time the router does not find the specified route.
At the beginning the form_for code was like this:
= form_for ['backoffice', cart], html: { autocomplete: "off", class: 'cart-quoter' } do |f|
I just added more explicit code trying to force the form submission.
The route exists but just to be sure
backoffice_cart GET /backoffice/carts/:id(.:format) backoffice/carts#show
PATCH /backoffice/carts/:id(.:format) backoffice/carts#update
PUT /backoffice/carts/:id(.:format) backoffice/carts#update
DELETE /backoffice/carts/:id(.:format) backoffice/carts#destroy
The controller has an update method, no magic in there.

Replace this:
= form_for cart, as: :cart, url: backoffice_cart_path(cart) , method: :put, html: { autocomplete: "off", class: 'cart-quoter' } do |f|
by
= form_for cart, as: :cart, url: backoffice_cart_path(cart) , html: { autocomplete: "off", class: 'cart-quoter' } do |f|

The form object / record will have information on its own, on whether it is a new record or a existing record. You can check it by calling:
cart.new_record?
If it returns true, then it means this form is going to perform a POST /create according to the REST resource rules. If it returns false, then it will perform a PATCH to /update (standard is now PATCH as opposed to PUT)
Ideally, you should not have url or method parameters in the form tag for a REST resource / record. You don't even need as here, because if cart is an instance of a class named Cart, Rails will make sure everything is treated as cart, parameters wise.
In the end, your tag would look like:
= form_for cart do |f|
... form stuff ...
I understand you're using a namespace here, backoffice, so you could achieve it by something like:
= form_for [:backoffice, cart] do |f|
.... form stuff ....
Hopefully this should fix your problem. Make sure cart is coming from a source where it will always be a ActiveRecord model. And of course, your controller needs both create and update methods.

The answer to the problem is as weird as it can be.
The form has some manual input inside, like this one
input[type='text' value=item.net_unit_price name="cart[cart_items][][id]"]
One of them was written this way
input[type='text' value=item.net_unit_price name="cart[cart_items][price]"]
With some missing brackets. I fixed that difference and the controller is working correctly.
I don't fully understand why this is the solution for my problem, but it is something you should consider when encountering this kind of problem. Looks like a form that is filled with some errors brings trouble to the router, and later the controllers in rails.

Related

Rails form_with defaulting to incorrect method

I'm using Rails 6. I have a route that defines a get request:
namespace :admin do
get '/machines/search', to: 'machines#search', as: 'search_machines'
end
Then, I have a form_with that sets the url to the route. When the form is loaded, the HTML that's generated for the form contains a method="post" instead of method="get" which is what I would've expected since the route is a GET request and not a POST. I can add the method: "get" parameter to the form_with and this fixes the issue but I don't understand why Rails didn't pick up the correct method initially.
<%= form_with url: admin_search_machines_path() do |i| %>
<%= i.text_field :q, placeholder: "Search", autocomplete: "off", class: "debounce-form-submit form-control" %>
<% end %>
According to the documentation form_with does default to POST if method is not specified. Which is not 100% correct, as it will switch to PATCH (via hidden field trick) if the model is persisted (source).
Up to today, the code does not look at the route itself to specify the method.
I think the (backwards incompatible) automagic you suggest is not totally off, though. You might ask about that in the rubyonrails forums and get feedback whether this would be a welcomed change. Personally, I never expected it to inspect the route. Also, you might have the same route with post and get methods, which would make lookup more involved than what meets the eye on first sight.

Rails form_for that uses STI base class

I've got a pretty simple (I think) single-table inheritance (STI) setup in my Rails app.
There is a User model with a nested resource Post.
Using STI, I have it so that some Posts can be Post::Urgent objects.
I noticed that my URL helpers like <%= [#user, #post] %> needed to be hard-coded to <%= user_post_path[#user, #post] %> or else I'd end up seeing errors about Rails not finding the user_post_urgent_path method. Okay, that was reasonably easy.
(An alternate way of doing this is also <%= [#user, post: #post] %>.)
Well, for some reason I'm not able to figure out how to make the form_for method adjusted in the same way. When I write simply
<%= form_for [#user, #post] do |f| %>
, I get similar errors to the URL helper case, but in the form context: undefined method 'user_post_urgen_path'.
I fixed this by specifying:
<%= form_for [#user, #post], url: :user_post do |f| %>
But this doesn't completely work, because when I go to submit the form, I get problems in my controller, saying that strong parameters line params.require(:post) failed:
param is missing or the value is empty: post
If I inspect the params I find that the form is passing a post_urgent object and not a post object.
I could, of course, manually put in some logic that always says if !params[:post] and params[:post_urgent], then params[:post] = params[:post_urgent]. But that just seems way too hacky, especially for Rails.
Is there a better way to make a form like this "just work" regardless of what subclass of the Post model it actually happens to be?
Not sure if you found a solution already, but I am using the following for my forms
= form_for [#user, #post.becomes(Post)] do |f|
- f.object = #post.becomes #post.class
reference: http://thepugautomatic.com/2012/08/rails-sti-and-form-for/
I had some nested models initialized in the controller, empty unsaved models to work with accepts_nested_attribues_for and becomes empties them for some reason, so instead, I acted on the controller strong params, not the cleanest, I know.
def unpermitted_model_params
polymorphic_form_params = params.to_unsafe_hash
.slice('sub_model1', 'sub_model2')
form_values = polymorphic_form_params.first.to_a.second
ActionController::Parameters.new(parent_model: form_values)
end
def allowed_params
unpermitted_model_params.require(:parent_model)
.permit(:type, :etc, :etc)
end

rails 3 form_tag method: :get to include only the fields that were changed

I'm writing a search form that will reload the page adding to the URL the search parameters.
Currently the URL generated by my form contains all the form fields, including the empty ones. This clutters a lot the url generated with data that is often not needed.
I would like my form to redirect to an url that only has the fields that contain data.
There's the code for my form:
= form_tag orders_path, method: :get do
= text_field_tag :search, params[:search], class: "span2 appendedInputButtons"
= select_tag "order_types",
options_from_collection_for_select(OrderType.all, :id, :name),
multiple: true,
size: 8,
include_blank: "select"
%button.btn.btn-primary Update list
When I hit the submit button without filling any of the form I get redirected to an url like this:
http://localhost:3000/orders?utf8=%E2%9C%93&search=&order_types%5B%5D=
while I would like the url to be like this:
http://localhost:3000/orders
and if I select only some of the order_types I get:
http://localhost:3000/orders?utf8=%E2%9C%93&search=&order_types%5B%5D=3&order_types%5B%5D=6
while I would like it to be like:
http://localhost:3000/orders?order_types%5B%5D=3&order_types%5B%5D=6
or even better:
http://localhost:3000/orders?order_types=3,6
or something similar, but more concise,
Thanks a lot for any help,
form_for binds the form to an object and rails internals will namespace the fields related to this object (or objects) with a prefix, so that when the form is submitted the data will appear in params as params[:prefix][:some_data].
To reduce the size of the query string I would suggest you to use form_tag and build the form manually. In this way you can have full control over the submitted fields and also avoid the utf8=%E2%9C%93 utf8 enforcement snowmen hack.

simple_form_for: Removing root model name from params

When I create a form using simple_form_for #model, upon submit the post params has all the attributes grouped under params[model]. How do I get simple_form to drop this grouping and instead send it directly (under params root)?
<%= simple_form_for #user, do |f| %>
<%= f.input :name %>
<%= f.input :password %>
<%= f.submit %>
Now the name and password attributes would normally be sent under params[:user][:name], params[:user][:password] etc. How do I get simple_form to post these as params[:name], params[:password] etc.?
Thanks!
Ramkumar
ps: In case you are wondering why I need this, the bulk of my app is to serve as an API and I have built a set of methods to validate a request which expect some attributes to be in root. In a rare instance (forgot password), I actually need to present a form and I am looking for a way to use these methods.
you can explicitly define the name for an input by passing input_html to it:
input_html: { name: :name }
(needed this myself for sending an resource to a thirdparty endpoint with redirect to my side which relied on the plain attribute names, but i actually wanted not to built up label and input via the tags ;) )
also see simple form builder impl
Two ways I can think of:
The first is, don't use simple_form to build your form, but do it by hand or with the form_tag and *_tag methods. These will allow you to more closely specify what parameters are used in your form.
If you want to keep simple_form, though, then have it call a different controller action. Refactor the controllers to strip out the logic into a separate method. Something like:
class UsersController
def create_from_api
controller_logic(params)
end
def create_from_form
controller_logic(params[:user])
end
def controller_logic(params)
[actual work happens here]
end
end

routing scope problem with form_for (partial)

Trying to route:
scope :shortcut do
resources :text_elems
end
Using basic scaffold with form partial
*_form.html.erb*
<%= form_for(#text_elem, :shortcut => #shortcut) do |f| %>
...
Problem is: When I call the edit action, the form html shows as:
<form ... action="/25/text_elems/25">
Note: The new action renders the form action correctly:
<form ... action="/home/text_elems">
So it appears that my :shortcut param is getting trumped by the :id param when form_for processes it's block. Now I am able to get the action to correctly route with the :shortcut param if I manually make the :url => {...} in the form_for block, but I would prefer to keep the code dry, plus I want to report this problem to rails if it is indeed a bug.
Can anyone else confirm this as a bug?
Actually, you can pass the values as a full hash, rather than trying to rely on the default to_param (which is what gets called if all you do is pass the #text_elem)
<%= form_for({:id => #text_elem.to_param, :shortcut => #shortcut}) do |f| %>
however, if this is actually a nested-resource, you could also do:
<%= form_for([#shortcut, #text_elem]) do |f| %>
I was having the same issues and none of the above answers helped.
The last answer on this page worked for me though...
https://rails.lighthouseapp.com/projects/8994/tickets/6736-problem-with-scoped-routes-and-form_for-helper

Resources