How do I get the arel components in such a ways that I can do something like:
queries = []
queries << MyModel.some_scope.get_the_arel_component
queries << MyModel.some_scope_with_param("Dave").get_the_arel_component
queries << MyModel.where(:something => 'blah').get_the_arel_component
queries << MyModel.some_scope_with_join_and_merge.get_arel_component
# etc ... (may be any number of queries)
# join each query with OR
combined_query = nil
queries.each do |query|
combined_query ||= query
combined_query = combined_query.or(q)
end
# run the query so it just works
MyModel.where(combined_query)
I've encountered some issues with accepted answers of similar questions.
Lets say I have a class like so:
class Patient
has_one :account
scope :older_than, ->(date) { where(arel_table[:dob].lt(date)) }
scope :with_gender, ->(gender) { where(:gender => gender) }
scope :with_name_like, ->(name) { where("lower(name) LIKE ?", name.downcase) }
scope :in_arrears, -> { joins(:account).merge( Account.in_arrears ) }
end
The goal is to combine any scope or where clause with an OR.
One way would be Patient.with_name_like("Susan") | Patient.with_name_like("Dave")
. This seems to run each individual query separately instead of combine into a single query. I've ruled this solution out.
Another method that only works in some instances is:
# this fails because `where_values` for the `with_name_like` scope returns a string
sues = Patient.with_name_like("Susan").where_values.reduce(:and)
daves = Patient.with_name_like("Dave").where_values.reduce(:and)
Patient.where(sues.or(daves))
# this works as `where_values` returns an `Arel::Nodes::Equality` object
ages = Patient.older_than(7.years.ago).where_values.reduce(:and)
males = Patients.with_gender('M').where_values.reduce(:and)
Patient.where(ages.or(males))
# this fails as `in_arrears` scope requires a joins
of_age = Patient.older_than(18.years.ago).where_values.reduce(:and)
arrears = Patients.in_arrears.where_values.reduce(:and)
Patient.where(of_age.or(arrears)) # doesn't work as no join on accounts
Patient.join(:account).where(of_age.or(arrears)) # does work as we have our join
To sum up, the issues with ORing queries arise when where
is passed a string or the query requires a join.
I'm pretty sure where
converts anything passed to it into an arel object, it's just a matter of getting access to the correct pieces and recombining them in the correct way. I just haven't managed to work it out yet.
Preferably the answer will only make use of ActiveRecord and AREL and not a third party library.