How can I convert that to MetaWhere or Arel? - ruby-on-rails

Consider a City Model having:
def self.search(field, search)
if search
where("#{field} LIKE ?", "%#{search}%")
else
scoped
end
end
How can I use Arel or Metawhere in that situation knowing that field is a String can have anything like:
"name"
"residents.name"
"state.name"
I want to do something like that (will not work):
def self.search(field, search)
if search
where(field =~ "%#{search}%")
else
scoped
end
end
So, what are your thoughts?
The real question is, how can I convert that:
"residents.name LIKE '#{value}%'"
To that:
:residents => { :name =~ "#{value}%" }

You should be able to use Arel like this.
def self.search(field, search)
if search
if field =~ /\./ # table and field
table, field = field.split('.')
arel_join = table.singularize.camelize.constantize.arel_table
joins(table.to_sym).where(arel_join[field].matches("%#{search}%"))
else
where(Resource.arel_table[field].matches("%#{search}%"))
end
else
scoped
end
end
There's a Railscast that does a good job of explaining the basics of using Arel in Rails 3.

Related

Ruby on Rails search like any

how could i implement a method that can search those records has any require field which has value like any word in the input search keyword.
module Searchable
module_function
def search_like_any(attributes, keyword)
end
end
For example, suppose i have a model book that has name, author, description attributes then if i search like this:
class Book < ApplicationRecord
include Searchable
end
Book.search_like_any([:name, :author, :description], "ruby on rails")
The return result should be books those have either name or author or description contains either "ruby" or "on" or "rails".
Thanks!
I found the solution
module Searchable
module_function
def search_like_any(attributes, keyword)
return if attributes.blank? || keyword.blank?
values = keyword.split(/[,?.\s]/).uniq.reject(&:blank?)
matches = attributes.uniq.product(values).map do |(attr, value)|
arel_table[attr].matches("%#{value}%")
end
where matches.inject(:or)
end
end
Maybe help you with this way.
def self.search keyword
word = "%#{keyword}%"
("name LIKE ? OR auth LIKE ? OR description LIKE ?",word, word, word)
end
Try this:
def search_like_any(attributes, keyword)
query_attrs = attributes.map { |attr| attr.to_s + ' LIKE :search_values'}.join(' OR ')
Book.where(query_attrs, search_values: keyword)
end
Here, query_attrs contains the actual query.

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.

Ignore parameters that are null in active record Rails 4

I created a simple web form where users can enter some search criteria to look for venues e.g. a price range. When a user clicks "find" I use active record to query the database. This all works very well if all fields are filled in. Problems occur when one or more fields are left open and therefore have a value of null.
How can I work around this in my controller? Should I first check whether a value is null and create a query based on that? I can imagine I end up with many different queries and a lot of code. There must be a quicker way to achieve this?
Controller:
def search
#venues = Venue.where("price >= ? AND price <= ? AND romance = ? AND firstdate = ?", params[:minPrice], params[:maxPrice], params[:romance], params[:firstdate])
end
You may want to filter out all of the blank parameters that were sent with the request.
Here is a quick and DRY solution for filtering out blank values, triggers only one query of the database, and builds the where clause with Rails' ActiveRecord ORM.
This approach safeguards against SQL-injection, as pointed out by #DanBrooking. Rails 4.0+ provides "strong parameters." You should use the feature.
class VenuesController < ActiveRecord::Base
def search
# Pass a hash to your query
#venues = Venue.where(search_params)
end
private
def search_params
params.
# Optionally, whitelist your search parameters with permit
permit(:min_price, :max_price, :romance, :first_date).
# Delete any passed params that are nil or empty string
delete_if {|key, value| value.blank? }
end
end
I would recommend to make method in Venue
def self.find_by_price(min_price, max_price)
if min_price && max_price
where("price between ? and ?", min_price, max_price)
else
all
end
end
def self.find_by_romance(romance)
if romance
where("romance = ?", romance)
else
all
end
end
def self.find_by_firstdate(firstdate)
if firstdate
where("firstdate = ?", firstdate)
else
all
end
end
And use it in your controller
Venue
.find_by_price(params[:minPrice], params[:maxPrice])
.find_by_romance(params[:romance])
.find_by_firstdate(params[:firstdate])
Another solution to this problem, and I think a more elegant one, is using scopes with conditions.
You could do something like
class Venue < ActiveRecord::Base
scope :romance, ->(genre) { where("romance = ?", genre) if genre.present? }
end
You can then chain those, which would work as an AND if there is no argument present, then it is not part of the chain.
http://guides.rubyonrails.org/active_record_querying.html#scopes
Try below code, it will ignore parameters those are not present
conditions = []
conditions << "price >= '#{params[:minPrice]}'" if params[:minPrice].present?
conditions << "price <= '#{params[:maxPrice]}'" if params[:maxPrice].present?
conditions << "romance = '#{params[:romance]}'" if params[:romance].present?
conditions << "firstdate = '#{params[:firstdate]}'" if params[:firstdate].present?
#venues = Venue.where(conditions.join(" AND "))

Rails simple filtering using LIKE

I'm looking for a simple way to build up a filter query using given column/value hash. All of the values should be used in LIKE queries. I can build up the query and the parameters in arrays and pass it to where, like this:
def self.filter(filters=nil)
if filters.nil? || filters.empty?
return all
end
query = []
value_array = []
filters.each do |key, value|
query << "#{key} LIKE ?"
value_array << "%#{value}%"
end
where([query.join(' AND ')] + value_array)
end
But I was wondering if there is a better way of doing this either built into Rails (using version 4) or if there is a super simple gem that can easily accept a has and turn into a LIKE filter?
a nice way to play nice with hashes in queries is to make use of the Hash#slice method and scopes:
...
filtering_params(params).each do |key, value|
#products = #products.public_send(key, value) if value.present?
end
def filtering_params(params)
params.slice(:status, :location, :starts_with)
end
class Product < ActiveRecord::Base
scope :status, -> (status) { where status: status }
scope :location, -> (location_id) { where location_id: location_id }
scope :starts_with, -> (name) { where("name like ?", "#{name}%")}
end
Taken from here. After all you might need to restrict you logic to some specific queries in your DSL.

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