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
|
# frozen_string_literal: true
module ActiveRecord
module Associations
class AssociationScope # :nodoc:
def self.scope(association)
INSTANCE.scope(association)
end
def self.create(&block)
block ||= lambda { |val| val }
new(block)
end
def initialize(value_transformation)
@value_transformation = value_transformation
end
INSTANCE = create
def scope(association)
klass = association.klass
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
chain = get_chain(reflection, association, scope.alias_tracker)
scope.extending! reflection.extensions
scope = add_constraints(scope, owner, chain)
scope.limit!(1) unless reflection.collection?
scope
end
def self.get_bind_values(owner, chain)
binds = []
last_reflection = chain.last
binds.push(*last_reflection.join_id_for(owner))
if last_reflection.type
binds << owner.class.polymorphic_name
end
chain.each_cons(2).each do |reflection, next_reflection|
if reflection.type
binds << next_reflection.klass.polymorphic_name
end
end
binds
end
private
attr_reader :value_transformation
def join(table, constraint)
Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint))
end
def last_chain_scope(scope, reflection, owner)
primary_key = Array(reflection.join_primary_key)
foreign_key = Array(reflection.join_foreign_key)
table = reflection.aliased_table
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
primary_key_foreign_key_pairs.each do |join_key, foreign_key|
value = transform_value(owner._read_attribute(foreign_key))
scope = apply_scope(scope, table, join_key, value)
end
if reflection.type
polymorphic_type = transform_value(owner.class.polymorphic_name)
scope = apply_scope(scope, table, reflection.type, polymorphic_type)
end
scope
end
def transform_value(value)
value_transformation.call(value)
end
def next_chain_scope(scope, reflection, next_reflection)
primary_key = Array(reflection.join_primary_key)
foreign_key = Array(reflection.join_foreign_key)
table = reflection.aliased_table
foreign_table = next_reflection.aliased_table
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
constraints = primary_key_foreign_key_pairs.map do |join_primary_key, foreign_key|
table[join_primary_key].eq(foreign_table[foreign_key])
end.inject(&:and)
if reflection.type
value = transform_value(next_reflection.klass.polymorphic_name)
scope = apply_scope(scope, table, reflection.type, value)
end
scope.joins!(join(foreign_table, constraints))
end
class ReflectionProxy < SimpleDelegator # :nodoc:
attr_reader :aliased_table
def initialize(reflection, aliased_table)
super(reflection)
@aliased_table = aliased_table
end
def all_includes; nil; end
end
def get_chain(reflection, association, tracker)
name = reflection.name
chain = [Reflection::RuntimeReflection.new(reflection, association)]
reflection.chain.drop(1).each do |refl|
aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do
refl.alias_candidate(name)
end
chain << ReflectionProxy.new(refl, aliased_table)
end
chain
end
def add_constraints(scope, owner, chain)
scope = last_chain_scope(scope, chain.last, owner)
chain.each_cons(2) do |reflection, next_reflection|
scope = next_chain_scope(scope, reflection, next_reflection)
end
chain_head = chain.first
chain.reverse_each do |reflection|
reflection.constraints.each do |scope_chain_item|
item = eval_scope(reflection, scope_chain_item, owner)
if scope_chain_item == chain_head.scope
scope.merge! item.except(:where, :includes, :unscope, :order)
elsif !item.references_values.empty?
scope.merge! item.only(:joins, :left_outer_joins)
associations = item.eager_load_values | item.includes_values
unless associations.empty?
scope.joins! item.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
end
end
reflection.all_includes do
scope.includes_values |= item.includes_values
end
scope.unscope!(*item.unscope_values)
scope.where_clause += item.where_clause
scope.order_values = item.order_values | scope.order_values
end
end
scope
end
def apply_scope(scope, table, key, value)
if scope.table == table
scope.where!(key => value)
else
scope.where!(table.name => { key => value })
end
end
def eval_scope(reflection, scope, owner)
relation = reflection.build_scope(reflection.aliased_table)
relation.instance_exec(owner, &scope) || relation
end
end
end
end
|