Active Record OR with Merge
Hey Rails devs. I learned something new today I wanted to share. So doing OR
queries in active record can be complex. For 1, you have to reference a whole query. For example, you can’t do this:
Job.where(job_type: :per_ton).or(status: :status1)
You’ll get the error: ArgumentError: You have passed Hash object to #or. Pass an ActiveRecord::Relation object instead. So you need to do something like this instead:
Job.where(job_type: :per_ton).or(Job.where(status: :status1))
That’s not too bad, but if you need to do joins:
Job.where(job_type: :per_ton)
.or(Job.joins(:company).where(companies: {name: 'Brown'}))
You’ll get the error: ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:joins]
But to fix that you can do this, which is cool and easier to read:
Job.joins(:company)
.merge(
Job.where(job_type: :per_ton).or(Company.where(name: 'Brown'))
)
It’ll produce this sql:
SELECT "jobs".*
FROM "jobs"
INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id"
WHERE ("jobs"."job_type" = 1 OR "companies"."name" = 'Brown')
This was the actual query in the code base:
Vendor
.joins(distributions: [:job_product, :sub_account])
.where("job_products.job_id" => job_ids)
.merge(
Account.where(category: [:logging_company, :trucking_company])
.or(Job::Product::Distribution.where(service_type: [:logging, :trucking]))
).distinct
Also merge is pretty powerful because you can use scopes on the model/table you are querying.
Company.joins(:jobs).merge(Job.active)
versus:
Company.joins(:jobs => :job_status).where(jobs: {job_statuses: {active: true}})