mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #39250 from kamipo/support_rewhere_for_merge
Support merging option `:rewhere` to allow mergee side condition to be replaced exactly
This commit is contained in:
commit
972f22e42a
6 changed files with 120 additions and 28 deletions
|
@ -1,3 +1,20 @@
|
||||||
|
* Support merging option `:rewhere` to allow mergee side condition to be replaced exactly.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
david_and_mary = Author.where(id: david.id..mary.id)
|
||||||
|
|
||||||
|
# both conflict conditions exists
|
||||||
|
david_and_mary.merge(Author.where(id: bob)) # => []
|
||||||
|
|
||||||
|
# mergee side condition is replaced by rewhere
|
||||||
|
david_and_mary.merge(Author.rewhere(id: bob)) # => [bob]
|
||||||
|
|
||||||
|
# mergee side condition is replaced by rewhere option
|
||||||
|
david_and_mary.merge(Author.where(id: bob), rewhere: true) # => [bob]
|
||||||
|
```
|
||||||
|
|
||||||
|
*Ryuta Kamizono*
|
||||||
|
|
||||||
* Add support for finding records based on signed ids, which are tamper-proof, verified ids that can be
|
* Add support for finding records based on signed ids, which are tamper-proof, verified ids that can be
|
||||||
set to expire and scoped with a purpose. This is particularly useful for things like password reset
|
set to expire and scoped with a purpose. This is particularly useful for things like password reset
|
||||||
or email verification, where you want the bearer of the signed id to be able to interact with the
|
or email verification, where you want the bearer of the signed id to be able to interact with the
|
||||||
|
|
|
@ -7,15 +7,16 @@ module ActiveRecord
|
||||||
class HashMerger # :nodoc:
|
class HashMerger # :nodoc:
|
||||||
attr_reader :relation, :hash
|
attr_reader :relation, :hash
|
||||||
|
|
||||||
def initialize(relation, hash)
|
def initialize(relation, hash, rewhere = nil)
|
||||||
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
||||||
|
|
||||||
@relation = relation
|
@relation = relation
|
||||||
@hash = hash
|
@hash = hash
|
||||||
|
@rewhere = rewhere
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge #:nodoc:
|
def merge
|
||||||
Merger.new(relation, other).merge
|
Merger.new(relation, other, @rewhere).merge
|
||||||
end
|
end
|
||||||
|
|
||||||
# Applying values to a relation has some side effects. E.g.
|
# Applying values to a relation has some side effects. E.g.
|
||||||
|
@ -48,10 +49,11 @@ module ActiveRecord
|
||||||
class Merger # :nodoc:
|
class Merger # :nodoc:
|
||||||
attr_reader :relation, :values, :other
|
attr_reader :relation, :values, :other
|
||||||
|
|
||||||
def initialize(relation, other)
|
def initialize(relation, other, rewhere = nil)
|
||||||
@relation = relation
|
@relation = relation
|
||||||
@values = other.values
|
@values = other.values
|
||||||
@other = other
|
@other = other
|
||||||
|
@rewhere = rewhere
|
||||||
end
|
end
|
||||||
|
|
||||||
NORMAL_VALUES = Relation::VALUE_METHODS -
|
NORMAL_VALUES = Relation::VALUE_METHODS -
|
||||||
|
@ -172,7 +174,7 @@ module ActiveRecord
|
||||||
def merge_clauses
|
def merge_clauses
|
||||||
relation.from_clause = other.from_clause if replace_from_clause?
|
relation.from_clause = other.from_clause if replace_from_clause?
|
||||||
|
|
||||||
where_clause = relation.where_clause.merge(other.where_clause)
|
where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
|
||||||
relation.where_clause = where_clause unless where_clause.empty?
|
relation.where_clause = where_clause unless where_clause.empty?
|
||||||
|
|
||||||
having_clause = relation.having_clause.merge(other.having_clause)
|
having_clause = relation.having_clause.merge(other.having_clause)
|
||||||
|
|
|
@ -702,15 +702,10 @@ module ActiveRecord
|
||||||
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
||||||
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
||||||
def rewhere(conditions)
|
def rewhere(conditions)
|
||||||
attrs = []
|
|
||||||
scope = spawn
|
scope = spawn
|
||||||
|
|
||||||
where_clause = scope.build_where_clause(conditions)
|
where_clause = scope.build_where_clause(conditions)
|
||||||
where_clause.each_attribute do |attr|
|
|
||||||
attrs << attr
|
|
||||||
end
|
|
||||||
|
|
||||||
scope.unscope!(where: attrs)
|
scope.unscope!(where: where_clause.extract_attributes)
|
||||||
scope.where_clause += where_clause
|
scope.where_clause += where_clause
|
||||||
scope
|
scope
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,21 +28,22 @@ module ActiveRecord
|
||||||
# # => Post.where(published: true).joins(:comments)
|
# # => Post.where(published: true).joins(:comments)
|
||||||
#
|
#
|
||||||
# This is mainly intended for sharing common conditions between multiple associations.
|
# This is mainly intended for sharing common conditions between multiple associations.
|
||||||
def merge(other)
|
def merge(other, *rest)
|
||||||
if other.is_a?(Array)
|
if other.is_a?(Array)
|
||||||
records & other
|
records & other
|
||||||
elsif other
|
elsif other
|
||||||
spawn.merge!(other)
|
spawn.merge!(other, *rest)
|
||||||
else
|
else
|
||||||
raise ArgumentError, "invalid argument: #{other.inspect}."
|
raise ArgumentError, "invalid argument: #{other.inspect}."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge!(other) # :nodoc:
|
def merge!(other, *rest) # :nodoc:
|
||||||
|
options = rest.extract_options!
|
||||||
if other.is_a?(Hash)
|
if other.is_a?(Hash)
|
||||||
Relation::HashMerger.new(self, other).merge
|
Relation::HashMerger.new(self, other, options[:rewhere]).merge
|
||||||
elsif other.is_a?(Relation)
|
elsif other.is_a?(Relation)
|
||||||
Relation::Merger.new(self, other).merge
|
Relation::Merger.new(self, other, options[:rewhere]).merge
|
||||||
elsif other.respond_to?(:to_proc)
|
elsif other.respond_to?(:to_proc)
|
||||||
instance_exec(&other)
|
instance_exec(&other)
|
||||||
else
|
else
|
||||||
|
|
|
@ -10,21 +10,21 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def +(other)
|
def +(other)
|
||||||
WhereClause.new(
|
WhereClause.new(predicates + other.predicates)
|
||||||
predicates + other.predicates,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def -(other)
|
def -(other)
|
||||||
WhereClause.new(
|
WhereClause.new(predicates - other.predicates)
|
||||||
predicates - other.predicates,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge(other)
|
def merge(other, rewhere = nil)
|
||||||
WhereClause.new(
|
predicates = if rewhere
|
||||||
predicates_unreferenced_by(other) + other.predicates,
|
except_predicates(other.extract_attributes)
|
||||||
)
|
else
|
||||||
|
predicates_unreferenced_by(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
WhereClause.new(predicates + other.predicates)
|
||||||
end
|
end
|
||||||
|
|
||||||
def except(*columns)
|
def except(*columns)
|
||||||
|
@ -98,10 +98,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def each_attribute(&block)
|
def extract_attributes
|
||||||
|
attrs = []
|
||||||
predicates.each do |node|
|
predicates.each do |node|
|
||||||
Arel.fetch_attribute(node, &block)
|
Arel.fetch_attribute(node) { |attr| attrs << attr }
|
||||||
end
|
end
|
||||||
|
attrs
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -13,6 +13,81 @@ require "models/rating"
|
||||||
class RelationMergingTest < ActiveRecord::TestCase
|
class RelationMergingTest < ActiveRecord::TestCase
|
||||||
fixtures :developers, :comments, :authors, :author_addresses, :posts
|
fixtures :developers, :comments, :authors, :author_addresses, :posts
|
||||||
|
|
||||||
|
def test_merge_in_clause
|
||||||
|
david, mary, bob = authors(:david, :mary, :bob)
|
||||||
|
|
||||||
|
david_and_mary = Author.where(id: [david, mary]).order(:id)
|
||||||
|
mary_and_bob = Author.where(id: [mary, bob]).order(:id)
|
||||||
|
|
||||||
|
assert_equal [david, mary], david_and_mary
|
||||||
|
assert_equal [mary, bob], mary_and_bob
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary))
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.where(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.rewhere(id: mary))
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.rewhere(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary), rewhere: true)
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.where(id: bob), rewhere: true)
|
||||||
|
|
||||||
|
assert_equal [mary, bob], david_and_mary.merge(mary_and_bob)
|
||||||
|
assert_equal [david, mary], mary_and_bob.merge(david_and_mary)
|
||||||
|
|
||||||
|
assert_equal [mary, bob], david_and_mary.merge(mary_and_bob, rewhere: true)
|
||||||
|
assert_equal [david, mary], mary_and_bob.merge(david_and_mary, rewhere: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_merge_between_clause
|
||||||
|
david, mary, bob = authors(:david, :mary, :bob)
|
||||||
|
|
||||||
|
david_and_mary = Author.where(id: david.id..mary.id).order(:id)
|
||||||
|
mary_and_bob = Author.where(id: mary.id..bob.id).order(:id)
|
||||||
|
|
||||||
|
assert_equal [david, mary], david_and_mary
|
||||||
|
assert_equal [mary, bob], mary_and_bob
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary))
|
||||||
|
assert_equal [], david_and_mary.merge(Author.where(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.rewhere(id: mary))
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.rewhere(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary), rewhere: true)
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.where(id: bob), rewhere: true)
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(mary_and_bob)
|
||||||
|
assert_equal [mary], mary_and_bob.merge(david_and_mary)
|
||||||
|
|
||||||
|
assert_equal [mary, bob], david_and_mary.merge(mary_and_bob, rewhere: true)
|
||||||
|
assert_equal [david, mary], mary_and_bob.merge(david_and_mary, rewhere: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_merge_or_clause
|
||||||
|
david, mary, bob = authors(:david, :mary, :bob)
|
||||||
|
|
||||||
|
david_and_mary = Author.where(id: david).or(Author.where(id: mary)).order(:id)
|
||||||
|
mary_and_bob = Author.where(id: mary).or(Author.where(id: bob)).order(:id)
|
||||||
|
|
||||||
|
assert_equal [david, mary], david_and_mary
|
||||||
|
assert_equal [mary, bob], mary_and_bob
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary))
|
||||||
|
assert_equal [], david_and_mary.merge(Author.where(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.rewhere(id: mary))
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.rewhere(id: bob))
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(Author.where(id: mary), rewhere: true)
|
||||||
|
assert_equal [bob], david_and_mary.merge(Author.where(id: bob), rewhere: true)
|
||||||
|
|
||||||
|
assert_equal [mary], david_and_mary.merge(mary_and_bob)
|
||||||
|
assert_equal [mary], mary_and_bob.merge(david_and_mary)
|
||||||
|
|
||||||
|
assert_equal [mary, bob], david_and_mary.merge(mary_and_bob, rewhere: true)
|
||||||
|
assert_equal [david, mary], mary_and_bob.merge(david_and_mary, rewhere: true)
|
||||||
|
end
|
||||||
|
|
||||||
def test_relation_merging
|
def test_relation_merging
|
||||||
devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3"))
|
devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3"))
|
||||||
assert_equal [developers(:david), developers(:jamis)], devs.to_a
|
assert_equal [developers(:david), developers(:jamis)], devs.to_a
|
||||||
|
|
Loading…
Reference in a new issue