Postgres LIKE in a rails application - ruby-on-rails

I have a Postgres LIKE statement like this:
#favorites.find_each do |profiles|
#related_profiles = Profile.where('name LIKE ?', '%' + profiles.name + '%')
end
What I'm trying to do is loop through the favourites variable and find all profiles that contain some characters of the name.
For example a name "jasson jackson" should be found if the name contains "jackson" or "jasson"

The query you're looking for would be like:
Profile.where("name LIKE ?", "%#{profiles.name}%")
But note that your #related_profiles may not be properly assigned as the result would be the same as saying:
Profile.where("name LIKE ?", "%#{#favorites.last.name}%")
whereas, I doubt if that is what you need.
Also note that the it would be an ActiveRecord::Collection, an array like object.
A way to work around that is to initialize #related_profiles = [] and then at each point through your loop, you could do:
#related_profiles +=
Profile.where("name LIKE ?", "%#{profiles.name}%")
or another way is:
names = #favorites.map(&:name)
query = names.map{|name| "name LIKE '%#{name}%'"}.join(" OR ")
OR
query = #favorites.map{|favorite| "name LIKE '%#{favorite.name}%'" }.join(" OR ")
THEN
profiles = Profile.where(query)
UPDATE
Based on a comment from #joshrumbut, I decided to reimplement using the bind parameters.
However, code clarity is a bit lost, but here's a way it could be done:
names = #favorites.map(&:name)
query = names.map{|favorite| "name LIKE ?" }.join(" OR ")
profiles = Profile.where(query, *names.map{|name| ("%#{name}%")})
Based on the comment from #muistooshort, I removed the quotes from the first two queries and I think this approach looks a bit cleaner, as he suggested. From the docs
Profile.where('name like any(array[?])', names.map { |s| "%#{s}%" })

For better results with this type of search, You can try use FTS (Full text search).
Rails has a gem with this feature implemented:
https://github.com/Casecommons/pg_search
PgSearch builds named scopes that take advantage of PostgreSQL's full text search.
With this gem installed on your project, just use this statement for search:
PgSearch.multisearch("jasson")
Other option is Elasticsearch, with him you can index yours registers for a better text search.
Hope it helps :)

I'll try with Postgres patterns and "OR" operator - postgres doc
names = #favorites.map(&:name)
Profile.where('name LIKE %(?)%', names.join('|'))

Related

Rails Query Unique Items

I have a search bar which works fine but it produces a duplicate every time it shows the correct result.
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).page(params[:page]).per_page(18)
end
Any clue where I'm going wrong?
Try to the following
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).distinct.page(params[:page]).per_page(18)
To retrieve objects from the database, Active Record provides several finder methods. Each finder method allows you to pass arguments into it to perform certain queries on your database without writing raw SQL.
You can see this Rails Guide for very well understand
include .uniq
try this,
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).uniq.page(params[:page]).per_page(18)
end
Use uniq or distinct to avoid duplicate records. In your case, you should use distinct:
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).distinct.page(params[:page]).per_page(18)
end

Rails .where any field contains specific text

Is there a short-hand way of querying a Rails database for any record that has a field containing a specific piece of text? I know I could code every field with a .where("field_name LIKE ?", "my text"), but I have several fields and am wondering if there is a shorter way of doing this.
Thanks in advance.
I do not know of a framework-way to do so. You could code something using
my_attributes = YourModel.attributes
# delete attributes you do not need, like `id` etc.
# or just create an array with your desired attributes,
# whichever way is faster
queries = my_attributes.map { |attr| "#{attr} LIKE %insert_your_text_here%" }
# Do not use this if the text your looking for is provided by user input.
built_query = queries.join(" OR ")
YourModel.where(built_query)
This could bring you closer to your goal. Let me know if this makes sense to you.
edit: The answer https://stackoverflow.com/a/49458059/299781 mentions Ransack. That's a nice gem and takes the load off of you. Makes it easier, nicer and performs better :D
Glad you like this, but pay attention that you make your app open for sql injection, if you take user-input as the text you are looking for. (with this solution) Ransack would alleviate that.
class MyModel
scope :search_like, -> (field_name, search_string) {where("#{field_name} LIKE ?", "%#{search_string}%")}
end
then you can call it like:
MyModal.search_like('name', 'foobar')
UPDATE based on #holgar answer but beware if not indexed these searches can be slow on large data sets:
class MyModel
def self.multi_like(search_string)
my_attributes = [:first_name, :last_name] # probalby only use string fields here
queries = my_attributes.map { |attr| "#{attr} LIKE '%#{search_string}%'" }
where(queries.join(" OR "))
end
end
If you want full fledge text search based on params then you can use ransack gem

Connecting subqueries with 'OR' in active Record / Ruby on Rails framework

