Rails OR query with postgres array - ruby-on-rails

I have a tagging system in rails using postgres' array data type. I'm trying to write a scope that will return any posts that include a tag. So far I have this scope working:
scope :with_tag, ->(tag) { where("tags #> ARRAY[?]", tag) }
I want to extend this scope so that I can query on multiple tags at the same time, ideally something like:
Post.with_tags(['some', 'tags', 'to', 'query'])
Which would return any Post that have one of those tags. I've thought about making a class method to handle iterating over the input array:
def self.with_tags(args)
# start with empty activerecord relation
# want to output AR relation
results = Post.none
args.each do |tag|
results = results.concat(Post.with_tag(tag))
end
results.flatten
end
but this approach smells funny to me because it's creating a new query for each argument. It also doesn't return an ActiveRecord::Relation because of flatten, which I would really like to have as the output.
Can I accomplish what I'm after in a scope with an OR query?

I'm not running the code but I think the && operator does what you want:
scope :with_tags, ->(tags) { where("tags && ARRAY[?]", tags) }

Related

Custom ActiveAdmin Filter for Date Range - Complex Logic in Method

I am trying to create a custom ActiveAdmin filter that takes date_range as a parameter. Every solution I've found has been for excessively simple model methods.
Is there a way to pass both parameters into the model ransacker method, and/or at the very least to control the order in which these parameters are passed as well as to know which one is being passed? (end_date vs. start_date -- start_date is passed first, whereas I might be able to work around this is end_date were sent first). Any alternative solution, which would not break all other filters in the application (ie, overwriting activeadmin filters to use scopes - this is one filter out of hundreds in the application) welcome as well.
Thank you!
admin/model.rb
filter :model_method_in, as: :date_range
models/model.rb
ransacker :model_method, :formatter => proc { |start_date, end_date|
Model.complicated_method(start_date, end_date)
} do |parent|
parent.table[:id]
end
...
def method_for_base_queries(end_date)
Model.long_complicated_sql_call_using_end_date
end
def complicated_method(start_date, end_date)
model_instances = method_for_base_queries(end_date)
model_instances.logic_too_complex_for_sql_using_start_date
end
Similar question, but filter/model logic was simple enough for an alternative solution that didn't require both parameters to be passed in: Custom ActiveAdmin filter for Date Range
This might help. Given your index filter
filter :model_method, as: :date_range
you can write the following in your model:
scope :model_method_gteq_datetime, -> (start_date) {
self.where('users.your_date_column >= ?', start_date)
}
scope :model_method_lteq_datetime, -> (end_date) {
# added one day since apparently the '=' is not being counted in the query,
# otherwise it will return 0 results for a query on the same day (as "greater than")
self.where('users.your_date_column <= ?', (Time.parse(end_date) + 1.day).to_date.to_s)
}
def self.ransackable_scopes(auth_object = nil)
[model_method_gteq_datetime, :model_method_lteq_datetime]
end
..._gteq_datetime and ..._lteq_datetime is how Activeadmin interprets the two dates in a custom date_range index filter (see also the corresponding url generated after adding the filter).
I've written a sample query that fits my case (given that users is a model related to the current one), since I don't know the complexity of yours.
I'm using:
Ruby 2.3.1
Rails 5.0.7
Activeadmin 1.3.0

How to Dynamically Build an Active Record Query with Rails Scopes

I am looking for a better way to dynamically build a active record query without making a sql string.
The following method does a new search for every word in the search_str and returns the records that are returned by all the search scopes.
scope :multi_search, ->(search_str){
query = ''
if search_str.present?
search_str.split(' ').each do |x|
query += ".search('#{x}')"
end
eval(query[1..-1])
else
all
end
}
It works, but this is not a clean implementation with the use of eval. Is there a better way of doing this?
In a model method:
def self.multi_search(your_params)
scope = Model.scoped({})
your_params.split(' ').map{|v| scope = scope.search(v)}
scope
end

Rails comma separated search with scopes

