Searching for specific characters in user input - ruby-on-rails

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

Related

Grails 3 - return list in query result from HQL query

I have a domain object:
class Business {
String name
List subUnits
static hasMany = [
subUnits : SubUnit,
]
}
I want to get name and subUnits using HQL, but I get an error
Exception: org.springframework.orm.hibernate4.HibernateQueryException: not an entity
when using:
List businesses = Business.executeQuery("select business.name, business.subUnits from Business as business")
Is there a way I can get subUnits returned in the result query result as a List using HQL? When I use a left join, the query result is a flattened List that duplicates name. The actual query is more complicated - this is a simplified version, so I can't just use Business.list().
I thought I should add it as an answer, since I been doing this sort of thing for a while and a lot of knowledge that I can share with others:
As per suggestion from Yariash above:
This is forward walking through a domain object vs grabbing info as a flat list (map). There is expense involved when having an entire object then asking it to loop through and return many relations vs having it all in one contained list
#anonymous1 that sounds correct with left join - you can take a look at 'group by name' added to end of your query. Alternatively when you have all the results you can use businesses.groupBy{it.name} (this is a cool groovy feature} take a look at the output of the groupBy to understand what it has done to the
But If you are attempting to grab the entire object and map it back then actually the cost is still very hefty and is probably as costly as the suggestion by Yariash and possibly worse.
List businesses = Business.executeQuery("select new map(business.name as name, su.field1 as field1, su.field2 as field2) from Business b left join b.subUnits su ")
The above is really what you should be trying to do, left joining then grabbing each of the inner elements of the hasMany as part of your over all map you are returning within that list.
then when you have your results
def groupedBusinesses=businesses.groupBy{it.name} where name was the main object from the main class that has the hasMany relation.
If you then look at you will see each name has its own list
groupedBusinesses: [name1: [ [field1,field2,field3], [field1,field2,field3] ]
you can now do
groupedBusinesses.get(name) to get entire list for that hasMany relation.
Enable SQL logging for above hql query then compare it to
List businesses = Business.executeQuery("select new map(b.name as name, su as subUnits) from Business b left join b.subUnits su ")
What you will see is that the 2nd query will generate huge SQL queries to get the data since it attempts to map entire entry per row.
I have tested this theory and it always tends to be around an entire page full of query if not maybe multiple pages of SQL query created from within HQL compared to a few lines of query created by first example.

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(',')

Ruby On Rails Dynamic Where Search

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

Ruby on Rails: Return name if table value is between a specified range

I have three tables (that are relevant to this problem). One table is called organizations.
I also have a table called organization_details, which contains organization_id and multi-row information about the organization.
I work in the event industry, so the organization_details table contains a column called total_attendance, where a person can input an integer of the org's attendance for a certain year.
The third table is called divisions. This has five rows total, with columns division_smallest and division_largest (referring to the attendance range). Each row has a range to separate which division an organization should belong to according to their most recent attendance record.
For example, one row in the division table shows a division_smallest equal to 1 and a division_largest equal to 100000 (again, referring to attendance). Finally, the division table also has a name column (e.g. "Division 1").
I want the app to automatically figure out which division an organization belongs to according to their most recent total_attendance. Ideally, the division's name would display in the organization index and show pages.
I'd like to make a custom method for this, but am unsure how best to tackle it. I've read a little bit about .between? as in (possibly) .between?(division.division_smallest, division.division_largest) return "#{division.name}"
...But I am not sure how the entire method would work or if I need to steer away from that entirely. I would greatly appreciate any insight into this!
My suggestion is to add the following method to organization.rb
def division_name
last_details = organization_details.order('created_at DESC').first
if last_details.present?
Division.where(':attendance >= division_smallest AND :attendance <= division_largest', attendance: last_details.attendance).first.name
else
"None"
end
end
The code first grabs the organization details that have been created most recently. If the organization has organization details it uses the attendance value to select the appropriate division and it returns that division's name. If the organization doesn't have any organization_details it returns the string "None". You may also want to handle the case where the attendance isn't inside of the range on any of the divisions you have defined.
I hope this points you in the right direction.
A naive implementation might look something like this:
class Division
def self.for_attendance(total)
first('? BETWEEN divisions.division_smallest AND divisions.division_largest', total)
end
end
class Organization
def latest_division
Division.for_attendance(organization_details.last.try(:total_attendance))
end
end
Now calling some_organization.latest_division will pull the latest division for that organization. This is great for a 'show' page, but will run you into trouble when you have an 'index' with many Organizations - these 2 queries will need to run for each Organization (an N+1 problem). Instead use this:
class Division
def self.merge_latest!(organizations)
left_join = "LEFT JOIN organization_details od2 ON organization_details.organization_id = od2.organization_id AND organization_details.created_at < od2.created_at"
subquery = OrganizationDetails.where(organization_id: organizations.map(&:id)).
joins(left_join).
where(od2: {id: nil}).to_sql
divisions = joins("#{subquery} as t ON t.total_attendance divisions.division_smallest AND divisions.division_largest").
select('divisions.*, t.organization_id')
organizations.each {|org| org.latest_division = divisions.detect{|d| d.organization_id == org.id}
end
end
def Organization
attr_accessor :latest_division
end
Now you can call Division.merge_latest!(organizations) to collect the latest division for all the organizations in a single query, addressable via an organization's :latest_division attribute.

CONCAT_WS for Rails?

No matter what language I'm using I always need to display a list of strings separated by some delimiter.
Let's say, I have a collection of products and need to display its names separated by ', '.
So I have a collection of Products, where each one has a 'name' attribute. I'm looking for some Rails method/helper (if it doesn't exist, maybe you can give me ideas to build it in a rails way) that will receive a collection, an attribute/method that will be called on each collection item and a string for the separator.
But I want something that does not include the separator at the end, because I will end with "Notebook, Computer, Keyboard, Mouse, " that 2 last characters should not be there.
Ex:
concat_ws(#products, :title, ", ")
#displays: Notebook, Computer, Keyboard, Mouse
Supposing #products has 4 products with that names of course.
Thanks!
you should try the helper to_sentence.
If you have an array, you can do something like
array.to_sentence. If your array has the data banana, apple, chocolate it will become:
banana, apple and chocolate.
So now if you have your AR Model with a field named, you could do something like
MyModel.all.map { |r| r.name }.to_sentence
#products.map(&:title).join(', ')
As #VP mentioned, Array#to_sentence does this job well in rails. The code for it is here:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/array/conversions.rb
Saying that, its use of the Oxford Comma is questionable :-)

Resources