Hello I struggle creating a specific query.
I have a searchfield with input parameter "{search}". I want to use this search field to find all tables which have a plate with veggies of a specific flavor or description.
I already got a tricky query which does exactly what it is supposed to do - so this works perfectly fine!
Tables.where(id: Plate.select("plate_id").where(Veggie.select("id").where('description LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")))
Aside from Veggies, suppose there is also a SQL-Table calles Fruits. Now i want to search for all Tables which have Plates which have Veggies OR(!!!) Fruits fitting to the description or flavor of the searchfield input.
I experimented a lot but can not find the right way.
Here is one of my experiments which does not work though:
Tables.where(id: Plate.select("plate_id").where((Veggie.select("id").where('description LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")).or(Fruit.select("id").where('color LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")))
The code above does not work! The problem is, I can not figure out how to combine those 2 subqueries. The or statement does not seem to work. Any help appreciated.
EDIT:
As has been pointed out, i now tried doing it with the UNION command. Unfortunately the .union() option with the Gem active_record_union seems to work only for rails 4+.
So i simulated a union with the following code.
def self.search2(search2)
if search2
if search2.length > 0
#Plate1 = Plate.where(veggie_id: Veggie.select("id").where('description LIKE ? OR color LIKE ?', "%#{search2}%","%#{search2}%"))
#Plate2 = Plate.where(fruit_id: Fruit.select("id").where('description LIKE ? OR color LIKE ? OR flavor LIKE ?', "%#{search2}%","%#{search2}%", "%#{search2}%" ))
#Plate12 = #Plate1+#Plate2
#table_ids = Array.new
#Plate12.each do |plate|
#table_ids.push(plate.table_id)
end
where(id: #table_ids)
else
scoped
end
else
scoped
end
end
Actually, i do believe this is very messy! It DOES work as intended, but i am a fraid it is a bit slow and unperformant. If anyone has a better suggestion i am glad about any help.

ferret / acts_as_ferret : Specify that a field should be blank

This relates to the lucene-based search engine, Ferret.
https://github.com/dbalmain/ferret
Let's say i have a model with two fields, myfield1 and myfield2. I want to get records that have myfield equal to "foo", or that have null (or an empty string) for myfield but have myfield2 set to "foo".
I DON'T want to get records that have, for example, myfield = "bar" and myfield2 = "foo". So, it's not as simple as just saying "myfield:foo || myfield2: foo" - i only want to look at myfield2 if myfield is empty.
The sql equivalent would be where (myfield = 'foo') or ((myfield is null or myfield = '') and myfield2 = 'foo'). What would the ferret search string equivalent of this be?
The following doesn't work, but it's an example of the sort of thing I'm after:
"myfield:foo || (myfield:<blank> && myfield2:foo)"
thanks, Max
BTW in case it's relevant i'm using acts_as_ferret in ruby on rails, but i think my question really just relates to a ferret search string. I'm using the ferret gem, v=0.11.6
EDIT: Slightly dirty-feeling solution below, would still like to know if it's possible just with the query string like above.
OK, i got around this by adding a new method, "myfield_blank":
def myfield_blank
myfield_blank?.to_s
end
then adding myfield_blank => {}, to my acts_as_ferret index definition. So now i can say
"myfield:foo || (myfield_blank:true && myfield2:foo)"
This works but like I say i'd still like to know if I can just do it in the query, without making new fields: this approach would be unacceptably hacky if i wanted to do it for lots of different fields. thanks
According to the source ?* should match any nonempty string, so you can try to do it this way:
'myfield:"foo" || (-myfield:"?*" && myfield2:"foo")'
And also I don't really see why 'myfield:"foo" || (myfield:"" && myfield2:"foo")' shouldn't work, but you probably already tried it...

Ruby set dictionary for search with activerecord

In my rails app i'm fetching data from mysql database, part of code:
#search = ArtLookup.find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE) and ARL_KIND = 1', search_condition.gsub(/[^0-9A-Za-z]/, '')])
But main trouble that i have different suppliers price list's, and there i have different coding's for same variable in db, for example:
LEMFÖRDER
But how can i set dictionary for my search_condition so that if my search_condition is for example:
LEM?FORDER or
LEMFOERDER or
LEMFÖRDER
It will find my LEMFÖRDER in db?
I know that it could sound very strange, sorry for my english, but i explain all on my example...
I think that, in this case, you should start using a library to deal with full-text-search and additional search capabilities, like Solr or Sphinx.
Take a look at http://pat.github.com/ts/en/searching.html.
This kind of complexity is common and it is already implemented in many algorithms.
Hope it helps!
You could do this by using ActiveRecord's AREL engine like the following:
def lookup(*alternatives)
match_condition = 'MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)'
or_conditions = alternatives.map do |alternative|
ArtLookup.where(match_condition, alternative).
where_values.reduce(:and)
end
and_condition = ArtLookup.where('ARL_KIND = 1').where_values.reduce(:and)
# Build a disjunction
conditions = or_conditions.shift
or_conditions.each do |condition|
conditions = conditions.or(condition)
end
# Build the final conjunction
conditions = conditions.and(and_condition)
ArtLookup.where(conditions)
end
Then you can find the objects like the following:
#search = lookup('LEM?FORDER', 'LEMFOERDER', 'LEMFÖRDER')
Or directly provide an array:
alternatives = [
'LEM?FORDER',
'LEMFOERDER',
'LEMFÖRDER'
]
#search = lookup(*alternatives)
I'm aware of the fact that this is far too much code for the simple thing it's doing. But it should do it and I'm not aware of a much better way. I didn't test that code, so it could contain some minor mistakes.
If I've understood your question correctly, you want to have Mysql treat those three values as the same thing. Now, assuming that they are considered the same thing in a specific language (for example, ß = ss in German), Mysql will handle this automatically based on your collation settings, so selecting the correct collation should fix it for you.

Resources