I would like my search to allow for multiple inputs. I have a scope in my model:
scope :by_description, lambda { |description| where('description LIKE ?', "%#{description}%") unless description.nil? }
Currently if I search for "abc, efg", it will look for that exact string. How can I modify my scope to allow "abc, efg" to search for any records that have either "abc" OR "efg" in the description field?
Something like this (based on other answers):
scope :by_description, ->(desc=nil) {
if desc.blank?
all
else
terms = desc.split(/\s*,\s*/).map { |t| t.strip }.map { |t| "%#{t}%" }
where( ( ["#{table_name}.description like ?"] * terms.count).join(' or '), *terms )
end
}
Try this:
scope :by_description, lambda { |description|
description.split(",").
map(&:strip).
inject(self) { |memo, term|
memo.where("description like ?", term)
}
unless description.nil?
}
What this does:
takes the incoming string and splits it into an array of comma-separated strings
calls strip on each string to remove leading and trailing spaces
uses inject to chain together a collection of where clauses, one for each search term in the array
I haven't actually tried so it may require some adjustment. But the approach is sound. The key bit is that you need multiple WHERE clauses to get the "and" behavior you're looking for.
This is based off Todd's answer:
scope :by_description, ->(desc=nil) {
if desc.present?
desc.split(",").map(&:strip).inject(self) do |memo, term|
memo.where("#{table_name}.description LIKE ?", "%#{term}%")
end
else
all # or none, if you want no results returned
end
}
Logic wise essentially the same, but I used the stabby syntax, and ensured that the scope would always return something chainable. I also like to be safe and add the table_name to manually built queries, to prevent ambiguous table errors (when 2 or more tables have a description field).

Scoped and scope in rails

Can somebody explain what this method does and what I can pass to it?
scoped(options = nil)
Returns an anonymous scope.
And also what the scope method does? I don't understand after reading the documentation.
In ActiveRecord, all query building methods (like where, order, joins, limit and so forth) return a so called scope. Only when you call a kicker method like all or first the built-up query is executed and the results from the database are returned.
The scoped class method also returns a scope. The scope returned is by default empty meaning the result set would not be restricted in any way meaning all records would be returned if the query was executed.
You can use it to provide an "empty" alternative like in the query_by_date example by MurifoX.
Or you can use it to combine multiple conditions into one method call, like for example:
Model.scoped(:conditions => 'id < 100', :limit => 10, :order => 'title ASC')
# which would be equivalent to
Model.where('id < 100').limit(10).order('title ASC')
The scope class method allows you to define a class method that also returns a scope, like for example:
class Model
scope :colored, lambda {|col|
where(:color => col)
}
end
which can be used like this:
Model.colored
The nice thing with scopes is that you can combine them (almost) as you wish, so the following is absolutely possible:
Model.red.where('id < 100').order('title ASC').scoped(:limit => 10)
I also strongly suggest reading through http://guides.rubyonrails.org/active_record_querying.html
I have used it in the past.When you make chained calls to the ActiveRecord query interface like this:
Model.where(:conditions).where(:more_conditions).where(:final_conditions)
Each one of them is already scoped, making the chain work without any problems. But let's say you have something like this:
Model.query_by_date(date).query_by_user(user).query_by_status(status)
scope :query_by_date, lambda { |date|
case date
when "today"
where(:date => Date.today)
when "tomorrow"
where(:date => Date.tomorrow)
else
# Any value like '' or 0 or Date.whatever
end
}
This would cause an error if the date param is not today or tomorrow. It would pick the last value and try to chain this query with the next one query_by_user, resulting in a undefined method default_scoped? for ''. But if you put a scoped method in the else condition, it would work without any flaws, because you are saying to activerecord that you pass through this method/named scope and didn't make any calls to where/find/other activerecord methods, but returned a scoped object, so you can continue chaining queries and stuff.
It would be this way in the end.
else
scoped
end
Hope you understand this simple example.

Rails: Applying function to every record in table?

I have a table in Rails and I would like to find all records of a table where a certain function returns true.
What is the best way to do this? I could of course iterate over all the records in the table and use conditional statements to test whether the function, given the individual record, returns true and add it to a list.
Is there any easier way of doing this something along the lines of Model.find(:all, :conditions => {...}) maybe?
Thanks
Class MyModel < ActiveRecord
def self.targetted
find_each.select(&:predicate_method?)
end
def predicate_method?
#something that returns either true or false
end
end
this is a bit more Rails idiomatic :
find_each will fetch your record by batches of 1000. it is better than all for your memory
&:predicate_method : transforming a symbol into a Proc (with the # operator) will actually make your code call the method on each of the passed objects
def record_that_returns_true_for_xfunction
Model.all.select {|record| xfunction(record.some_column) == true}
end
This is seems like what you are looking for. This method will return an array of all the records where xfunction(record.some_column) == true.

Resources