User Lower Function in where condition in searchkick - ruby-on-rails

#ankane
How can i use postgres lower function in searchkick's where condition
I have below query which is working fine
klass.search(#params[:query], fields: [:name,:category_name],where: {or: [[{available_cities_name: "New Yo"},{available_cities_short_name: "NY"}]]}).hits
Now i want to use lower function but i am getting syntax error
klass.search(#params[:query],
fields: [:name,:category_name],
where: {
or: [ [
{"lower(available_cities_name) = ?", "New Yo"},
{"lower(available_cities_short_name) = ?", "ny"}
]]
}
).hits
I am getting below syntax error,
SyntaxError: unexpected '}', expecting end-of-input
e_cities_name) = ?", "New Yo"},{"lower(available_cities_shor
Can somebody tell me how to use lower function in searchkick ?

Elasticsearch does not have a lower function. To get around this, you can index a lowercase version of the field and query against that.
def search_data
{
available_cities_name: available_cities_name,
available_cities_name_lower: available_cities_name.downcase
}
end

There are two ways to pass args to part of your query. The first is to use hash-syntax eg: {arg_name: 'expected_value'} and the second is array-syntax: ["arg_name = ?", 'expected_value']
Your bug is that you are using array-syntax, but trying to pass it as a hash. ie: {"arg_name = ?", 'expected_value'} which is invalid syntax.
Instead try:
klass.search(#params[:query],
fields: [:name,:category_name],
where: {
or: [
["lower(available_cities_name) = ?", "New Yo"],
["lower(available_cities_short_name) = ?", "ny"]
]
}
).hits
or even just:
klass.search(#params[:query],
fields: [:name,:category_name],
where: ["lower(available_cities_name) = ? OR lower(available_cities_short_name) = ?", "New Yo", "ny"]
).hits
(Note: all code needs to be bug-tested before running).

Related

Query on Params

I want to get records based on params received. Sometimes I receive 2 params or sometimes 3. I have written a code to get results when all 3 params received but when I receive only 1 param am getting 0 results.
Example
Office.where(state: params[:state], type: params[:type]).where("name like ?","%#{params[:name]}%")
When i get values like
{state: "KA", type: "private", name: "google"}
But when I get the only {name: "google"} I get no records
I have tried with condition
If params[:name].present? && params[:state].present? && params[:type].present?
query
elsif condition
query
end
Let me know how can I solve this or any better way
You can do something like this
In controller
filter_params = params.slice(:state, :type, :name)
Office.filter(filter_params)
In Office model
scope :state, -> (state) { where(state: state) }
scope :type, -> (type) { where(type: type) }
scope :name, -> (name) { where("name LIKE ?", "%#{name}%") }
def self.filter(filter_params)
results = where(nil)
filter_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
PS: It runs a single query irrespective of the number of params!
Hope that helps!
If a parameter is missing, it will probably be blank. If you pass them all in this will result in clauses like type = ''. For example, if only name is passed in you'll get something like...
where name like '%google%' and type = '' and state = ''
You need to strip out the blank fields. There are various ways to do this. Since you have a special case, the name clause, one good way to handle this is to build the query piece by piece.
query = Office.all
query = query.where(state: params[:state]) if params[:state].present?
query = query.where(type: params[:type]) if params[:type].present?
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?
The query does not execute until you fetch the values from query.
If there's a lot of simple parameters you can make a Hash and remove the pairs with a blank value.
qparams = {
state: params[:state],
type: params[:type]
}.select { |k,v|
v.present?
}
query = Office.where(qparams)
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?
Or use the handy compact_blank gem.
using CompactBlank
qparams = {
state: params[:state],
type: params[:type]
}.compact_blank
query = Office.where(qparams)
query = query.where("name like ?","%#{params[:name]}%") if params[:name].present?

when i was pass empty value in params all my datas are showed in rails

def policy_details
policy_details = Policy.where("policy_number LIKE ? or assured_name LIKE ? or application_number LIKE ? or proposer_name LIKE ?","%#{params[:policy_number].present?}%","%#{params[:assured_name]}%","%#{params[:application_number]}%",
"%#{params[:proposer_name]}%")
if policy_details.present?
policy_details = policy_details.select(:assured_name, :policy_number, :application_number, :id, :issuance_date, :proposer_name, :policy_name, :base_premium)
end
output = {status: '10', data: policy_details}.to_json
render json: output, status: :ok
end
"%" matches everything, and you get all the records in the result.
so you can use ".present?". its working for if the value is that search was consider otherwise not.
policy_details = policy_details.where('assured_name LIKE ?',"%#{params[:assured_name]}%") if params[:assured_name].present?
policy_details = policy_details.where('proposer_name LIKE ?',"%#{params[:proposer_name]}%") if params[:proposer_name].present?
policy_details = policy_details.where('application_number LIKE ?',"%#{params[:application_number]}%") if params[:application_number].present?
policy_details = policy_details.where('client_id LIKE ?',"%#{params[:client_id]}%") if params[:client_id].present?
policy_details = policy_details.where('advisor_code LIKE ?',"%#{params[:advisor_code]}%") if params[:advisor_code].present?
If params[:assured_name] is nil or the empty string then
"%#{params[:assured_name]}%"
becomes
"%%"
Since % is a wildcard, doing a like on '%%' matches everything, and you get all the records in the result.
The database is behaving as expected. You need to define what you want your query to be when there is no assured_name.

Relation passed to #or must be structurally compatible. Incompatible values: [:references]

I have two queries, I need an or between them, i.e. I want results that are returned by either the first or the second query.
First query is a simple where() which gets all available items.
#items = #items.where(available: true)
Second includes a join() and gives the current user's items.
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
I tried to combine these with Rails' or() method in various forms, including:
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
.or(
#items
.joins(:orders)
.where(available: true)
)
But I keep running into this error and I'm not sure how to fix it.
Relation passed to #or must be structurally compatible. Incompatible values: [:references]
There is a known issue about it on Github.
According to this comment you might want to override the structurally_incompatible_values_for_or to overcome the issue:
def structurally_incompatible_values_for_or(other)
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
(Relation::MULTI_VALUE_METHODS - [:eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
Also there is always an option to use SQL:
#items
.joins(:orders)
.where("orders.user_id = ? OR items.available = true", current_user.id)
You can write the query in this good old way to avoid error
#items = #items.joins(:orders).where("items.available = ? OR orders.user_id = ?", true, current_user.id)
Hope that helps!
Hacky workaround: do all your .joins after the .or. This hides the offending .joins from the checker. That is, convert the code in the original question to...
#items =
#items
.where(orders: { user_id: current_user.id})
.or(
#items
.where(available: true)
)
.joins(:orders) # sneaky, but works! 😈
More generally, the following two lines will both fail
A.joins(:b).where(bs: b_query).or(A.where(query)) # error! 😞
A.where(query).or(A.joins(:b).where(bs: b_query)) # error! 😞
but rearrange as follows, and you can evade the checker:
A.where(query).or(A.where(bs: b_query)).joins(:b) # works 😈
This works because all the checking happens inside the .or() method. It's blissfully unaware of shennanigans on its downstream results.
One downside of course is it doesn't read as nicely.
I ran into the same issue, however the code was defined in a different place and was very difficult to change directly.
# I can't change "p"
p = Post.where('1 = 1').distinct # this could also be a join
And I needed to add an or statement to it
p.or(Post.where('2 = 2'))
The following code won't raise an error, because it has distinct like the initial relationship.
p.or(Post.where('2 = 2').distinct)
The problem with it it that it only works as long as you know the relationship. It may or not have a join, or distinct.
This works regardless of what the relationship is:
p.or(p.unscope(:where).where('2 = 2'))
=> SELECT DISTINCT `posts`.* FROM `posts` WHERE ((1 = 1) OR (2 = 2))
It occurs when you try to combine two multi-active records of the same type, but one of them has a joins value or an includes value, or in your case a reference value, that the other does not.
Therefore we need to match the values between them, and I found a general way to do this without knowing the actual values in advance.
items_1 = #items.joins(:orders)
.where(orders: { user_id: current_user.id})
items_2 = #items.where(available: true)
.joins(items_1.joins_values)
.includes(items_1.includes_values)
.references(items_1.references_values)
#items = items_1.or(items_2)
just solve it!
def exec_or_statement(q1, q2)
klass = q1.klass
key = klass.primary_key
query_wrapper_1 = {}
query_wrapper_1[key] = q1
query_wrapper_2 = {}
query_wrapper_2[key] = q2
klass.where(query_wrapper_1).or(klass.where(query_wrapper_2))
end
query_1 = #items.where(available: true)
query_2 =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
exec_or_statement(query_1, query_2)

How to select all records that start with certain string?

I have several records for which title starts with:
Nick1
Nick2
Nick3
Othername1
How can I select all the records of which the title starts with "Nick" and have them rendered in the correct order? Something like:
#records = Record.where(title starts with: params[:title_name])
render json: #records
You can use the LIKE operator here:
#records = Record.where('title LIKE ?', "#{params[:title_name]}%").order(:title)
I would prefer to put these into a scope:
scope :title_search, ->(title){ where('title LIKE ?', "#{title}%") }
and call it via:
#records = Record.title_search(params[:title_name])
You can try Searchlogic .
Then it's as easy as:
#search = Record.new_search(params[:search])
#search.condition.title_starts_with = "Nick"
#models = #search.all
Or you can try
Record.where("title LIKE :prefix", prefix: "#{prefix}%")
I'd recommend using arel as in "How to do a LIKE query in Arel and Rails?"
records = Record.arel_table
Record.where records[:title].matches("#{params[:title_name]}%")

Adding a LIKE criteria to a Rails Conditions block

Consider the following code which is to be thrown at an AR find:
conditions = []
conditions[:age] = params[:age] if params[:age].present?
conditions[:gender] = params[:gender] if params[:gender].present?
I need to add another condition which is a LIKE criteria on a 'profile' attribute. How can I do this, as obviously a LIKE is usually done via an array, not a hash key.
You can scope your model with hash conditions, and then perform find on scope with array conditions:
YourModel.scoped(:conditions => conditions).all(:conditions => ["profile like ?", profile])
Follwing is ugly but it works
conditions = {} #This should be Hash
conditions[:age] = params[:age] if params[:age].present?
conditions[:gender] = params[:gender] if params[:gender].present?
conditions[:profile] = '%params[:profile]%' if params[:profile].present?
col_str ="" #this is our column names string for conditions array
col_str = "age=:age" if params[:age].present?
col_str+= (col_str.blank?)? "gender=:gender" :" AND gender=:gender" if params[:gender].present?
col_str += (col_str.blank?) 'profile like :profile' : ' AND profile like :profile' if params[:profile].present?
:conditions=>[col_str , conditions]
When you call your active record find, you send your conditions string first, then the hash with the values like :
:conditions => [ "age = :age AND gender = :gender AND profile LIKE :profile", conditions ]
that way you can keep doing what you are doing :)

Resources