Rails .where any field contains specific text - ruby-on-rails

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

Related

Ruby: Hash: use one record attribute as key and another as value

Let's say I have a User with attributes name and badge_number
For a JavaScript autocomplete field I want the user to be able to start typing the user's name and get a select list.
I'm using Materialize which offers the JS needed, I just need to provide it the data in this format:
data: { "Sarah Person": 13241, "Billiam Gregory": 54665, "Stephan Stevenston": 98332 }
This won't do:
User.select(:name, :badge_number) => { name: "Sarah Person", badge_number: 13241, ... }
And this feels repetitive, icky and redundant (and repetitive):
user_list = User.select(:name, :badge_number)
hsh = {}
user_list.each do |user|
hsh[user.name] = user.badge_number
end
hsh
...though it does give me my intended result, performance will suck over time.
Any better ways than this weird, slimy loop?
This will give the desired output
User.pluck(:name, :badge_number).to_h
Edit
Though above code is one liner, it still have loop internally. Offloading such loops to database may improve the performance when dealing with too many rows. But there is no database agnostic way to achieve this in active record. Follow this answer for achieving this in Postgres
If your RDBMS is Postgresql, you can use Postgresql function json_build_object for this specific case.
User.select("json_build_object(name, badge_number) as json_col")
.map(&:json_col)
The whole json can be build using Postgresql supplied functions too.
User.select("array_to_json(array_agg(json_build_object(name, badge_number))) as json_col")
.limit(1)[0]
.json_col

Function parameter and different use as a object and SQL query

I am trying to build a function with a parameter and then use this parameter all over my function but I have some issue, I don't know how to convert it for this two different cases
def html_append(attribute)
[...].order(attribute: :asc) [...]
[...]current_object.attribute[...]
Do you have any lead?
Have a great day.
Jonathan
So what you could do here is send the attribute in as a symbol and handle it as follows:
def html_append(attribute)
Post.order(attribute => :asc)
current_object.public_send(attribute)
end
example_attribute = :name
html_append(example_attribute)
This should work just fine, the only thing with public_send is its gives whoever is sending in the attribute access to invoke any of your public methods (send gives then access to invoke all of them) so I would be careful where you are getting this attribute from and have a well thought out public and private interface for this object.
Great question!
For your second example, you can simply do something like this.
def html_append(attribute)
current_object[attribute]
end
Using the order method is a bit more complicated. Be careful because it might seem like a good idea to do something like this.
def unsafe_method(attribute)
[...].order("#{attribute} ASC") [...]
end
The above example would be vulnerable to SQL injection. In an ideal world, you’d be able to do something like this.
Model.all.order("? ASC", attribute)
But unfortunately that’s not part of the Rails API. As things are, you need to make sure that anything going into order is safe to execute against your database. There's a great answer about this already.
One simple option would be to have an array of safe attributes which you can validate the argument against. But be careful using this because if the if statement is removed, the method is open to SQL injection once again.
def safe_method(attribute)
safe_order_options = ["name", "email", "phone"]
if attribute.in? safe_order_options
[...].order("#{attribute} ASC") [...]
end
end
And even better option would be this.
def safer_method(attribute)
safe_order_options = ["name", "email", "phone"]
order_index = safe_order_options.index(attribute)
[...].order("#{safe_order_options[order_index]} ASC") [...]
end
Hope that helps. Sorry if there are any typos. I haven't tested this code, but it should work in principle.
What ever you do with attribute It is better to check for column names for security reasons.
def html_append(attribute)
return unless Post.class.column_names.include?(attribute.to_s)
# Above guard condition will make sure the security of below code
# your code do public_send(attribute) or send(). You are safe.
end

Rails Modify Array in Loop

I thought this would be a simple task, but I'm finding it difficult to make Rails do what I want.
I've got an array of dates.
So I thought that something like this would work:
def index
#datetimes = Books.all.map(&:checkouts).flatten.map(&:out_date)
#datetimes.each do |c|
c.to_date
end
end
Then I can just call this in my view:
%ul
-#datetimes.each do |c|
%li=c
How do I modify each key in the array? What am I missing here?
Thanks, so much for being nice to new, novice, and ignorant hobbyists like myself.
.each doesn't modify the caller. It simply loops through. You could change the controller action to just this:
#datetimes = Books.all.map(&:checkouts).flatten.map{|e| e.out_date.to_date}
You might also want to explore including :checkouts in your Books query to avoid N+1 queries. Or perhaps doing something like this maybe.
Checkout.where("book_id is not null").map{|e| e.out_date.to_date}

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.

Rails sort columns by column name in another table

I'm using handles_sortable_columns to sort my columns in Rails 3 and was wondering if there is a way to sort by a column in another table other than using a join.
I have a column that is a lookup of the name of a column in another table.
That is, I have cities.country_id as a column and in the View template I do:
#cities.each do |city|
city.country.name
to display the name.
I would like to be able to sort by this column, but the countries.name, not the cities.country_id.
Does anyone know if this possible with the gem or any other simple way? If so how?
The only thing I can think of is to do an IF statement in the controller and catch the sorting by country and then run a different query using a join for that, but that's ugly and error-prone, was hoping for something more elegant.
Thanks.
Can you override to the to_s on the country model to return the name, then sort by that in the controller?
In your country model override to_s by:
def to_s
country.name
end
Then in your controller
#sorted_cities = #cities.sort_by { |obj| obj.country.to_s }
in addition to Webjedi answer..
#sorted_cities = #cities.includes(:country).sort_by { |obj| obj.country.to_s } #for eager loading
In the end I decided to use a join to get the right data to begin with. While Webjedi's answer is quite intriguing I didn't want to override any built-in functions, but it's good to know I can do that.
The code looks like this:
#countries = Country.select('countries.*, continents.name AS continent_name').joins{continent}.search(params[:term]).order(order).page(params[:page])

Resources