There is a Project model and Payment model:
class Project < ApplicationRecord
has_many :payments
end
class Payment < ApplicationRecord
belongs_to :project
end
It is quite easy for me to combine queries for colums with either or
OR and
.
Project.where(projects[:created_at].gt(6.months.ago).or(projects[:updated_at].gt(1.month.ago)))
The reason for using Arel is that the exact columns, operators and values comes from the user and Arel operators match very nicely with what the user can select.
However, if I want to combine this with data from another associated table, then I just cannot figure out what to do. ActiverRecord requires OR
to be structurally compatible, so I cannot do:
Project.where(projects[:created_at].gt(6.months.ago)).or(Project.joins(:payments))
Something like this:
Project.where(projects[:created_at].gt(6.months.ago).or(projects.join(payments).on(projects[:id].eq(payments[:project_id]))))
raises exception: ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: subquery must return only one column.
One way I was able to do it was something like:
Project.where(projects[:created_at].gt(6.months.ago)).or(Project.where(id: Project.joins(:payments).select(:id)))
which generated SQL
SELECT "projects".* FROM "projects" WHERE (("projects"."created_at" > '2019-08-19 09:11:22.064298') OR "projects"."id" IN (SELECT "projects"."id" FROM "projects" INNER JOIN "payments" ON "payments"."project_id" = "projects"."id"))
Is this the best way to do it or is there a better way? Please there might be 10+ such clauses combined, involving multiple tables, using include
on all the possible tables to make all individual clauses compatible is likely to be too expensive.
Is there any reason why you can't keep your joins consistent across conditions? For example in Rails 5+ you can write:
Project.where(some_field: '12').joins(:payments)
.or(
Project.where(payments: {id: '1'}).joins(:payments)
)
Which would generate SQL like:
SELECT projects.* FROM projects
INNER JOIN payments ON (payments.project_id = project.id)
WHERE projects.some_field = 12 OR payments.id = 1
Note:
Using your structurally incompatible example in the OP, you can simply add a join to the first condition to make it compatible:
Project.where(projects[:created_at].gt(6.months.ago)).joins(:payments).or(Project.joins(:payments))