Submitting a form with GET method which doesn't match route - ruby-on-rails

I want to filter results by category and I'd like to use the GET method instead of POST. However, I am doing something wrong that I can't figure out: the form's action does not match the defined route, so it triggers a different method.
Here's the form:
<div>
<%= form_tag '/expenses/search', method: 'get' do %>
<%= select_tag 'category_name', options_from_collection_for_select(Category.order(:name), :name, :name) %>
<%= submit_tag 'search' %>
<% end %>
</div>
Sending this form produces an URL like the following:
http://localhost:3000/expenses/search?utf8=%E2%9C%93&category_name=Alcohol&commit=Search
However the route is defined like this:
resources :expenses
get 'expenses/search/:category_name', to: 'gastos#search_by_category'
This means the URL where the form is submitted isn't the one I'm trying to submit it to. It's matched with the one corresponding to the show method, as you can imagine.
How can I submit the form to the matching URL? What is the usual way to deal with this situation?

You didn't set your route properly as it has unrecognized :category_name segment. Your route should be defined like this:
get 'expenses/search', to: 'gastos#search_by_category`

If your route is nested on expenses I recommend to use block function
resources :expenses do
collection do
match 'search', to: 'gastos#search_by_category`, via: :get
end
end
Is a good practice to use rials routes helper, try to avoid put routes with plain text, in your case will be:
<%= form_tag search_expenses_path, method: 'get' do %>
<%= select_tag 'category_name', options_from_collection_for_select(Category.order(:name), :name, :name) %>
<%= submit_tag 'search' %>
<% end %>
Furthermore, don't confuse 'query params' with 'url params'
http//www.host.com/profile/12?type='json'
In this example '12' is a url param and is expresed with :(nameofparam) in routes files but 'type' is a query param that are not expresed on rails routes.

It's supposed to work like that, since it's client-side.
You see, parameters are sent by the browser, that (in general) has no understanding of how your site routing works inside. Submitting a form, in general, requires an URL (to submit params to) and a set of parameters, which in case with GET typically* gets passed as a query string.
The browser will eventually hit the exact route that is specified in form's URL and supply all the form's parameters in a query string appended to the end in usual format:
...?category=stuff
You simply cannot expect the browser to hit a different route (which query string is not part of) with one form just because it has a different value in one of the <input>s.
* I've never actually seen this done differently, but I didn't find a firm requirement of this either.
Do you really want pretty search links that badly?
You could try to circumvent this by placing a "prettifying redirection" – direct search queries to that action, but do not perform search there: instead use the received parameters to construct a route and redirect your user to it.
def search_redirect
redirect_to whatever_search_path(category: params[:category])
end
That would trigger the route helper to build the pretty adress that conforms to the defined routes.
Too hacky?
Well, you could go with submitting a form through JavaScript and alter the parameters and URL request in any way you want. But this is still hacky and I wouldn't do either. Query string in search requests looks perfectly fine to me.

Related

form_for and form_tag parameters for custom action

