Ruby On Rails Dynamic Where Search - ruby-on-rails

I am using postgres for the db.
Service Address Controller contains this line
#service_addresses = ServiceAddress.where("customer_id =?" , params[:customer_id]).search(params[:search])
Service Address Model Method:
def self.search(query)
where("street LIKE ? OR city LIKE ?", "%#{query.to_s}%","%{query.to_s}%")
end
In my view I have a search bar that sends params
service_addresses?utf8=✓&search=123+Echo+Dr+New+York
Lets assume I have two columns on my view Street & City
If I search 123 Echo Dr. New York it will NOT return a record where Street = 123 Echo Dr. and the City = New York
However, if I simply search 123 Echo Dr. or 123 it will return all records that have that in the Street or City column. Similairly if I search New York it will return records that contain New York
I tried parsing out the search param into an array (successfully) and then using a loop to essentially build a string of "%#{parsed_query[i]}%" However when I tried to pass the string of binds to my where statement, but I got an error stating I have the wrong number of binds. It was treating my string variable as one bind.
Not able to use Data Tables gem!
Thank you SOF community.

Iterate over the tokens parsed from the query string to build up your query with ActiveRecord. Note that this will result in a SQL query many AND conditions.
#service_addresses = ServiceAddress.where("customer_id =?" , params[:customer_id])
parsed_query.each do |token|
#service_addresses = #service_addresses.search(token)
end
Update for case-insensitivity
To ignore case you can convert all strings to either uppercase or lowercase.
# Service Address Model Method:
def self.search(query)
where("upper(street) LIKE ? OR upper(city) LIKE ?", "%#{query.to_s.upcase}%","%{query.to_s.upcase}%")
end
Alternative
You may wish to look into document matching (full-text search). Postgres has some nice support for this. The topic is too much to cover here, but there are many resources online. A good place to start is the Postgres documentation

If I understand you problem, try this:
def self.search(keyword)
keyword_search = "%#{keyword.downcase}%"
where('lower(city) LIKE :search OR lower(street) LIKE :search', search: keyword_search)
end

Related

Searching for specific characters in user input

Within my house table I have a postcode for each house.
I also have an index view for my housing table that contains a table which contains headings such as 'Name', 'Address', 'State'. I was looking to integrate a text_field_tag that would allow user's to input the 9 digits of a postcode in order to filter the table to only show the house with that postcode. However, I also want the user to be able to input the first 4 digits of their postcode e.g. '7644' and it would display all houses that begin with '7644' e.g. two records one with the postcode of the '76444-5645' and '76443-123'. Ideally I would apply logic through my '#search' variable within my houses controller. However I am up to any ideas or tips.
In order to instantiate the house model I would use #house = House.all
I'll be honest I don't know where to begin with this. I have arel_sql in my system so I assume that would be used to query for the search.
It depends on how your models/controllers are defined but you're probably looking for the SQL operator LIKE + '%', which allows you to search for a pattern in a given column. Example:
LIKE Operator
Description
WHERE CustomerName LIKE 'a%'
Finds any values that start with "a"
Assuming you're using ActiveRecord and your model is House, it wouldn't event need to instantiate all houses. Your code would look something like this:
postcode = '7644'
#houses = House.where('postcode LIKE ?', "#{postcode}%") # this returns where the postcode starts with '7644'
another similar SO answer for reference

Query in a string column for one of the value in an array like multiple OR (using full text search)

