145 lines
4.1 KiB
Ruby
145 lines
4.1 KiB
Ruby
# typed: true
|
|
# frozen_string_literal: true
|
|
|
|
require "active_record/associations/association_scope"
|
|
|
|
class ActiveRecord::Associations::AssociationScope
|
|
def get_chain(reflection, association, tracker)
|
|
name = reflection.name
|
|
chain =
|
|
T.let(
|
|
[
|
|
ActiveRecord::Reflection::RuntimeReflection.new(
|
|
reflection,
|
|
association
|
|
)
|
|
],
|
|
T.untyped
|
|
)
|
|
|
|
reflection
|
|
.chain
|
|
.drop(1)
|
|
.each do |refl|
|
|
refl_klass = T.cast(refl.klass, T.class_of(ActiveRecord::Base))
|
|
if refl_klass.is_a?(HasAuxTable::ClassMethods) &&
|
|
(aux_config = refl_klass.aux_table_for(refl.foreign_key))
|
|
aliased_table = aux_config.aux.klass.arel_table
|
|
chain << ReflectionProxy.new(refl, aliased_table)
|
|
else
|
|
aliased_table =
|
|
tracker.aliased_table_for(refl.klass.arel_table) do
|
|
refl.alias_candidate(name)
|
|
end
|
|
chain << ReflectionProxy.new(refl, aliased_table)
|
|
end
|
|
end
|
|
chain
|
|
end
|
|
|
|
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?
|
|
chain.each do |refl|
|
|
klass = refl.klass
|
|
next unless klass.is_a?(HasAuxTable::ClassMethods)
|
|
next unless aux_config = klass.aux_table_for(refl.join_primary_key)
|
|
aux_table = aux_config.aux.klass.table_name
|
|
main_table = aux_config.main.klass.table_name
|
|
main_keys =
|
|
aux_config.main.primary_keys.map { |key| "'#{main_table}'.'#{key}'" }
|
|
aux_keys =
|
|
aux_config.aux.primary_keys.map { |key| "'#{aux_table}'.'#{key}'" }
|
|
join_clause =
|
|
main_keys
|
|
.zip(aux_keys)
|
|
.map { |(main_key, aux_key)| "#{main_key} = #{aux_key}" }
|
|
.join(" AND ")
|
|
scope.joins!("INNER JOIN '#{main_table}' ON (#{join_clause})")
|
|
end if association.is_a?(
|
|
ActiveRecord::Associations::HasManyThroughAssociation
|
|
)
|
|
scope
|
|
end
|
|
end
|
|
|
|
module HasAuxTable
|
|
module RelationExtensions
|
|
extend T::Sig
|
|
Util = HasAuxTable::Util
|
|
|
|
sig { params(aux_config: AuxTableConfig).void }
|
|
def setup_relation_extensions!(aux_config)
|
|
main_class = aux_config.main.klass
|
|
|
|
Util.hook_method(main_class, :where, false) do |original, *args|
|
|
if args.length == 1 && args.first.is_a?(Hash)
|
|
opts_remapped = aux_config.remap_conditions(args.first)
|
|
original.call(opts_remapped)
|
|
else
|
|
original.call(*args)
|
|
end
|
|
end
|
|
|
|
Util.hook_method(
|
|
main_class,
|
|
:all,
|
|
false
|
|
) do |original, *args, **kwargs, &block|
|
|
original.call(*args, **kwargs, &block).eager_load(
|
|
aux_config.aux_association_name
|
|
)
|
|
end
|
|
|
|
Util.hook_method(
|
|
main_class,
|
|
:unscoped,
|
|
false
|
|
) do |original, *args, **kwargs, &block|
|
|
ret = original.call(*args, **kwargs, &block)
|
|
if ret.is_a?(ActiveRecord::Relation)
|
|
ret.eager_load!(aux_config.aux_association_name)
|
|
end
|
|
ret
|
|
end
|
|
|
|
relation_class =
|
|
main_class.relation_delegate_class(ActiveRecord::Relation)
|
|
|
|
collection_proxy_class =
|
|
main_class.relation_delegate_class(
|
|
ActiveRecord::Associations::CollectionProxy
|
|
)
|
|
|
|
[
|
|
[relation_class, :build_where_clause],
|
|
[collection_proxy_class, :where]
|
|
].each do |klass, method_name|
|
|
Util.hook_method(klass, method_name, true) do |original, opts, *rest|
|
|
if opts.is_a?(Hash)
|
|
opts_remapped = aux_config.remap_conditions(opts)
|
|
T.unsafe(original).call(opts_remapped, *rest)
|
|
else
|
|
T.unsafe(original).call(opts, *rest)
|
|
end
|
|
end
|
|
end
|
|
|
|
Util.hook_method(
|
|
relation_class,
|
|
:bind_attribute,
|
|
true
|
|
) do |original, name, value, &block|
|
|
aux_config.aux_bind_attribute(name, value, &block)
|
|
end
|
|
end
|
|
end
|
|
end
|