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
|
# frozen_string_literal: true
require "mutex_m"
module ActiveRecord
module Delegation # :nodoc:
module DelegateCache # :nodoc:
def relation_delegate_class(klass)
@relation_delegate_cache[klass]
end
def initialize_relation_delegate_cache
@relation_delegate_cache = cache = {}
[
ActiveRecord::Relation,
ActiveRecord::Associations::CollectionProxy,
ActiveRecord::AssociationRelation
].each do |klass|
delegate = Class.new(klass) {
include ClassSpecificRelation
}
include_relation_methods(delegate)
mangled_name = klass.name.gsub("::", "_")
const_set mangled_name, delegate
private_constant mangled_name
cache[klass] = delegate
end
end
def inherited(child_class)
child_class.initialize_relation_delegate_cache
super
end
def generate_relation_method(method)
generated_relation_methods.generate_method(method)
end
protected
def include_relation_methods(delegate)
superclass.include_relation_methods(delegate) unless base_class?
delegate.include generated_relation_methods
end
private
def generated_relation_methods
@generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
const_set(:GeneratedRelationMethods, mod)
private_constant :GeneratedRelationMethods
end
end
end
class GeneratedRelationMethods < Module # :nodoc:
include Mutex_m
def generate_method(method)
synchronize do
return if method_defined?(method)
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(#{definition})
scoping { klass.#{method}(#{definition}) }
end
RUBY
else
define_method(method) do |*args, &block|
scoping { klass.public_send(method, *args, &block) }
end
ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
end
end
end
end
private_constant :GeneratedRelationMethods
extend ActiveSupport::Concern
# This module creates compiled delegation methods dynamically at runtime, which makes
# subsequent calls to that method faster by avoiding method_missing. The delegations
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
delegate :to_xml, :encode_with, :length, :each, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
:to_sentence, :to_formatted_s, :as_json,
:shuffle, :split, :slice, :index, :rindex, to: :records
delegate :primary_key, :connection, to: :klass
module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
module ClassMethods # :nodoc:
def name
superclass.name
end
end
private
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
@klass.generate_relation_method(method)
scoping { @klass.public_send(method, *args, &block) }
else
super
end
end
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
end
module ClassMethods # :nodoc:
def create(klass, *args, **kwargs)
relation_class_for(klass).new(klass, *args, **kwargs)
end
private
def relation_class_for(klass)
klass.relation_delegate_class(self)
end
end
private
def respond_to_missing?(method, _)
super || @klass.respond_to?(method)
end
end
end
|