convert search field input to url - ruby-on-rails

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

Related

Submitting a form with GET method which doesn't match route

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.

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 generate update form with post instead of put

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!

Passing parameter to form

I have a route like this:
match '/:search/:page', :to => 'search#create'
and in my form:
<%= form_for :search, :url => {:controller => 'search', :action => 'create', :page => 1, :search => ???} do |f| %>
I need to pass something to :search but I want it to be the value that is submitted with the form. What do I do?
There is no straight way to do that as form with url (action) must be rendered before user types anything into search. So you need initial value, but it may not make sense when you don't know what user wants to type in. GET form with parameters would solve it but I suppose you want "nice" search urls.
Generally there are two options:
You can't change URL path by simple form submittion - only params (?x=1&y=2...) may be added. But you can modify whole URL by JavaScript when user types something to search input (onchange) or submits form (onsubmit).
Use another action which will receive standard form input values then redirect it to proper url. E.g. /searchforward?search=Query&page=1 should forward to /Query/1.
routes.rb:
match 'searchforward', :to => 'search#searchforward'
match '/:search/:page, :to => 'search#create', :as => :search
controller:
def searchforward
redirect_to search_path(params[:search],params[:page]
end
If you set a value in form_for, where you have it now, it will be part of the address the form is sent to, therefore you will be able to get it in the controller through params[:search].
You can also make a text field (text area, select box, etc.) for the user to fill the value, as e.g. described in APIDock. You can even have a hidden field, which will not be displayed to the user, but still submitted with the form. In any case, it will be available through params[:search].
Edit Another way of understanding your question is that you want the search parameter in form_for :url to be passed dynamically based on whatever user typed. In this case I can think of three solutions:
Option 1. Submit the form to somewhere independent of :search and redirect from there:
Form: form_for :search, :url=>{:controller=>"search", :action=>"handle"}
SearchController:
def handle
case params[:search] of
when :foo then redirect_to some_path(params[:search], ...)
when :bar then redirect_to other_path(params[:search], ...)
end
end
The disadvantage is that you cannot make a POST-redirect, only GET. The advantage is that the form is processed in one place. Since it is - why not including the whole handling there and skip redirecting? That would be an option I would consider.
Option 2. Use some JavaScript to alter the action parameter of the form HTML element before submission.
Option 3. Use some JavaScript to generate a form based on an earlier selection.
In this scenario there are two forms, only one of which is user-submittable through a button. The first form contains only a drop-down box (text box, radio buttons) and a JavaScript form observer. Based on the value this observer displays the proper form - note that the :search value entered by the user is known at this point when the form is rendered.
Of course, insead of the first form you can use a collection of links if the choice of :search options available to the user is limited. Option 3 is probably not ideal in terms of performance, but should be nice enough to extend, understand and reuse it later.
There might be some other ways to do it, though.

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.)

Resources