Pulling unique case-insensitive values - ruby-on-rails

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

Related

Problem with selecting elements with the same params

What i do wrong? I want to return every products which pass condition:
def show
products = Product.select{|x| x[:category_id] == params[:id]}
render json: products
end
When i write
def show
products = Product.select{|x| x[:category_id] == 1}
render json: products
end
it works why the first example doesn't work?
I am pretty sure that there is mismatch in data type.
1=='1' #will be always false
1==1 #will be true
'1'=='1' #will be true as well
And also check for nil value from params[:id]
Please make sure to change as follows
def show
products = Product.select{|x| x.category_id == params[:id].to_i}
render json: products
end
OR
The best solution as suggested by #Eyeslandic is to use .where as it will not check for mismatch in data type. And also you don't have to take care of nil value from params[:id].
You should really be using a where to stop sql from loading all your products.
#products = Product.where('category_is = ?', params[:id])
The being said, if you are sticking to rails restful conventions, the fact you have a param called :id that is the category_id suggests you are on the category controller. So maybe consider changing your logic to:
#category = Category.includes(:products).find(params[:id])
you can then access products via
#category.products
or if your not interested in the category too much maybe
#products = Category.includes(:products).find(params[:id])&.products

ActiveRecord how to use Where only if the parameter you're querying has been passed?

I'm running a query like the below:
Item.where("created_at >=?", Time.parse(params[:created_at])).where(status_id: params[:status_id])
...where the user can decide to NOT provide a parameter, in which case it should be excluded from the query entirely. For example, if the user decides to not pass a created_at and not submit it, I want to run the following:
Item.where(status_id: params[:status_id])
I was thinking even if you had a try statement like Time.try(:parse, params[:created_at]), if params[created_at] were empty, then the query would be .where(created_at >= ?", nil) which would NOT be the intent at all. Same thing with params[:status_id], if the user just didn't pass it, you'd have a query that's .where(status_id:nil) which is again not appropriate, because that's a valid query in itself!
I suppose you can write code like this:
if params[:created_at].present?
#items = Item.where("created_at >= ?", Time.parse(params[:created_at])
end
if params[:status_id].present?
#items = #items.where(status_id: params[:status_id])
end
However, this is less efficient with multiple db calls, and I'm trying to be more efficient. Just wondering if possible.
def index
#products = Product.where(nil) # creates an anonymous scope
#products = #products.status(params[:status]) if params[:status].present?
#products = #products.location(params[:location]) if params[:location].present?
#products = #products.starts_with(params[:starts_with]) if params[:starts_with].present?
end
You can do something like this. Rails is smart in order to identify when it need to build query ;)
You might be interested in checking this blog It was very useful for me and can also be for you.
If you read #where documentation, you can see option to pass nil to where clause.
blank condition :
If the condition is any blank-ish object, then #where is a no-op and returns the current relation.
This gives us option to pass conditions if valid or just return nil will produce previous relation itself.
#items = Item.where(status_condition).where(created_at_condition)
private
def status_condition
['status = ?', params[:status]] unless params[:status].blank?
end
def created_at_condition
['created_at >= ?', Time.parse(params[:created_at])] unless params[:created_at].blank?
end
This would be another option to achieve the desired result. Hope this helps !

Ignore uppercase, downcase and accent in search

I have a search system with filter here. This system work like a charm but I have some problem with downcase / uppercase and accent.
For example if I search "marée" I have result but if I search "MAREE" or "Marée" or "maree". I don't have result.
I want to fix that. How I can fix this ? Thank you.
my controller
def resultnohome
if params[:query].blank?
redirect_to action: :index and return
else
#campings = Camping.searchi(params[:query], params[:handicap], params[:animaux], params[:television], params[:plage], params[:etang], params[:lac])
if params[:query] == "aube"
#pub = Camping.find_by_id(1)
else
end
end
end
My model
def self.searchi(query, handicap, animaux, television, plage, etang, lac)
return scoped unless query.present?
result = left_outer_joins(:caracteristiquetests, :situations).where('nomdep LIKE ? OR name LIKE ? OR nomregion LIKE ? OR commune LIKE?', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%")
result = result.where('handicap LIKE ?', "%#{handicap}%") if handicap
result = result.where('animaux LIKE ?', "%#{animaux}%") if animaux
result = result.where('television LIKE ?', "%#{television}%") if television
result = result.where('plage LIKE ?', "%#{plage}%") if plage
result = result.where('etang LIKE ?', "%#{etang}%") if etang
result = result.where('lac LIKE ?', "%#{lac}%") if lac
return result
end
If you insist on using SQLite then you don't have many good options. The most common suggestion is to have extra columns in your database, that are normalized values so if your plage column contains "Marée" then you also have a column plage_ascii that contains "maree"
you need to create the additional columns with migrations, then you would have a before_save action in your model...
before_save :create_normalized_strings
def create_normalized_strings
self.handicap_ascii = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
self.animaux_ascii = animaux.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
# etc etc
end
Then in your search do...
if handicap
test_handicap = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
result = result.where('handicap_ascii LIKE ?', "%#{handicap}%")
end
It's not great, as it basically forces you to duplicate data in your database into extra columns. If you can consider more sophisticated databases other than SQLite then you'd be better off... personally I wouldn't ever use SQLite in a production environment.

Ruby on Rails - Implementing Simple Search with scopes

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

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

Resources