diff options
Diffstat (limited to 'activerecord/lib')
-rw-r--r-- | activerecord/lib/active_record/null_relation.rb | 8 | ||||
-rw-r--r-- | activerecord/lib/active_record/querying.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 59 |
3 files changed, 68 insertions, 1 deletions
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index b406da14dc..802adca908 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -75,5 +75,13 @@ module ActiveRecord def exists?(_id = false) false end + + def or(other) + if other.is_a?(NullRelation) + super + else + other.or(self) + end + end end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 91c9a0db99..4e597590e9 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -7,7 +7,7 @@ module ActiveRecord delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0078b0f32e..78ee8b4580 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -582,6 +582,65 @@ module ActiveRecord unscope(where: conditions.keys).where(conditions) end + # Returns a new relation, which is the logical union of this relation and the one passed as an + # argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by +where+ (if no +group+ has been defined) or +having+ (if a +group+ is + # present). Neither relation may have a +limit+, +offset+, or +uniq+ set. + # + # Post.where("id = 1").or(Post.where("id = 2")) + # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) + # + def or(other) + spawn.or!(other) + end + + def or!(other) + combining = group_values.any? ? :having : :where + + unless structurally_compatible?(other, combining) + raise ArgumentError, 'Relation passed to #or must be structurally compatible' + end + + unless other.is_a?(NullRelation) + left_values = send("#{combining}_values") + right_values = other.send("#{combining}_values") + + common = left_values & right_values + mine = left_values - common + theirs = right_values - common + + if mine.any? && theirs.any? + mine = mine.map { |x| String === x ? Arel.sql(x) : x } + theirs = theirs.map { |x| String === x ? Arel.sql(x) : x } + + mine = [Arel::Nodes::And.new(mine)] if mine.size > 1 + theirs = [Arel::Nodes::And.new(theirs)] if theirs.size > 1 + + common << Arel::Nodes::Or.new(mine.first, theirs.first) + end + + send("#{combining}_values=", common) + end + + self + end + + def structurally_compatible?(other, allowed_to_vary) + Relation::SINGLE_VALUE_METHODS.all? do |name| + send("#{name}_value") == other.send("#{name}_value") + end && + (Relation::MULTI_VALUE_METHODS - [allowed_to_vary, :extending]).all? do |name| + send("#{name}_values") == other.send("#{name}_values") + end && + (extending_values - [NullRelation]) == (other.extending_values - [NullRelation]) && + !limit_value && + !offset_value && + !uniq_value + end + private :structurally_compatible? + # Allows to specify a HAVING clause. Note that you can't use HAVING # without also specifying a GROUP clause. # |