In a rails 4 app, in one model I have a column containing multiple ids as a string with comma separated values.
"123,4568,12"
I have a "search" engine that I use to retrieve the records with one or many values using the full text search of postgresql I can do something like this which is very useful:
records = MyModel.where("my_models.col_name ## ?", ["12","234"])
This return all the records that have both 12 and 234 in the targeted column. The array comes from a form with a multiple select.
Now I'm trying to make a query that will find all the records that have either 12 or 234 in there string.
I was hopping to be able to do something like:
records = MyModel.where("my_models.col_name IN (?)", ["12","234"])
But it's not working.
Should I iterate through all the values in the array to build a query with multiple OR ? Is there something more appropriate to do this?
EDIT / TL;DR
#BoraMa answer is a good way to achieve this.
To find all the records containing one or more ids referenced in the request use:
records = MyModel.where("my_models.col_name ## to_tsquery(?)", ["12","234"].join('|'))
You need the to_tsquery(?) and the join with a single pipe |to do a OR like query.
To find all the records containing exactly all the ids in the query use:
records = MyModel.where("my_models.col_name ## ?", ["12","234"])
And of course replace ["12","234"] with something like params[:params_from_my_form]
Postgres documentation for full text search
If you already started to use the fulltext search in Postgres in the first place,I'd try to leverage it again. I think you can use a fulltext OR query which can be constructed like this:
records = MyModel.where("my_models.col_name ## to_tsquery(?)", ["12","234"].join(" | "));
This uses the | operator for ORing fulltext queries in Postgres. I have not tested this and maybe you'll need to do to_tsvector('my_models.col_name') for this to work.
See the documentation for more info.
Suppose your ids are :
a="1,2,3,4"
You can simply use:
ModelName.find(a)
This will give you all the record of that model whose id is present in a.
I just think a super simple solution, we just sort the ids in saving callback of MyModel, then the query must be easier:
class MyModel < ActiveRecord::Base
before_save :sort_ids_in_col_name, if: :col_name_changed?
private
def sort_ids_in_col_name
self.col_name = self.col_name.to_s.split(',').sort.join(',')
end
end
Then the query will be easy:
ids = ["12","234"]
records = MyModel.where(col_name: ids.sort.join(',')

Rails 4 ActiveRecord: How to drill down to nested 3 layer record and see if an array contains string

So I have a nested activerecord which contains an array of hashes. I am trying to get the country in an app I am making using a country code that is stored in one of the elements in the array.
the record is described:
user.rules.first.countries.first["country_code"]
user has_many rules,
rules contains a jsonb column called countries
countries is a jsonb array of hashes
at the moment I am iterating through all of them to find the record. e.g.
country_code_to_find = "US"
user.rules.each do |r|
r.countries.each do |c|
if c["country_code"] == "US"
# Do some stuff
end
end
end
Is there a way I can access that country with a single line using a .where() or scope or something like that? I am using rails 4, activerecord and postgres.
Without knowing more about the JSON structure, I'm not confident you can access "that country" with a single query, since a "country" is an element in an array. You can query for the Rule objects that contain the desired "country". Something like this might work
user.rules.where("
countries #> '[{\"country_code\": \"US\"}]'
")
Depending on your business logic, it might be enough to know that this user has at least one rule with country=US.
country_code_to_find = "US"
if user.rules.where("countries #> '[{\"country_code\": \"#{country_code_to_find}\"}]'").exists?
# Do some stuff
end
More on Postgres' JSONB functions.
These questions seem related, but are not Rails-specific:
Postgresql query array of objects in JSONB field.
Query for array elements inside JSON type
Using the answer from messenjah I was able to get a solution that worked. Had to find the index of the array so I could use it. To give some more information that messenjar was after here is the json:
countries: [{"code"=>"US", "name"=>"United States", "states"=>{"NY" => "New York"}}, {"code"=>"MX", "name"=>"Mexico", "states"=>{"YUC" => "Yucatán"}}]
Then to get an array of the states I used:
country_code = "MX"
rule = #user.rules.where("countries #> '[{\"code\": \"#{country_code}\"}]'").first
country_index = rule.countries.index {|h| h["code"] == country_code }
states = rule.countries[country_index]["states"]
Basically this get the index of the array of hashes that I want. Not sure if this is better or worse than what I was using to begin with. But it works. Happy to consider other answers if they can clean this up.

Multi parameter search via user input - ruby on rails & mongodb

I have a web page where a user can search through documents in a mongoDB collection.
I get the user's input through #q = params[:search].to_s
I then run a mongoid query:
#story = Story.any_of( { :Tags => /#{#q}/i}, {:Name => /#{#q}/i}, {:Genre => {/#{#q}/i}} )
This works fine if the user looks for something like 'humor' 'romantic comedy' or 'mystery'. But if looking for 'romance fiction', nothing comes up. Basically I'd like to add 'and' 'or' functionality to my search so that it will find documents in the database that are related to all strings that a user types into the input field.
How can this be done while still maintaining the substring search capabilties I currently have?Thanks in advance for help!
UPDATE:
Per Eugene's comment below...
I tried converting to case insensitive with #q.map! { |x| x="/#{x}/i"}. It does save it properly as ["/romantic/i","/comedy/i"]. But the query Story.any_of({:Tags.in => #q}, {:Story.in => #q})finds nothing.
When I change the array to be ["Romantic","Comedy"]. Then it does.
How can I properly make it case insensitive?
Final:
Removing the quotes worked.
However there is now no way to use an .and() search to find a book that has both words in all these fields.
to create an OR statement, you can convert the string into an array of strings, and then convert the array of strings into an array of regex and then use the '$in' option. So first, pick a delimeter - perhaps commas or space or you can set up a custom like ||. Let's say you do comma seperated. When user enters:
romantic, comedy
you split that into ['romantic', 'comedy'], then convert that to [/romantic/i, /comedy/i] then do
#story = Story.any_of( { :Tags.in => [/romantic/i, /comedy/i]}....
To create an AND query, it can get a little more complicated. There is an elemMatch function you could use.
I don't think you could do {:Tags => /romantic/i, :Tags => /comedy/i }
So my best thought would be to do sequential queries, even though there would be a performance hit, but if your DB isn't that big, it shouldn't be a big issue. So if you want Romantic AND Comedy you can do
query 1: find all collections that match /romantic/i
query 2: take results of query 1, find all collections that match /comedy/i
And so on by iterating through your array of selectors.

Help with rails active record querying (like clause)

I want my code to do two things that is currently not doing
#students = Student.where(["first_name = ? OR middle_name = ? OR last_name = ?", params[:query].split])
Work. (it says im supposed to pass 4 parameters but I want the user to be able to type words and find by those words on each of those fields and return whatever matches)
Actually use Like clause instead of rigid equal clause.
Please Help.
This looks like a problem that would be better suited to using search rather than SQL. Have you considered something like thinking sphinx or act as ferret (solr would probably be overkill).
...if you must do this in sql, you could build a query something like this:
cols = ['first_name', 'last_name', 'middle_name']
query = 'John Smith'
sql_query = cols.map{|c| query.split.map{|q| "#{c} like '?'"}}.join(' OR ')
sql_query_array = query.split * cols.count
Student.where(sql_query, sql_query_array)
I agree with the previous advice that if you need to do search you should look at something like Solr or Sphinx.
Anyhow, this should help you out.
def search
query = params[:query].split.map {|term| "%#{term}%" }
sql = "first_name LIKE ? OR middle_name LIKE ? OR last_name LIKE ?"
#students = Student.where([sql, *query])
end
The answer to step 1 is using Ruby's awesome little feature called the "splat operator" which allows you to take an array and evaluate it as a list of arguments.
The answer to step 2 is to just massage the query string you get back from the params and turn it into something you can use with the LIKE operator. I basically stole this from Railscasts Simple Search Form episode.
I'm not sure if you want all fields to search the same term if that is the case then you can do this:
where("first_name LIKE :term OR middle_name LIKE :term OR last_name LIKE :term", { term: "%#{params[:term]}%"})
No need for any crazy split or map or anything else, this is just straight ActiveRecord

Resources