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
|
# 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 << 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)
table.create_join(table, table.create_on(constraint))
end
def last_chain_scope(scope, reflection, owner)
join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
table = reflection.aliased_table
value = transform_value(owner[foreign_key])
scope = apply_scope(scope, table, key, value)
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)
join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
table = reflection.aliased_table
foreign_table = next_reflection.aliased_table
constraint = table[key].eq(foreign_table[foreign_key])
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, constraint))
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.table_name,
refl.alias_candidate(name),
refl.klass.type_caster
)
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|
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
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)
end
reflection.all_includes do
scope.includes! 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
|