Skip to content

Commit d31071a

Browse files
committed
Add ActiveRecord::Relation#structurally_compatible?.
1 parent c3664b0 commit d31071a

File tree

4 files changed

+62
-1
lines changed

4 files changed

+62
-1
lines changed

activerecord/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
* Add `ActiveRecord::Relation#structurally_compatible?`.
2+
3+
Adds a query method by which a user can tell if the relation that they're
4+
about to use for `#or` or `#and` is structurally compatible with the
5+
receiver.
6+
7+
*Kevin Newton*
8+
19
* Fix `eager_loading?` when ordering with `Symbol`
210

311
`eager_loading?` is triggered correctly when using `order` with symbols.

activerecord/lib/active_record/relation/query_methods.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,21 @@ def invert_where! # :nodoc:
731731
self
732732
end
733733

734+
# Checks whether the given relation is structurally compatible with this relation, to determine
735+
# if it's possible to use the #and and #or methods without raising an error. Structurally
736+
# compatible is defined as: they must be scoping the same model, and they must differ only by
737+
# #where (if no #group has been defined) or #having (if a #group is present).
738+
#
739+
# Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
740+
# # => true
741+
#
742+
# Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
743+
# # => false
744+
#
745+
def structurally_compatible?(other)
746+
structurally_incompatible_values_for(other).empty?
747+
end
748+
734749
# Returns a new relation, which is the logical intersection of this relation and the one passed
735750
# as an argument.
736751
#

activerecord/test/cases/relation/delegation_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class QueryingMethodsDelegationTest < ActiveRecord::TestCase
5050
ActiveRecord::FinderMethods.public_instance_methods(false) - [:include?, :member?, :raise_record_not_found_exception!] +
5151
ActiveRecord::SpawnMethods.public_instance_methods(false) - [:spawn, :merge!] +
5252
ActiveRecord::QueryMethods.public_instance_methods(false).reject { |method|
53-
method.end_with?("=", "!", "value", "values", "clause")
53+
method.end_with?("=", "!", "?", "value", "values", "clause")
5454
} - [:reverse_order, :arel, :extensions, :construct_join_dependency] + [
5555
:any?, :many?, :none?, :one?,
5656
:first_or_create, :first_or_create!, :first_or_initialize,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper"
4+
require "models/post"
5+
6+
module ActiveRecord
7+
class StructuralCompatibilityTest < ActiveRecord::TestCase
8+
fixtures :posts
9+
10+
def test_compatible_values
11+
left = Post.where(id: 1)
12+
right = Post.where(id: 2)
13+
14+
assert left.structurally_compatible?(right)
15+
end
16+
17+
def test_incompatible_single_value_relations
18+
left = Post.distinct.where("id = 1")
19+
right = Post.where(id: [2, 3])
20+
21+
assert_not left.structurally_compatible?(right)
22+
end
23+
24+
def test_incompatible_multi_value_relations
25+
left = Post.order("body asc").where("id = 1")
26+
right = Post.order("id desc").where(id: [2, 3])
27+
28+
assert_not left.structurally_compatible?(right)
29+
end
30+
31+
def test_incompatible_unscope
32+
left = Post.order("body asc").where("id = 1").unscope(:order)
33+
right = Post.order("body asc").where("id = 2")
34+
35+
assert_not left.structurally_compatible?(right)
36+
end
37+
end
38+
end

0 commit comments

Comments
 (0)