I am having index method to list all students. Now I am adding search in the index with one text_field_tag. My function is working but the search value is showing as query string in the url. I don't want this.
My view code:
<p>
<%= form_tag students_path, :method => 'get' do %>
Find by name: <%= text_field_tag :search %>
<%= submit_tag "Search", :name => nil %>
<% end %>
</p>
My controller code is
def index
searchString = params[:search]
if searchString
#students = Student.where("LastName like ?", "%#{searchString}%").paginate(:page => params[:page])
else
#students = Student.paginate(:page => params[:page])
end
end
My current url look like below:
http://localhost:3000/students?utf8=%E2%9C%93&search=A
I just need like /students. Please suggest the best practice
You need to use POST request instead of GET. In order to accomplish that, change your form_tag from:
<%= form_tag students_path, :method => 'get' do %>
to
<%= form_tag students_path, :method => 'post' do %>
What could possibly go wrong...
As #carlosramireziii mentioned, answering this question, requires a bit more work (and he is absolutely right!), so - there is update
1. Custom routes
If you are configuring your routes on your own, one of ways proceeding with this is to add a route, that will properly accept your requests. Take a look at config/routes.rb:
Rails.application.routes.draw do
match '/students', controller: :students, action: :index, via: [:get, :post]
# other definitions
end
Definition like this will create following route for you:
students GET|POST /students(.:format) students#index
This will make your new POST request nicely fall into students#index, generating expected result. However, if you are defining your routes via resources it's not that easy change. This leads us to...
2. Modifying standard resources routes
If routes for students are defined as follows:
Rails.application.routes.draw do
resources :students
# other definitions
end
the following routes are registered:
students GET /students(.:format) students#index
POST /students(.:format) students#create
new_student GET /students/new(.:format) students#new
edit_student GET /students/:id/edit(.:format) students#edit
student GET /students/:id(.:format) students#show
PATCH /students/:id(.:format) students#update
PUT /students/:id(.:format) students#update
DELETE /students/:id(.:format) students#destroy
This indicates, the POST request falls into students#create. In order to make it work, you need to change the create action, to something like:
class StudentsController < ApplicationController
# code omitted
def create
if params.include?('search')
#students = Student.where("LastName like ?", "%#{searchString}%").paginate(:page => params[:page])
render :index
else
# your current code from create action
end
end
end
While this will work, it is not recommended to do it this way. The create action does two things now, which is considered a bad pattern (or bad smell at least), and it is error prone.
So, what can we do?
Making search the right way
1. Custom search route
Define custom collection route:
Rails.application.routes.draw do
resources :students do
collection do
post :search
end
end
# other definitions
end
This registers following routes:
search_students POST /students/search(.:format) students#search
students GET /students(.:format) students#index
POST /students(.:format) students#create
new_student GET /students/new(.:format) students#new
edit_student GET /students/:id/edit(.:format) students#edit
student GET /students/:id(.:format) students#show
PATCH /students/:id(.:format) students#update
PUT /students/:id(.:format) students#update
DELETE /students/:id(.:format) students#destroy
You need to change your form_tag as follows:
<%= form_tag search_students_path, :method => 'post' do %>
And add search action to StudentsController:
class StudentsController < ApplicationController
# code omitted
def search
#students = Student.where("LastName like ?", "%#{searchString}%").paginate(:page => params[:page])
# If you don't want to create separate template for `search`,
# you can try to reuse your `index` template with
# render :index
end
end
2. Use students#index with params passed in URL
This approach is more REST-y, and it makes it available to pass the whole URL to someone else, so the another person is able to see exactly the same result of filtering, which is impossible with params "hidden" in POST request.
I hope I've covered all possibilities. If you have any questions - I'm more than happy to answer!
Good luck!
Related
With Rails 5, how do I create a form that submits to a RESTful action?
In my routes file I have:
resources :people do
collection do
get 'image/:id', :to => "people#image", :as => 'image'
get 'ranks', :to => "people#ranks", :as => 'ranks'
get 'search/:search(.:format)', to: 'people#search'
end
end
So I'm wondering how to construct a form that submits to the "search" action.
I tried the following:
<%= form_tag(people_search_path) do %>
<%= text_field_tag :search %>
<%= submit_tag 'Search' %>
<% end %>
But this results in the following error:
undefined local variable or method `people_search_path' for #<#<Class:0x00007ff2afc59168>:0x00007ff2afc50f68>
If you do rake routes, you'll see that your search path has no name:
image_people GET /people/image/:id(.:format) people#image
ranks_people GET /people/ranks(.:format) people#ranks
GET /people/search/:search(.:format) people#search
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PATCH /people/:id(.:format) people#update
PUT /people/:id(.:format) people#update
DELETE /people/:id(.:format) people#destroy
If, instead, you do:
resources :people do
collection do
get 'image/:id', :to => "people#image", :as => 'image'
get 'ranks', :to => "people#ranks", :as => 'ranks'
get 'search/:search(.:format)', to: 'people#search', as: :search
end
end
You'll see your path now has a name:
image_people GET /people/image/:id(.:format) people#image
ranks_people GET /people/ranks(.:format) people#ranks
search_people GET /people/search/:search(.:format) people#search
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PATCH /people/:id(.:format) people#update
PUT /people/:id(.:format) people#update
DELETE /people/:id(.:format) people#destroy
Which you can use as search_people_path.
BTW, I believe that (.:format) bit is unnecessary and you can simply do:
post 'search/:search', to: 'people#search', as: :search
If I were you, however, I would do:
resources :people do
collection do
get 'image/:id', :to => "people#image", :as => 'image'
get 'ranks', :to => "people#ranks", :as => 'ranks'
get :search, to: 'people#search', as: :search
end
end
Which will give you:
image_people GET /people/image/:id(.:format) people#image
ranks_people GET /people/ranks(.:format) people#ranks
search_people GET /people/search(.:format) people#search
people GET /people(.:format) people#index
POST /people(.:format) people#create
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
PATCH /people/:id(.:format) people#update
PUT /people/:id(.:format) people#update
DELETE /people/:id(.:format) people#destroy
Then, I would update your form as Anees Muhammed suggests (I used :terms instead of :search):
<%= form_tag search_people_path, method: :get do %>
<%= text_field_tag :terms %>
<%= submit_tag 'Search' %>
<% end %>
Then, when you submit, you should get something like:
Started GET "/people/search?terms=foo" for ::1 at 2018-01-29 13:49:40 -0800
Processing by People#search as HTML
Parameters: {"utf8"=>"✓", "terms"=>"foo", "commit"=>"Search"}
And you can access your terms by doing params[:terms].
There is, BTW, nothing "non-RESTful" about this. It is, IMO, completely consistent the Guide for adding RESTful actions.
You should use search_people_path instead of people_search_path. But I don't think that change will take you to what you are trying to achieve. Since your route is get 'search/:search(.:format)', to: 'people#search' rails will expect you to provide a value for the keyword :search to build a url like /people/search/yoursearchterm with something like search_people_path(yoursearchterm). For your functionality to work, change the form to
<%= form_tag search_people_path, method: :get do %>
<%= text_field_tag :search %>
<%= submit_tag 'Search' %>
<% end %>
and then in your controller
def search
params[:search]
end
Also the routes to
get 'search', to: 'people#search'
Updated
Since you are trying to submit the form directly to a rest route like search/:search, I believe there is no direct rails way to achieve the same because that's how forms work. But if all you want is just a rest route like yourwebsite.com/search/yoursearchterm when you submit the form, there are some workarounds to achieve this. Either you have to write a piece of js on submitting the form and forward the request to your route with the input value or you can perform a redirect in the controller to the route, like this.
Routes:
resources :people do
collection do
get 'search', to: 'people#search_people'
get 'search/:search', to: 'people#search'
end
end
So, first the form will be submitted to search_people action and then redirect to search with params. In controller
def search_people
redirect_to search_people_path(params[:search])
end
This will then give you a url yoursite.com/people/search/searchterm and then in your search action, you can use the search params.
Hope this helps.
I'm new to rails and trying to create up / down vote buttons (implemented as text links for now for clarity). However no matter which link is clicked it calls the action of the first link despite the different names.
I've read over and over the docs and answers on here and wrestled with it all day but still can't understand why rails can't see the difference, any help greatly appreciated.
Routes
Futurebot::Application.routes.draw do
resources :posts do
resources :comments
end
resources :posts do
member do
post 'delete'
post 'upVote'
post 'downVote'
end
end
match ':posts/:id/:upVote', :controller => 'posts', :action => 'upVote'
match ':posts/:id/:downVote', :controller => 'posts', :action => 'downVote'
If I remove the resources :posts block it can't find the route but it seems like the match statements should work (that's what the url looks like)
view
<%= link_to "up: ", :action => 'upVote', :id => post.id %>
<%= link_to "down: ", :action => 'downVote', :id => post.id %>
controller
def upVote
#post = Post.find(params[:id])
if #post.increment!(:score)
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
def downVote
#post = Post.find(params[:id])
if #post.decrement!(:score)
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
rake routes: with the 2 new routes commented out (so just the block is there)
up_vote_post POST /posts/:id/up_vote(.:format) posts#up_vo
te
down_vote_post POST /posts/:id/down_vote(.:format) posts#down_
vote
GET /posts(.:format) posts#index
POST /posts(.:format) posts#creat
e
GET /posts/new(.:format) posts#new
GET /posts/:id/edit(.:format) posts#edit
GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#updat
e
DELETE /posts/:id(.:format) posts#destr
oy
root /
posts#index
Rake routes with two routes in routes.rb
delete_post POST /posts/:id/delete(.:format) posts#delet
e
up_vote_post POST /posts/:id/up_vote(.:format) posts#up_vo
te
down_vote_post POST /posts/:id/down_vote(.:format) posts#down_
vote
GET /posts(.:format) posts#index
POST /posts(.:format) posts#creat
e
GET /posts/new(.:format) posts#new
GET /posts/:id/edit(.:format) posts#edit
GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#updat
e
DELETE /posts/:id(.:format) posts#destr
oy
/:post/up_vote/:id(.:format) post#up_vot
e
/:post/down_vote/:id(.:format) post#down_v
ote
root /
posts#index
The added routes
match ':post/up_vote/:id' => "post#up_vote"
match ':post/down_vote/:id' => "post#down_vote"
UPDATE
strangely if I change the route around to:
match ':post/:id/up_vote/' => "post#up_vote"
match ':post/:id/down_vote/' => "post#down_vote"
..as that looks like the link then the error is
uninitialized constant PostController
I've tried using both post and posts based on the solution to that in another question
It seems to me that
resources :posts do
member do
post 'delete'
post 'up_vote'
post 'down_vote'
end
end
should give you a routes output similar to this
POST /posts/:id/delete(.:format)
/posts/:id/up_vote(.:format)
/posts/:id/down_vote(.:format)
And they should map out to the PostsController and its respective actions delete, up_vote, down_vote.
Is there any reason why you have those two match routes at the end? They pretty much look like wildcards to me and I don't think you need them.
What is really happening is that anything that matches the following
something/another/path
will get mapped to those routes, specifically to the first one and it will be split in the params like
params[:posts] => something
params[:id] => another
params[:upVote]=> path
That happens because you are using the colon character which allows you to specify dynamic routes. For example something like this
match 'hello/world/:name' => "hello#say"
would get mapped out to a HelloController and action say. Inside the action you would then have params[:name] that would be equal to whatever is under name.
So hello/world/leo would have params[:name] equal to leo
For more information take a look at this: Rails Routes: Dynamic Segments
NOTE Also, try not to use camel case for method names in Ruby :D
UPDATE You need a :method => "post" on your link_to in order for it to send a post request
I have a list of names, that contain this:
<td><%= product.date %></td>
and it is in my index page. (It is the list of my applications)
I want to assign a custom link to that, and pass THAT value to the controller:
I tried this:
<td><%= link_to product.date, {:controller => "product", :action => "sort_by_date", <how do I pass the 'product.date' string?> }%></td>
I read the rails route guide but I couldn't find help
Thanks
what rakes routes returns:
products GET /products(.:format) products#index
POST /products(.:format) products#create
new_product GET /products/new(.:format) products#new
edit_product GET /products/:id/edit(.:format) products#edit
product GET /products/:id(.:format) products#show
PUT /products/:id(.:format) products#update
DELETE /products/:id(.:format) products#destroy
root /
products#home.html
use like below
<%= link_to product.date, {:controller => "product", :action => "sort_by_date", :product_date => product.date %>
and in your controller you should get the date in params[:product_date]
You can also use resource helper function to pass custom parameters like below.
<%= link_to product.date, products_path(:product_date => product.date)
This method will route to the ProductsController#index with a params[:product_date]
Edit:
I just tried with following
resources :products do
collection do
get 'sort_by_date' => 'products#sort_by_date'
end
end
The rake routes are as following
sort_by_date_products GET /products/sort_by_date(.:format) products#sort_by_date
To generate the URL it will be better to use the resource helper function like below
sort_by_date_products_path(:product_date => '1/1/2012')
This will generate a url like /product/sort_by_date?product_date=1/1/2012 which will call the sort_by_date method of ProductsController and also have params[:product_date] available.
Lets check if its work...
I am using acts_as_taggable_on steroids and I am having problem with this piece of code that generates a link to a tag:
<%= link_to tag, tag_path(:id => tag.name) %>
when I access the URL:
http://localhost:3000/tags/rails
I get the error:
No action responded to rails. Actions: show
However, this URL works:
http://localhost:3000/tags/show/rails
I have defined the show action in my tags_controller.rb
class TagsController < ApplicationController
def show
#stories = Story.find_tagged_with(params[:id])
end
end
I have the following routes generated by rake:routes :
tags GET /tags(.:format) {:controller=>"tags", :action=>"index"}
POST /tags(.:format) {:controller=>"tags", :action=>"create"}
new_tag GET /tags/new(.:format) {:controller=>"tags", :action=>"new"}
edit_tag GET /tags/:id/edit(.:format) {:controller=>"tags", :action=>"edit"}
tag GET /tags/:id(.:format) {:controller=>"tags", :action=>"show"}
PUT /tags/:id(.:format) {:controller=>"tags", :action=>"update"}
DELETE /tags/:id(.:format) {:controller=>"tags", :action=>"destroy"}
so I know that URL tags/rails points to the route tags/:id, and I've provided an additional param to link_to to assign the tag name as the :id param, but as you can see, it's not working. A forum suggested I use the to_param but I have not Tag model and the book suggested against it. Am I missing anything?
I am following the Sitepoint book Simply Rails 2
EDIT: added working URL, see top
Try adding this to your route resource:
:requirements => { :id => /.*/ }
Shooting in the dark here but should
<%= link_to tag, tag_path(:id => tag.name) %>
be
<%= link_to tag, tag_path(:id => tag.id) %>
or
<%= link_to tag, tag_path(tag) %>
Try this for your link:
link_to tag.name, { :action => :tag, :id => tag.name }
I don't know what version of rails you're using, I'm assuming 3.
Basically, you're using the tag_path which goes off of the id. If you haven't changed anything, that means something like tag/43, tag with id 43. The reason you were suggested to override to_param is in case you want it to go off the name of the tag instead, so something like tag/rails. For that, you do something like this:
class Tag
def to_param
name
end
end
Finally, you will have to change the show action to use the name, not the id. So #stories = Story.find_tagged_with(params[:name]). Then I believe that you will want to create a route to compensate for this, so above your resources :tags, add match "/tags/:name" => "tags#show".
For me it looks like the difference in routes.rb between
resources :tags
and
resource :tags
The first will have as its default index action, the second will not have :index, but it will respond with show on default route.
I have Exam controller.
In routes.rb there is "resources :exams"
In controller there are REST-like generated methods.
I want to add my own method there:
def search
#exams = Exam.where("name like ?", params[:q])
end
In view file:
<%= form_tag(search_path, :method => "post") do %>
<%= label_tag(:q, "Szukaj: ") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Szukaj") %>
<% end %>
I know, there is no results presentation yet, it doesn't work at all at this moment (:
When i go to http://localhost:3000/exams/search it's mapping it to show controller and search is a :id paramether then...
How to get http://localhost:3000/exams/search to run the seach controller?
You forgot to add route. Put this in routes.rb, before resources :exams
map.search '/exams/search', :controller => :exams, :action => :search
Note, that resources :exams doesn't generate routes for all public methods of the controller, it generates very specific set of routes. You can find more information in the rails routing guide. (see section 3.2 in particular)
You'll need to add additional parameters to your mapping. You can add "collection" methods like so:
map.resources :exams, :collection => {:search => :get}
When you rake routes, you'll see that it generates something like so:
search_exams GET /exams/search(.:format) {:controller=>"exams", :action=>"search"}
exams GET /exams(.:format) {:controller=>"exams", :action=>"index"}
POST /exams(.:format) {:controller=>"exams", :action=>"create"}
new_exam GET /exams/new(.:format) {:controller=>"exams", :action=>"new"}
edit_exam GET /exams/:id/edit(.:format) {:controller=>"exams", :action=>"edit"}
exam GET /exams/:id(.:format) {:controller=>"exams", :action=>"show"}
PUT /exams/:id(.:format) {:controller=>"exams", :action=>"update"}
DELETE /exams/:id(.:format) {:controller=>"exams", :action=>"destroy"}