I would like to use a drop action in the friendships controller to drop requests of friendship, and I am struggling to understand how to create a working form and working routes.
I added the following route in config/routes.rb
resources :friendships, only: [:create, :destroy] do
member do
post :drop
end
end
This would generate the named route drop_friendship_path(id).
My question is now how to create a working form, that is what parameters am I necessarily required to use with form_for or form_tag.
Since in view I would iterate on #users requesting a friendship, I would not know the id of the request to delete, so I cannot use the above named route. Which one of the following is correct?
<%= form_for(current_user.friendship_request.find_by(requester_id: user.id), url: { action: drop }) %>
<%= form_tag({controller: "friendships_controller", action: "drop"}) do %>
I struggled to find documentation on parameters to use with form_for and form_tag. The api documentation says that the :url
argument for form_for may be represented in the same way as values passed to url_for or link_to. So for example you may use a named route directly. Documentation on url_for or link_to however does not add anything else.
I found only examples, not an exhaustive documentation, in the form helpers section of guides.rubyonrails.org for form_for and form_tag, so I realized that url for form_for can have a hash of subparameters (however only action is reported) , and that the controller and action parameters can be passed to form_tag.
What is the complete list of parameters for the url hash in the form_for helper?
What is the relative list of parameters for the form_tag helper?
Which ones are required in my form?
If, instead of specifying controller and action, I used inside form_for or form_tag:
url: drop_friendship_path(#friendship_request)
and defined #friendship_request in the drop action, would that work?
A better way is to use button_to or link_to helpers for your purpose. button_to generates a form. link_to generates a link, but can also send post request with {method: :post} option.
Why do you think you can't use the drop_friendship_path(id) helper method? You can:
<% request_id = current_user.friendship_request.find_by(requester_id: user.id) %>
<%= button_to "Drop", drop_friendship_path(request_id) %>
<!-- or -->
<%= link_to "Drop", drop_friendship_path(request_id), method: :post %>
Why don't you use the existing destroy action to delete friendships instead of drop ?
And also, sending a separate query for each user to get a friendship record is not a good thing. You should think of how you can avoid this. There are many solutions, but it is not the subject of the question.

Rails: Why am I getting "No route matches"?

Snippet from routes.rb:
resources :templates do
post :add_rates
resources :rates
delete :remove_rate
end
Now I try to use the "add_rates" path in my form.
Tried both:
<%= form_for(#template.template_id, :html => {:class=>"form-horizontal"},:url=> { :action=>:add_rates}) do |f| %>
and:
<%= form_tag(:template_add_rates) do %>
But I'm always getting: No route matches {:action=>"add_rates", :controller=>"templates"}
Any help would be appreciated.
You're doing it wrong:
form_for [#template, :add_rates], html: { class: "form-horizontal } do
or
form_for #template, url: template_add_rates_path(#template), html: {class: "form-horizontal" } do
The template, and its ID, and the action you want (add_rates) all have to be passed in as the same parameter. You can't give it the template ID as the first argument, and then try to tack additional URL parameters onto it. Additionally, in both cases, you're missing key parts of the URL. In the first one, you're just giving it an ID, and :add_rates; Rails can't take an arbitrary number and know that it's a template ID that you're giving it. In the second case, you're giving it :template_add_rates; how is Rails supposed to know which template you're trying to add rates to, without a template ID? You need to give it all three pieces of the route you're trying to match: /templates/:template_id/add_rates.
There are also a bunch of other weird issues/errors with the code you've posted:
#template.template_id should be #template.id, unless you've explicitly deviated from Rails' conventions, which you should almost never do.
Your routes are pretty weird. You shouldn't be adding a add_rate route, you should be using the routes provided by your nested resources :rates line.
Your routes should look like this:
resources :templates do
resources :rates
end
This gives you routes like POST /templates/:template_id/rates for creating rates (instead of your add_rate route), and DELETE /templates/:template_id/rates/:rate_id for deleting rates (instead of your remove_rate route).

Ruby on Rails: form and radio button question

I have a form around a list of items. I would like to check a radio button on the list and then, after clicking 'Submit', go to the 'Edit' page of the selected item.
The problem is that if I am writing something like
<%= form_tag edit_item_path %>
but Rails complains that I didn't provided a proper id, which in turn is being set on my radio buttons as the following:
<%= radio_button 'item', 'id', item.id %>
So - how would I send a form with a selected id to a given action?
Doing it the way you describe would not work, since edit_item_path by default RESTful path definitions is a GET request. You are trying to make it a POST request. If you would like to stick to the RESTful way of doing things I would recommend simply looping through your items and provide links to edit, since you are planning to edit them one at a time anyways. Otherwise, you would have to define a new route if you would prefer doing things within the form with radio buttons. If you are, than you would add something like this to your config/routes.rb: (assuming you are using rails 2.3.x)
map.resources :items, :member =>{:edit_radio_form => :post}
Than your form would look like this:
<%= form_tag edit_radio_form_item_path do |form| %>
And it should work, but it not the right way of doing things for several reasons, the most anoying one of which is that you won't get a full URL on your edit page because you got there with a POST request, so if you refresh the page, your item id param will be gone and will produce an error.

Using named routes with parameters and form_tag

I'm trying to create a simple search form in Rails, but I think I'm missing something.
I have a named route for search:
map.search ":first_name/:last_name", :controller => "home", :action => "search"
I'm trying to use that in my search form:
<% form_tag(search_path, :method => 'get') do %>
<%= text_field_tag(:first_name) %>
<%= text_field_tag(:last_name) %>
<%= submit_tag("Search") %>
<% end %>
But when I load up the search form I get an ActionController::RoutingError:
search_url failed to generate from {:action=>"search", :controller=>"home"} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: [:first_name, :last_name] - are they all satisfied?
What am I missing? I thought the fields defined in my form would be automatically linked up with my route parameters. :-/
Update:
I understand that search_path is generated before the form is displayed now, so it can't be updated. Obvious in hindsight!
I changed my routes:
map.search 'search', :controller => "home", :action => "search"
map.name ':first_name/:last_name', :controller => "home", :action => "name"
And now the search action just does:
def search
redirect_to name_path(params)
end
It all works a treat! The main goal here was getting that URL from the name named route as result of doing a search. Thanks guys!
form_for generates form and it has to have specified all parameters that are needed to create search_path, so it should look like:
<% form_tag(search_path, :firstname => 'some_text', :lastname => 'some_text', :method => 'get') do %>
or at least something like this. HTML tag form has parameter action='/some/url' and that's why you have to specify all parameters for search_path. But the above example won't work as you expected.
So what you can do?
Create empty form that has action='/' and with js replace it with content of your input text fields before submitting.
Create another route, on example /search that recives parameters from submit and then redirects to correct path.
Probably there is also some better ways to do it ;)
First, search_path is actually a method, which takes a hash of options. It is this method which should receive :first_name and :last_name.
Second, a browser can only submit form parameters as the body of a POST request, or as query string parameters (for any kind of request method). So there's unfortunately no way a browser's native submit function can generate that kind of URL.
Another way of thinking of it: What you're doing here is filling the form tag's action attribute with an URL. Rails needs a complete URL as you're building the form. So all parameters in your route need to be specified when the form helper is called, rather than at the next POST request.
So unfortunately, what you're trying to do is not possible in a normal Rails application.
(If you really want to, you might be able to pull it off by writing your own form helpers and a bit of Javascript to replace the browser's native submit function. The Javascript would then construct that URL based on the form fields. I'd argue against it, though.)

Send querystring params as part of form post

Is there a way to capture the querystring and send that along as part of a form post? I'm using Rails 2.3.5 and my user is on a page that has multiple querystring parameters. On this page, they are going to submit a form. Inside the action that receives the post, I want to know what those querystring parameters were. Obviously, they are not sent as part of the post. So I need the actual form values, plus the querystring params that were on the page when the user submitted the form.
I'm sure I could write some nasty javascript that would shove the querystring params into hidden fields on the form so they would be available, but that seems ugly. My Googling hasn't turned up much, which makes me wonder if I'm just going about this all wrong. To make matters worse, I'm a Rails newbie.
Appreciate any pointers or ideas to get me going in the right direction.
A friend of mine showed me what I believe is an easier way:
<% form_tag params.merge(:action=>"someAction") do %>
Merging params into the hash necessary for making the form_tag did the trick perfectly.
The preferred way would be to use hidden fields. I haven't tried it, but I think you can specify additional query string parameters within the *_path or *_url helpers. Something like:
<% form_for(#post,
:url => post_path(#post, :foo => 'foo', :bar => 'bar')) do |f| %>
...
<% end %>
<% form_tag params.merge(:action=>"someAction") do %>
- No route matches [POST]
Use hidden_field_tag if you're using a GET request.
In our case we were using a simple form with a select for setting the Per Page values for pagination. We found that any existing GET params were cleared when submitting this form. To fix this we used hidden_field_tags in our form.
Inside of your form, just set hidden_field_tags for the existing GET params, like so:
form_content = request.query_parameters.collect do |key, value|
hidden_field_tag key, value
end
This will ensure that your existing params persist.

Resources