I'm learning Rails and I have now a good knowledge about controllers.
By the way I always have some issues and I don't know whats the best way to solve them.
One of these is the search: I have a search in my website and I must reorder results for relevance and date.
My search controller
def show
#query = params[:query]
#contents = Content.published.search_by_text(#query).page(params[:page]).per(12)
end
This is the default search. I have to implement also the "data order" search and I think to do something like this:
def show
#query = params[:query]
#contents = Content.published.search_by_text(#query).page(params[:page]).per(12)
if params[:order]
#contents = Content.published.search_by_text(#query).reorder("created_at DESC").page(params[:page]).per(12)
end
end
Is there a better way to obtain my result?
Fortunately, rails allows us to chain calls when using Active Record (Rails' ORM)
Here is a possibility :
def show
#query = params[:query]
#contents = Content.published.search_by_text(#query)
#contents = #contents.reorder("created_at DESC") if params[:order]
#contents = #contents.page(params[:page]).per(12)
end
Related
I have model Places and I have the index method in a controller. I need to get all places via request
/places
And filter places via request with query
/places?tlat=xxxx&tlong=xxxx&blat=xxxxx&blong=xxxx
What the best way to get this records? Should I check an existence of each param or are there Rails way?
#places = if params[tlat]&¶ms[blat]....
Places.all.where("lat > ? AND long > ? AND lat < ? AND long < ?", tlat, tlong, blat, blong)
else
Places.all
If you want to set WHERE clauses depending on params, you can use Ursus' code which is fine.
However, if you need to apply those WHERE clauses only if a set of params are present, you can use the following:
#places = Place.all
if params[:blat].present? && params[:tlat].present?
#places = #places.where(blat: params[:blat], tlat: params[:tlat])
end
# etc.
You could use an array of arrays to pair the associated params, kind of like what Ursus did.
I'd do something like this if possible. Important to note the this is just one query, composed dynamically.
#places = Place.all
%i(tlat tlong blat blong).each do |field|
if params[field].present?
#places = #places.where(field => params[field])
end
end
IMO, truly the "Rails way" (but actually just the "Ruby way") would be to extract this long conditional, and the query itself, out to their own private method. It becomes much easier to understand what's going on in the index action
class MyController < ApplicationController
def index
#places = Place.all
apply_geo_scope if geo_params_present?
end
private
def geo_params_present?
!!(params[:tlat] && params[:blat] && params[:tlong] && params[:blong])
end
# A scope in the model would be better than defining this in the controller
def apply_geo_scope
%i(tlat tlong blat blong).each do |field|
#places = #places.where(field => params[field])
end
end
end
I have my search method and I want to pass my "search" to a analysis object. The search that user is looking will be the name of the analysis so as an Admin I can see the analysis and discovery what the users are most searching.
Here is my methods. My #analysis.name is always create with nil value. Does any body know how to fix that?
def search_articles
#search_param = params[:current_search][:search]
#articles = Article.where("title LIKE :search",
:search => "%#{params[:current_search][:search]}%")
search_attribute = :search
update_analysis(search_attribute)
end
def update_analysis(search)
#analysis = Analysis.create([{name: search, search_number: 1}])
end
Solution:
replace
search_attribute = :search
to
search_attribute = params[:current_search][:search]
A user wants to search by an attribute and/or order the results. Here are some example requests
/posts?order=DESC&title=cooking
/posts?order=ASC
/posts?title=cooking
How can I conditionally chain such options to form a query?
So far I have a very ugly method that will quickly become difficult to maintain.
def index
common = Hash.new
common["user_id"] = current_user.id
if params[:order] && params[:title]
#vacancies = Post.where(common)
.where("LOWER(title) LIKE ?", params[:title])
.order("title #{params[:order]}")
elsif params[:order] && !params[:title]
#vacancies = Post.where(common)
.order("title #{params[:order]}")
elsif params[:title] && !params[:order]
#vacancies = Post.where(common)
.where("LOWER(title) LIKE ?", params[:title])
end
end
Remember that query methods like where and order are meant to be chained. What you want to do is start with a base query (like Post.where(common), which you use in all cases) and then conditionally chain other methods:
def index
common = Hash.new
common["user_id"] = current_user.id
#vacancies = Post.where(common)
if params[:order]
#vacancies = #vacancies.order(title: params[:order].to_sym)
end
if params[:title]
#vacancies = #vacancies.where("LOWER(title) LIKE ?", params[:title])
end
end
P.S. Your original code had .order("title #{params[:order]}"). This is very dangerous, since it opens you up to SQL injection attacks. As a rule of thumb never use string concatenation (#{...}) with a value you get from the end user when you're going to pass the result to the database. Accordingly, I've changed it to .order(title: params[:order]). Rails will use this hash to construct a secure query so you don't have to worry about injection attacks.
You can read more about SQL injection attacks in Rails in the official Ruby on Rails Security Guide.
I have the following model and I want to pass multiple params in "with_query", don't know how to achieve it. currently you can see it takes only "query" param. how can I filter it with country and job_type. any help would be really appreciated.
search Model
def self.search(query, country, job_type, page = 1)
results = []
Refinery.searchable_models.each do |model|
results << model.limit(RESULTS_LIMIT).with_query(query)
end if query.present?
results.flatten[0..(RESULTS_LIMIT - 1)]
end
Controller
def show
#results = Refinery::SearchEngine.search(params[:query], params[:country], params[:job_type], params[:page])
present(#page = Refinery::Page.find_by_link_url("/search"))
end
I would try changing the line that builds your results in the search model to:
results << model.limit(RESULTS_LIMIT).with_query(query).where(country: country, job_type: job_type)
I've been wracking my brain over this but can't get it. I feel like the answer is probably obvious.
What I'm trying to do is the following:
I have an index controller which lists a series of Jobs which I can search using Ransack. Each job has a completion date which either has a date in it or is null (unfinished). Currently, the search itself works great. I would like to make it so that the index page loads up showing only the unfinished work, but I also want it to work so that when someone does run a search, returns results for both finished and unfinished work.
Any help would be greatly appreciated. In the code below, :actual is the name of the field with the completion date. I also was looking around the web and thought that maybe something like the DEFAULT_SEARCH_PARAMETER={} that I have in the Job model might work but I couldn't seem to get it to.
Here is the code:
class Job < ActiveRecord::Base
DEFAULT_SEARCH_PARAMETER ={}
attr_accessible :items_attributes, :actual
end
def index
#search = Job.search(params[:q] || Job::DEFAULT_SEARCH_PARAMETER)
#search.build_condition
#results = #search.result
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
Late to the party, but thought I'd suggest an alternate approach in case someone else comes across this.
The answer above works, but its disadvantage is that the default is not added to Ransack's search object, so - if you are using a search form - the default selection is not shown in the form.
The following approach adds the default to the search object and therefore will appear in your search form.
def index
#search = Job.search(params[:q])
#search.status_cont = 'Open' unless params[:q] #or whatever, must use Ransack's predicates here
#results = #search.result
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
I think you could just apply your own filter when the search parameters don't exist:
def index
#search = Job.search(params[:q])
#results = #search.result
#results = #results.where(:your_date => nil) unless params[:q]
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
Many years later I found myself with this exact problem so I thought I'd chime in with a solution I'm using. Set default search params in the controller and reverse merge them into params[:q]:
def index
default_search_params = {
status_cont: "open"
}
#search = Job.search((params[:q] || {}).reverse_merge(default_search_params))
...
end
So by default, you want the page to load with records where actual is nil. And later when the user searches you want to go back to how your search was working before.
Give this a try.
def index
#search = Job.search(params[:q] || Job::DEFAULT_SEARCH_PARAMETER)
#search.build_condition
#results = #search.result
if #results.nil?
#results=Job.find(:all, :conditions => ["actual = NULL"] )
end
#job = #results.paginate(:per_page => 10, :page => params[:page])
end