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
|
# frozen_string_literal: true
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
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 ||= Module.new.tap do |mod|
mod_name = "GeneratedRelationMethods"
const_set mod_name, mod
private_constant mod_name
end
end
def generate_relation_method(method)
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
scoping { klass.#{method}(*args, &block) }
end
RUBY
else
generated_relation_methods.define_method(method) do |*args, &block|
scoping { klass.public_send(method, *args, &block) }
end
end
end
end
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
included do
@delegation_mutex = Mutex.new
end
module ClassMethods # :nodoc:
def name
superclass.name
end
def delegate_to_scoped_klass(method)
@delegation_mutex.synchronize do
return if method_defined?(method)
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
scoping { @klass.#{method}(*args, &block) }
end
RUBY
else
define_method method do |*args, &block|
scoping { @klass.public_send(method, *args, &block) }
end
end
end
end
end
private
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
self.class.delegate_to_scoped_klass(method)
scoping { @klass.public_send(method, *args, &block) }
else
super
end
end
end
module ClassMethods # :nodoc:
def create(klass, *args)
relation_class_for(klass).new(klass, *args)
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
|