I have built a rails app that I wish to adapt to a restful webservice that returns results based upon queries requested from a mobile app. The functionality of the app requires that a search is enabled and I have employed sunspot solr within the rails app. The code for the search (within the respective controller) is as such:
def search(code, dow, period)
# performing the search as I want it
#search = Sunspot.search(Camps) do
fulltext code
with(dow).equal_to(true)
with(period).equal_to(true)
with(:is_active).equal_to(true)
order_by :price, :desc
end
#search.results
end
The major problem is that every rails webservice tutorial only lists very inflexible HTTP requests like: GET /user/{#}, and to execute you simply input the user #. But for the app, I will need to pass in varying parameters noting the method head as: search(code, dow, period).
What do I need to change to make the app respond and return the requested information when using a GET and POST requests to some URI
ie. /search...param...param...etc that can allow for the method head input parameters I have designated?
As mentioned in the comments, you'll want to look at the routes to sort this out
Non Resourceful Routes
The problem you have is you're trying to handle variables in the routes, which aren't the typical use case
Rails allows you to send any type of data through the routes - it's just a case of defining them correctly
Here's how we've implemented basic search routing:
#config/routes.rb
match 'search(/:search)', :to => 'products#search', :as => :search, via: [:get, :post]
#app/controllers/products_controller.rb
def search
#products = Product.search(params[:search])
respond_to do |format| --> handles different mime-types (HTML / JSON)
format.js { render :partial => "elements/livesearch", :locals => {:search => #products, :query => params[:search]} }
format.html { render :index }
end
end
#app/models/product.rb --> put your Solr stuff in here
def self.search(search)
basic_search(name: search, description: search).take(5)
end
Related
I am currently implementing the search functionality in a project and I am struggling displaying it on a dedicated search result page.
Being aware of questions on this topic already but being unable to work out a solution due to utter incompetence, I am asking you for the final pointer :).
The search form spawns on the index page which is entries_path and root_path. I'd like to pass on the parameters to a new page, search_path.
Here are my files:
EntriesController
def search
end
def index
#entries = Entry.all.order('entries.created_at DESC')
#entry = Entry.new # My index page also creates new entries.
if params[:search]
#entries = Entry.search(params[:search]).order("created_at DESC")
else
#entries = Entry.all.order("created_at DESC")
end
Model: entry.rb
def self.search(search)
where("content LIKE ? OR created_at LIKE ?", "%#{search}%", "%#{search}%")
end
routes.rb
Rails.application.routes.draw do
resources :entries
root 'entries#index'
get 'new' => 'entries/new'
get 'show' => 'entries/show'
get 'edit' => 'entries/edit'
get 'search' => 'entries/search'
Finally: the form on index
<%= form_tag(entries_path, :method => "get", class: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search for previous entries..", class: "form-control" %>
<% end %>
When I change the entries_path to search_path, I am getting a "We're sorry, but something went wrong. If you are the application owner check the logs for more information." – therefore, I suspect it is a routing problem. However, I can't seem to figure it out. The log says:
ActionController::RoutingError (uninitialized constant Entries):
Phew, would love to know what's going on here! Thanks a bunch already.
Change your routes.rb
Rails.application.routes.draw do
root 'entries#index'
resources :entries do
collection do
get :search
end
end
end
change your path in search form on index page:
<%= form_tag(search_entries_path, :method => :get, class: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search for previous entries..", class: "form-control" %>
<% end %>
Change your controller's method:
def search
if params[:search]
#entries = Entry.search(params[:search]).order("created_at DESC")
else
#entries = Entry.all.order("created_at DESC")
end
end
create one template for search method under view/entries/search.html.erb
You can here access your #entries object
Points of changes I have made:
1. Changes in routes.rb:
Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
A resource route maps a number of related requests to actions in a single controller. a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. for more information regarding routes
You can add additional routes that apply to the collection or individual members of the collection.
For Eg:
To add a member route, just add a member block into the resource block:
resources :entries do
member do
get 'preview'
end
end
To add a route to the collection:
resources :entries do
collection do
get 'search'
end
end
A member route will require an ID, because it acts on a member. A collection route doesn't because it acts on a collection of objects. for more info about
difference between collection route and member route in ruby on rails?
2. Which method should I use for search GET or POST?
There are numbers of post available regarding GET and POST request on the web as well as SO. GET and POST both have their place, and if you’re a Web developer you should understand the pros and cons of each of them. Or if you’re too lazy to do that, just remember that Search forms should use GET method. Your users will appreciate it. ;)
Let me define them in short description.
GET to fetch a resource(when you don't want to make any change in your DB), POST to create a resource(when you want to make a change/create in your DB), PUT (or PATCH, these is debate on the matter) to update a resource, DELETE to delete one.
For your reference:
When do you use POST and when do you use GET?
I hope this information may helps you. Good Luck :)
You could use html as a search field on Index page.
<form>
<legend>Search</legend>
<div class='col-xs-4'>
<input type='text' class='form-control' value='<%= params[:search] %>' name='keyword' placeholder='Keyword' >
</div>
</form>
Note: This form would hit your index action of entries controller, So at this point no need to create a search methods as you have created
In my app I'm toggling whether a student belongs to a training time or not when clicking on a time link.
# controller
def time
#student = Student.find(params[:student_id])
#time = TrainingTime.find(params[:training_time_id])
#student.toggle_time(#time)
respond_to do |format|
format.html { redirect_to #student }
format.js
end
end
# routes
resources :students do
match "time/:training_time_id", to: "students#time", as: :toggle_time
end
# view
<%= link_to t.time_format, student_toggle_time_path(#student, t), remote: true %>
Currently it's working using match, but what is the proper way of setting this up and why?
Thanks for your input.
It's all about semantics. GET to fetch a resource, POST to create a resource, PUT (or PATCH, these is debate on the matter) to update a resource, DELETE to delete one.
To be honnest, there is debate about POST and PUT too. But since web servers usually only handles GET and POST (the behavior of other verbs being emulated via parameters), I tend not to bother too much, and stick with what I wrote above.
You don't seem to be creating any resource, so semantically speaking, PUT is what you're looking for.
As for match, in rails 3.X, is a catch all => all verbs are matched in the route. You can restrain using match 'something', via: [:get, :post]. With Rails 4, by default it will do nothing, you have to be explicit about the verbs handled.
I'm working on implementing a SEO-hiarchy, which means that I need to prepend parameters for a show action.
The use-case is a search site where the URL-structure is:
/cars/(:brand)/ => a list page
/cars/(:brand)/(:model_name)?s=query_params => a search action
/cars/:brand/:model_name/:variant/:id => a car show action
My problem is to make the show action URLs work without having to provide :brand, :model_name and :variant as individual arguments. They are always available from as values on the resource.
What I have:
/cars/19330-Audi-A4-3.0-TDI
What I want
/cars/Audi/A4/3.0-TDI/19330
Previously, this was how the routes.rb looked like:
# Before
resources :cars. only: [:show] do
member do
get 'favourize'
get 'unfavourize'
end
Following was my first attempt:
# First attempt
scope '/cars/:brand/:model_name/:variant' do
match ":id" => 'cars_controller#show'
match ":car_id/favourize" => 'cars_controller#favourize', as: :favourize_car
match ":car_id/unfavourize" => 'cars_controller#unfavourize', as: :unfavourize_car
end
This makes it possible to do:
cars_path(car, brand: car.brand, model_name: car.model_name, variant: car.variant)
But that is obviously not really ideal.
How is it possible to setup the routes (and perhaps the .to_param method?) in a way that doesn't make it a tedious task to change all link_to calls?
Thanks in advance!
-- UPDATE --
With #tharrisson's suggestion, this is what I tried:
# routes.rb
match '/:brand/:model_name/:variant/:id' => 'cars#show', as: :car
# car.rb
def to_param
# Replace all non-alphanumeric chars with - , then merge adjacent dashes into one
"#{brand}/#{model_name}/#{variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-')}/#{id}"
end
The route works fine, e.g. /cars/Audi/A4/3.0-TDI/19930 displays the correct page. Generating the link with to_param, however, doesn't work. Example:
link_to "car link", car_path(#car)
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>#<Car id: 487143, (...)>})
link_to "car link 2", car_path(#car, brand: "Audi")
#=> ActionView::Template::Error (No route matches {:controller=>"cars", :action=>"show", :locale=>"da", :brand=>"Audi", :model_name=>#<Car id: 487143, (...)>})
Rails doesn't seem to know how to translate the to_param into a valid link.
I do not see any way to do this with Rails without tweaking either the URL recognition or the URL generation.
With your first attempt, you got the URL recognition working but not the generation. The solution I can see to make the generation working would be to override the car_path helper method.
Another solution could be, like you did in the UPDATE, to override the to_param method of Car. Notice that your problem is not in the to_param method but in the route definition : you need to give :brand,:model_name and :variant parameters when you want to generate the route. To deal with that, you may want to use a Wildcard segment in your route.
Finally you can also use the routing-filter gem which make you able to add logic before and after the url recognition / generation.
For me, it looks like all theses solutions are a bit heavy and not as easy as it should be but I believe this came from your need as you want to add some levels in the URL without strictly following the rails behavior which will give you URL like /brands/audi/models/A3/variants/19930
OK, so here's what I've got. This works in my little test case. Obviously some fixups needed, and I am sure could be more concise and elegant, but my motto is: "make it work, make it pretty, make it fast" :-)
In routes.rb
controller :cars do
match 'cars', :to => "cars#index"
match 'cars/:brand', :to => "cars#list_brand", :as => :brand
match 'cars/:brand/:model', :to => "cars#list_model_name", :as => :model_name
match 'cars/:brand/:model/:variant', :to => "cars#list_variant", :as => :variant
end
In the Car model
def to_param
"#{brand}/#{model_name}/#{variant}"
end
And obviously fragile and non-DRY, in cars_controller.rb
def index
#cars = Car.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #cars }
end
end
def list_brand
#cars = Car.where("brand = ?", params[:brand])
respond_to do |format|
format.html { render :index }
end
end
def list_model_name
#cars = Car.where("brand = ? and model_name = ?", params[:brand], params[:model])
respond_to do |format|
format.html { render :index }
end
end
def list_variant
#cars = Car.where("brand = ? and model_name = ? and variant = ?", params[:brand], params[:model], params[:variant])
respond_to do |format|
format.html { render :index }
end
end
You just need to create two routes, one for recognition, one for generation.
Updated: use the routes in question.
# config/routes.rb
# this one is used for path generation
resources :cars, :only => [:index, :show] do
member do
get 'favourize'
get 'unfavourize'
end
end
# this one is used for path recognition
scope '/cars/:brand/:model_name/:variant' do
match ':id(/:action)' => 'cars#show', :via => :get
end
And customize to_param
# app/models/car.rb
require 'cgi'
class Car < ActiveRecord::Base
def to_param
parts = [brand,
model_name,
variant.downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-'),
id]
parts.collect {|p| p.present? ? CGI.escape(p.to_s) : '-'}.join('/')
end
end
Sample of path helpers:
link_to 'Show', car_path(#car)
link_to 'Edit', edit_car_path(#car)
link_to 'Favourize', favourize_car_path(#car)
link_to 'Unfavourize', unfavourize_car_path(#car)
link_to 'Cars', cars_path
form_for(#car) # if resources :cars is not
# restricted to :index and :show
You want bounded parameters to be passed to url of which some parameters are optional and some of them strictly needs to be present.
Rails guides shows you can have strict as well as optional parameters and also you can give name to particular route in-order to simplify its usage.
Guide on rails routing
bound parameters
Example usage -
In below route,
brand is optional parameter as its surrounded by circular bracket
Also please note there can be optional parameters inside route but they needs to added at last /cars(/:brand)(/:make)(/:model)
match '/cars/(:brand)', :to => 'cars#index', :as => cars
here cars_url will map to index action of cars controller..
again cars_url("Totoya") will route index action of cars controller along-with params[:brand] as Toyota
Show url route can be as below where id is mandatory and others can be optional
match '/cars/:id(/:brand(/:model_name/)(/:variant)', :to => "cars#show", :as => car
In above case, id is mandatory field. Other parameters are optional.
so you can access it like car_url(car.id) or car_url(12, 'toyota') or car_url(12, 'toyota', 'fortuner') or car_url(12, 'toyota', 'fortuner', 'something else)
I don't really understand the pro's and con's of using "post" vs "get" vs "put" requests, on custom controller actions, and whether to use links or forms/buttons.
So let's say I have a simple to-do list with tasks, and a tasks controller, and I want a "complete" action where I find a specific task in the db and update it's status attribute from "incomplete" to "complete."
def complete
#task = Task.find(params[:id])
if #task.update_attributes(:status => "complete")
redirect_to tasks_url, :notice => "Completed!"
else
redirect_to tasks_url, :error => "Whoops."
end
end
What's the best practice way to define this route, which HTML request method should I use (post? put? get?), and should I use a plain link or a form? (and note: assume my user security model is all figured out with devise, and appropriate before filters, etc.)
And most of all, how would I articulate all this in a Rails 3 routes.rb file?
Note, the below code wasn't really working for me:
#routes.rb
resources :tasks do
members do
post 'complete'
end
end
so currently I'm using this instead:
#routes.rb
match 'tasks/:id/complete', 'tasks#complete', :as => "complete_task"
#view
= link_to "Complete", complete_task_path(:id => #task.id)
But this triggers a get request, and I feel like it should be a "put" or a "post." Or should it be a link at all? Should it be a form with hidden fields?
"link_to" method usually generates an anchor tag ie "<a></a>", ie a regular GET request
to do a POST request using link_to you should do the following
= link_to "Complete", complete_task_path(:id => #task.id), :method => :post
Remember if javascript is disabled in the browser, the above statement will fall back to a GET request instead of POST.
I am a beginner when it comes to Ruby on Rails, so I need a little bit of help. I started reading a basic tutorial recently, which was taught using Scaffolding. I made a "Clients" model: script/generate scaffold clients name:string ip_address:string speed:integer ... Inside the clients_controller.rb file, there is a method called show:
# GET /clients/1
# GET /clients/1.xml
def show
#client = Client.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #client }
end
end
For queries, I'd go to localhost:3000/clients/{Enter the ID here}. Instead of searching with the ID are the argument, I'd like to search with another value, like ip_address or speed, so I thought all I would have to do is change :id to :ip_address in "#client = Client.find(params[:id])". However, that does not work, so would someone please tell me how I can achieve a search with another parameter. Thanks!
This doesn't work because of the way things are routed
When you do something like
map.resources :client (See config/routes.rb)
This happens automatically when you use scaffold.
It sets up routes based on the assumption you're using an id.
One of these routes is something like
map.connect 'clients/:id', :controller => 'client', :action => 'show'
So :id is passed as a parameter as part of the URL.
You shouldn't have the IP be the primary identifier unless they're distinct - and even then it kind of messes with the RESTful routing.
If you want to have the ability to search by IP, modify your index action for the clients
def index
if params[:ip].present?
#clients = Client.find_by_ip_address(params[:ip]);
else
#clients = Client.all
end
end
Then you can search by ip by going to clients?ip=###.###.###
This line in your routes.rb file
map.connect 'clients/:id', :controller => 'client', :action => 'show'
implies that when the dispatcher receives an URI in the format "clients/abcdxyz' with GET Method, it will redirect it to show method with the value "abcdxyz" available in params hash with key :id.
EDIT
Since you have used scaffold, the clients resource will be RESTful. That means that when you send a GET request to "/clients/:id' URI, you will be redirected to show page of that particular client.
In your controller code you can access it as
params[:id] # which will be "abcdxyz"
The find method that is generated by scaffold searches on the primary key i.e 'id' column. You need to change that statement to either
#client = Client.find_by_ip_address(params[:id]) #find_by_column_name
OR
#client = Client.find(:first, :conditions => [":ip_address = ?", params[:id]])
:-)