Reputation: 2927
How can I get (A || B) && ( C || D) query in Rails 5 ActiveRecord?. I tried
Post.where(a).or(Post.where(b)).where(c).or(Post.where(d))
but It produces as: (A || B) && C || D. What would be the correct code to get desired query?
Upvotes: 3
Views: 826
Reputation: 16793
Have you tried pushing each of the or
queries down into individual named scopes in the Post
model? For me, splitting them out yielded different results. Try something like the code below and see if you get the expected results (I didn't have a Rails app handy so I put everything into a single Ruby file):
rails_where_or.rb
require 'active_record'
require 'rspec'
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3', database: ':memory:'
)
ActiveRecord::Schema.define do
create_table :posts do |t|
t.boolean :a, default: false
t.boolean :b, default: false
t.boolean :c, default: false
t.boolean :d, default: false
end
end
class Post < ActiveRecord::Base
def self.a_or_b_and_c_or_d
a_or_b.c_or_d
end
def self.a_or_b_and_c_or_d_all_chained
where(a: true).or(where(b: true)).
where(c: true).or(where(d: true))
end
def self.a_or_b
where(a: true).or(where(b: true))
end
def self.c_or_d
where(c: true).or(where(d: true))
end
end
RSpec.describe Post do
let!(:only_a) do
described_class.create!(a: true)
end
let!(:only_b) do
described_class.create!(b: true)
end
let!(:only_c) do
described_class.create!(c: true)
end
let!(:only_d) do
described_class.create!(d: true)
end
let!(:a_and_b) do
described_class.create!(a: true, b: true)
end
let!(:a_and_c) do
described_class.create!(a: true, c: true)
end
let!(:a_and_d) do
described_class.create!(a: true, d: true)
end
let!(:b_and_c) do
described_class.create!(b: true, c: true)
end
let!(:b_and_d) do
described_class.create!(b: true, d: true)
end
let!(:c_and_d) do
described_class.create!(c: true, d: true)
end
describe '.a_or_b_and_c_or_d' do
let(:a_or_b_and_c_or_d) do
described_class.a_or_b_and_c_or_d
end
it 'returns only records that match (A || B) && ( C || D )' do
expect(a_or_b_and_c_or_d).not_to include(only_a)
expect(a_or_b_and_c_or_d).not_to include(only_b)
expect(a_or_b_and_c_or_d).not_to include(only_c)
expect(a_or_b_and_c_or_d).not_to include(only_d)
expect(a_or_b_and_c_or_d).not_to include(a_and_b)
expect(a_or_b_and_c_or_d).not_to include(c_and_d)
expect(a_or_b_and_c_or_d).to include(a_and_c)
expect(a_or_b_and_c_or_d).to include(a_and_d)
expect(a_or_b_and_c_or_d).to include(b_and_c)
expect(a_or_b_and_c_or_d).to include(b_and_d)
end
end
describe '.a_or_b_and_c_or_d_all_chained' do
let(:a_or_b_and_c_or_d_all_chained) do
described_class.a_or_b_and_c_or_d_all_chained
end
it 'returns records that match (A || B) && C || D ' do
expect(a_or_b_and_c_or_d_all_chained).not_to include(only_a)
expect(a_or_b_and_c_or_d_all_chained).not_to include(only_b)
expect(a_or_b_and_c_or_d_all_chained).not_to include(only_c)
expect(a_or_b_and_c_or_d_all_chained).not_to include(a_and_b)
expect(a_or_b_and_c_or_d_all_chained).to include(only_d)
expect(a_or_b_and_c_or_d_all_chained).to include(c_and_d)
expect(a_or_b_and_c_or_d_all_chained).to include(a_and_c)
expect(a_or_b_and_c_or_d_all_chained).to include(a_and_d)
expect(a_or_b_and_c_or_d_all_chained).to include(b_and_c)
expect(a_or_b_and_c_or_d_all_chained).to include(b_and_d)
end
end
end
Upvotes: 0
Reputation: 8900
The Rails or()
method is not as complex as this. There is a long discussion about the feature when it was first proposed here, on Github
This method is meant to provide a convenient way of Railsifying queries, but sometimes, things just cannot be that simple. The or()
method essentially is stuck with reading left-to-right.
Your best bet while sticking to a "Railsy" convention would be to follow one of the following approaches:
Post.where("a OR b").where("c OR d") # Example 1 - SQL with additional where()
Post.where(a: [true, !b]).where(c: [true, !d]) # Example 2 - Array comparison
Post.where("(a OR b) AND (c OR d)") # Example 3 - SQL only
Upvotes: 1