March 06, 2025

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}})