Ruby on Rails - Implementing Simple Search with scopes - ruby-on-rails

I followed the Railscasts #37, very interesting. I tried to apply it to a search that already implies a scope and pagination. But it failed unsuccessfully. Being quite new at rails, I wonder if a better solution exists.
Here is my original controller, that works:
def index
#business_rules = BusinessRule.pgnd(current_playground).order("hierarchy ASC").paginate(page: params[:page], :per_page => paginate_lines)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #business_rules }
end
end
But trying to add the search function as defined in the RailsCast fails:
#business_rules = BusinessRule.pgnd(current_playground).search(params[:search]).order("hierarchy ASC").paginate(page: params[:page], :per_page => paginate_lines)
Error message is: undefined method order for #<Array:0x007fbf8cc62ca0>

Does your search function look like this, as in the railscast?
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
If so, I'm guessing your getting back an array of results, which cannot be chained with additional query methods.
So when you use the query methods (where, order, limit, etc), it returns an ActiveRelation object, which is basically a proxy for your eventual result set. It won't actually hit your DB until you try to use the result set by calling .all, .first, .each, something like that.
You could write your search method like this instead:
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
end
end
So if search is present, you'll scope your AR object down to the matching results. Otherwise you won't, which will have the same result as returning everything. Then you can still order those results, limit them, paginate them, whatever.

The final script for the search function created in the controller is:
def self.search(search)
if not search.empty?
where('name like ?', "%#{search}%")
else
where('1=1')
end
end
Then it is possible to cahin the query methods as this:
#business_rules=BusinessRule.pgnd(current_playground).search(params[:search]).limit(10)
For more explanations, please refer to RailsCast #37, and note that the syntax for inserting a from_tag is <%= form_tag ...
Thanks to all for your help,
Best regards,
Fred

Related

Pulling unique case-insensitive values

I'm using jQuery autocomplete in a few areas. It makes a call to this method:
def index
#schools = School.order(:name).where("name like ?", "%#{params[:term]}%").limit(10)
render json: #schools.map(&:name)
end
Initially I didn't handle case sensitivity well, so I have some duplicate names in differing cases (ie. "Rutgers" and "rutgers".
I'd like to change the above method to return only unique values, ignoring the case, so only one "Rutgers" result will be returned.
I'm going to clean up the DB but a quick temp fix for this would be great!
I guess it depends on which DB you are using, but you can try doing something like this:
def index
#school_names = School.order("lower(name)")
.where("name ilike ?", "%#{params[:term]}%")
.distinct.limit(10).pluck("lower(name)")
render json: #school_names.map(&:downcase)
end
The above will work with POSTGRESQL, for Sqlite, this will work:
def index
#school_names = School.order("lower(name)")
.where("lower(name) LIKE lower(?)", "%#{params[:term]}%")
.distinct.limit(10).pluck("lower(name)")
render json: #school_names.map(&:downcase)
end

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

Setting default search parameter on Ransack for rails

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

Simple Search and Rails

I have a simple search in Rails:
def self.search(search)
# if search is not empty
if search
find(:all, :conditions => ["name LIKE ?", "%#{search}%"])
# if search is empty return all
else
find(:all)
end
The view:
<% if #registries.empty? %>
Can't find the registry. Please try a differnet name.
<% else %>
<% #registries.each do |registry| %>
......etc.
How can I code it to show "Nothing Found" instead of find(:all) if it could not find a query?
I tries a few things, but nothing works. Even if I take out the else it still shows all queries if it can't find the one searching for.
Thanks in advance
When you mean "not empty" you must test against that specifically. Remember in Ruby that there are only two values that evaluate as false: false and nil.
You probably intended:
if (search.present?)
where("name like ?", "%#{search}%").all
else
all
end
Use of the Rails 3 style where clause makes your methods a lot easier to understand. Using find with :conditions is the old Rails 1 and 2 style.

Rails doing a FIND with Conditions?

In Rails 3, I created a Search Form that does a FIND with conditions in the Models file.
#projects = find(:all,
:select => 'projects.*',
:conditions => ['name = ?', search_name]
).first
This works great if a name is provided in the searchform (search_name). Problem is if search_name is blank, Rails Errors (can't say I blame it)...
What is the smart way to handle this situation? I'd like, if search_name is blank, to not error but return everything.
Suggestions? Thanks!
You can create a scope to handle this. In your Project model, add something like:
scope :search_by(name), lambda{|name| first.where(:name => name) unless name.blank?}
then in your controller, simply call:
Project.search_by(params[:search])
EDIT:
If you need to serach for multiple fields you can adapt the scope:
scope :search_by(name), lambda{|name| first.includes(:owner).where("projects.name LIKE ? OR owners.name LIKE ?", name, name) unless name.blank?}
if search_name.blank?
#projects = Project.order(:name)
else
#projects = Project.where(:name => search_name)
end
The cleanest way is using lazy loading with the new ActiveRecord functionalities like this:
#projects = Project.order(:name)
#projects = #projects.where(:name => search_name) if search_name
You can add as many conditions as you like this way. They won't be executed until you need the results anyway (with #projects.all or #projects.each, etc...)

Resources