1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
# frozen_string_literal: true
require "active_support/core_ext/hash/keys"
module ActiveRecord
class Relation
class HashMerger # :nodoc:
attr_reader :relation, :hash
def initialize(relation, hash)
hash.assert_valid_keys(*Relation::VALUE_METHODS)
@relation = relation
@hash = hash
end
def merge #:nodoc:
Merger.new(relation, other).merge
end
# Applying values to a relation has some side effects. E.g.
# interpolation might take place for where values. So we should
# build a relation to merge in rather than directly merging
# the values.
def other
other = Relation.create(
relation.klass,
table: relation.table,
predicate_builder: relation.predicate_builder
)
hash.each { |k, v|
if k == :joins
if Hash === v
other.joins!(v)
else
other.joins!(*v)
end
elsif k == :select
other._select!(v)
else
other.send("#{k}!", v)
end
}
other
end
end
class Merger # :nodoc:
attr_reader :relation, :values, :other
def initialize(relation, other)
@relation = relation
@values = other.values
@other = other
end
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
[:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
end
def merge
normal_values.each do |name|
value = values[name]
# The unless clause is here mostly for performance reasons (since the `send` call might be moderately
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
# don't fall through the cracks.
unless value.nil? || (value.blank? && false != value)
if name == :select
relation._select!(*value)
else
relation.send("#{name}!", *value)
end
end
end
merge_multi_values
merge_single_values
merge_clauses
merge_preloads
merge_joins
merge_outer_joins
relation
end
private
def merge_preloads
return if other.preload_values.empty? && other.includes_values.empty?
if other.klass == relation.klass
relation.preload!(*other.preload_values) unless other.preload_values.empty?
relation.includes!(other.includes_values) unless other.includes_values.empty?
else
reflection = relation.klass.reflect_on_all_associations.find do |r|
r.class_name == other.klass.name
end || return
unless other.preload_values.empty?
relation.preload! reflection.name => other.preload_values
end
unless other.includes_values.empty?
relation.includes! reflection.name => other.includes_values
end
end
end
def merge_joins
return if other.joins_values.blank?
if other.klass == relation.klass
relation.joins!(*other.joins_values)
else
associations, others = other.joins_values.partition do |join|
case join
when Hash, Symbol, Array; true
end
end
join_dependency = other.construct_join_dependency(
associations, Arel::Nodes::InnerJoin
)
relation.joins!(join_dependency, *others)
end
end
def merge_outer_joins
return if other.left_outer_joins_values.blank?
if other.klass == relation.klass
relation.left_outer_joins!(*other.left_outer_joins_values)
else
associations = other.left_outer_joins_values
join_dependency = other.construct_join_dependency(
associations, Arel::Nodes::OuterJoin
)
relation.joins!(join_dependency)
end
end
def merge_multi_values
if other.reordering_value
# override any order specified in the original relation
relation.reorder!(*other.order_values)
elsif other.order_values.any?
# merge in order_values from relation
relation.order!(*other.order_values)
end
extensions = other.extensions - relation.extensions
relation.extending!(*extensions) if extensions.any?
end
def merge_single_values
relation.lock_value ||= other.lock_value if other.lock_value
unless other.create_with_value.blank?
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
end
end
def merge_clauses
relation.from_clause = other.from_clause if replace_from_clause?
where_clause = relation.where_clause.merge(other.where_clause)
relation.where_clause = where_clause unless where_clause.empty?
having_clause = relation.having_clause.merge(other.having_clause)
relation.having_clause = having_clause unless having_clause.empty?
end
def replace_from_clause?
relation.from_clause.empty? && !other.from_clause.empty? &&
relation.klass.base_class == other.klass.base_class
end
end
end
end
|