Skip to content

Commit 21f9db1

Browse files
authored
Merge pull request rails#41841 from kddnewton/structurally-compatible
Add `ActiveRecord::Relation#structurally_compatible?`.
2 parents cd469fd + 845280a commit 21f9db1

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
* Add `ActiveRecord::QueryMethods#in_order_of`.
210

311
This allows you to specify an explicit order that you'd like records

activerecord/lib/active_record/relation/query_methods.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,21 @@ def invert_where! # :nodoc:
748748
self
749749
end
750750

751+
# Checks whether the given relation is structurally compatible with this relation, to determine
752+
# if it's possible to use the #and and #or methods without raising an error. Structurally
753+
# compatible is defined as: they must be scoping the same model, and they must differ only by
754+
# #where (if no #group has been defined) or #having (if a #group is present).
755+
#
756+
# Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
757+
# # => true
758+
#
759+
# Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
760+
# # => false
761+
#
762+
def structurally_compatible?(other)
763+
structurally_incompatible_values_for(other).empty?
764+
end
765+
751766
# Returns a new relation, which is the logical intersection of this relation and the one passed
752767
# as an argument.
753768
#

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)