@@ -124,20 +124,12 @@ def assert_klass_has_instance_method(klass, instance_method)
124
124
raise NameError , msg
125
125
end
126
126
127
- if ActiveRecord . version >= Gem ::Version . new ( 7.0 ) # Rails 7.0 expected methods to patch
128
- %w[
129
- grouped_records
130
- ] . each { |method | assert_klass_has_instance_method ( ActiveRecord ::Associations ::Preloader ::Branch , method ) }
131
- elsif ActiveRecord . version >= Gem ::Version . new ( 6.1 ) # Rails 6.1 methods to patch
132
- %w[
133
- preloaders_for_reflection
134
- preloaders_for_hash
135
- preloaders_for_one
136
- grouped_records
137
- ] . each { |method | assert_klass_has_instance_method ( ActiveRecord ::Associations ::Preloader , method ) }
138
- end
127
+ # Expect these methods to exist. (Otherwise we are patching the wrong methods)
128
+ %w[
129
+ grouped_records
130
+ preloaders_for_reflection
131
+ ] . each { |method | assert_klass_has_instance_method ( ActiveRecord ::Associations ::Preloader ::Branch , method ) }
139
132
140
- # Expected methods to patch on any version
141
133
%w[
142
134
build_select
143
135
arel_column
@@ -152,102 +144,70 @@ class Base
152
144
module Associations
153
145
class Preloader
154
146
prepend ( Module . new {
155
- # preloader.rb active record 6.0
156
- # changed:
157
- # since grouped_records can return a hash/array, we need to handle those 2 new cases
158
- def preloaders_for_reflection ( reflection , records , scope , polymorphic_parent )
159
- case reflection
160
- when Array
161
- reflection . flat_map { |ref | preloaders_on ( ref , records , scope , polymorphic_parent ) }
162
- when Hash
163
- preloaders_on ( reflection , records , scope , polymorphic_parent )
164
- else
165
- super ( reflection , records , scope )
166
- end
167
- end
168
-
169
- # rubocop:disable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
170
- # preloader.rb active record 6.0
171
- # changed:
172
- # passing polymorphic around (and makes 5.2 more similar to 6.0)
173
- def preloaders_for_hash ( association , records , scope , polymorphic_parent )
174
- association . flat_map { |parent , child |
175
- grouped_records ( parent , records , polymorphic_parent ) . flat_map do |reflection , reflection_records |
176
- loaders = preloaders_for_reflection ( reflection , reflection_records , scope , polymorphic_parent )
177
- recs = loaders . flat_map ( &:preloaded_records ) . uniq
178
- child_polymorphic_parent = reflection && reflection . respond_to? ( :options ) && reflection . options [ :polymorphic ]
179
- loaders . concat Array . wrap ( child ) . flat_map { |assoc |
180
- preloaders_on assoc , recs , scope , child_polymorphic_parent
181
- }
182
- loaders
147
+ # preloader is called with virtual attributes - need to resolve
148
+ def call
149
+ # Possibly overkill since all records probably have the same class and associations
150
+ # use a cache so we only convert includes once per base class
151
+ assoc_cache = Hash . new { |h , klass | h [ klass ] = klass . replace_virtual_fields ( associations ) }
152
+
153
+ # convert the includes with virtual attributes to includes with proper associations
154
+ records_by_assoc = records . group_by { |rec | assoc_cache [ rec . class ] }
155
+ # if these are the same includes, then do the preloader work
156
+ return super if records_by_assoc . size == 1 && records_by_assoc . keys . first == associations
157
+
158
+ # for each of the associations, run a preloader
159
+ records_by_assoc . each do |klass_associations , klass_records |
160
+ next if klass_associations . blank?
161
+
162
+ Array [ klass_associations ] . each do |klass_association | # rubocop:disable Style/RedundantArrayConstructor
163
+ # this calls back into itself, but it will take the short circuit
164
+ Preloader . new ( :records => klass_records , :associations => klass_association , :scope => scope ) . call
183
165
end
184
- }
185
- end
186
-
187
- # preloader.rb active record 6.0
188
- # changed:
189
- # passing polymorphic_parent to preloaders_for_reflection
190
- def preloaders_for_one ( association , records , scope , polymorphic_parent )
191
- grouped_records ( association , records , polymorphic_parent )
192
- . flat_map do |reflection , reflection_records |
193
- preloaders_for_reflection ( reflection , reflection_records , scope , polymorphic_parent )
194
- end
195
- end
196
-
197
- # preloader.rb active record 6.0, 6.1
198
- def grouped_records ( orig_association , records , polymorphic_parent )
199
- h = { }
200
- records . each do |record |
201
- # The virtual_field lookup can return Symbol/Nil/Other (typically a Hash)
202
- # so the case statement and the cases for Nil/Other are new
203
-
204
- # each class can resolve virtual_{attributes,includes} differently
205
- association = record . class . replace_virtual_fields ( orig_association )
206
- # 1 line optimization for single element array:
207
- association = association . first if association . kind_of? ( Array ) && association . size == 1
208
-
209
- case association
210
- when Symbol , String
211
- reflection = record . class . _reflect_on_association ( association )
212
- next if polymorphic_parent && !reflection || !record . association ( association ) . klass
213
- when nil
214
- next
215
- else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
216
- reflection = association
217
- end
218
- ( h [ reflection ] ||= [ ] ) << record
219
166
end
220
- h
221
167
end
222
- # rubocop:enable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
223
168
} )
169
+
224
170
class Branch
225
171
prepend ( Module . new {
172
+ # from branched.rb 7.0
226
173
def grouped_records
227
174
h = { }
228
175
polymorphic_parent = !root? && parent . polymorphic?
229
176
source_records . each do |record |
230
- # each class can resolve virtual_{attributes,includes} differently
231
- @association = record . class . replace_virtual_fields ( association )
232
-
233
- # 1 line optimization for single element array:
234
- @association = association . first if association . kind_of? ( Array ) # && association.size == 1
235
-
236
- case association
237
- when Symbol , String
238
- reflection = record . class . _reflect_on_association ( association )
239
- next if polymorphic_parent && !reflection || !record . association ( association ) . klass
240
- when nil
241
- next
242
- else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
243
- reflection = association
244
- end
177
+ # begin virtual_attributes changes
178
+ association = record . class . replace_virtual_fields ( self . association )
179
+ # end virtual_attributes changes
180
+
181
+ reflection = record . class . _reflect_on_association ( association )
182
+ next if polymorphic_parent && !reflection || !record . association ( association ) . klass
245
183
( h [ reflection ] ||= [ ] ) << record
246
184
end
247
185
h
248
186
end
187
+
188
+ # branched.rb 7.0
189
+ def preloaders_for_reflection ( reflection , reflection_records )
190
+ reflection_records . group_by do |record |
191
+ # begin virtual_attributes changes
192
+ needed_association = record . class . replace_virtual_fields ( association )
193
+ # end virtual_attributes changes
194
+
195
+ klass = record . association ( needed_association ) . klass
196
+
197
+ if reflection . scope && reflection . scope . arity != 0
198
+ # For instance dependent scopes, the scope is potentially
199
+ # different for each record. To allow this we'll group each
200
+ # object separately into its own preloader
201
+ reflection_scope = reflection . join_scopes ( klass . arel_table , klass . predicate_builder , klass , record ) . inject ( &:merge! )
202
+ end
203
+
204
+ [ klass , reflection_scope ]
205
+ end . map do |( rhs_klass , reflection_scope ) , rs |
206
+ preloader_for ( reflection ) . new ( rhs_klass , rs , reflection , scope , reflection_scope , associate_by_default )
207
+ end
208
+ end
249
209
} )
250
- end if ActiveRecord . version >= Gem :: Version . new ( 7.0 )
210
+ end
251
211
end
252
212
end
253
213
@@ -270,9 +230,8 @@ def build_select(arel)
270
230
end
271
231
end
272
232
273
- # from ActiveRecord::QueryMethods (rails 5.2 - 6.0)
274
- # TODO: remove from rails 7.0
275
- def arel_column ( field , &block )
233
+ # from ActiveRecord::QueryMethods (rails 5.2 - 7.0)
234
+ def arel_column ( field )
276
235
if virtual_attribute? ( field ) && ( arel = table [ field ] )
277
236
arel
278
237
else
0 commit comments