activerecord 4.0.4 → 4.1.16
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -4,11 +4,32 @@ module ActiveRecord
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
class_attribute :
|
8
|
-
|
7
|
+
class_attribute :_reflections, instance_writer: false
|
8
|
+
class_attribute :aggregate_reflections, instance_writer: false
|
9
|
+
self._reflections = {}
|
10
|
+
self.aggregate_reflections = {}
|
9
11
|
end
|
10
12
|
|
11
|
-
|
13
|
+
def self.create(macro, name, scope, options, ar)
|
14
|
+
case macro
|
15
|
+
when :has_many, :belongs_to, :has_one
|
16
|
+
klass = options[:through] ? ThroughReflection : AssociationReflection
|
17
|
+
when :composed_of
|
18
|
+
klass = AggregateReflection
|
19
|
+
end
|
20
|
+
|
21
|
+
klass.new(macro, name, scope, options, ar)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_reflection(ar, name, reflection)
|
25
|
+
ar._reflections = ar._reflections.merge(name.to_sym => reflection)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.add_aggregate_reflection(ar, name, reflection)
|
29
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
|
30
|
+
end
|
31
|
+
|
32
|
+
# \Reflection enables to interrogate Active Record classes and objects
|
12
33
|
# about their associations and aggregations. This information can,
|
13
34
|
# for example, be used in a form builder that takes an Active Record object
|
14
35
|
# and creates input fields for all of the attributes depending on their type
|
@@ -17,22 +38,9 @@ module ActiveRecord
|
|
17
38
|
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
18
39
|
# classes.
|
19
40
|
module ClassMethods
|
20
|
-
def create_reflection(macro, name, scope, options, active_record)
|
21
|
-
case macro
|
22
|
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
23
|
-
klass = options[:through] ? ThroughReflection : AssociationReflection
|
24
|
-
reflection = klass.new(macro, name, scope, options, active_record)
|
25
|
-
when :composed_of
|
26
|
-
reflection = AggregateReflection.new(macro, name, scope, options, active_record)
|
27
|
-
end
|
28
|
-
|
29
|
-
self.reflections = self.reflections.merge(name => reflection)
|
30
|
-
reflection
|
31
|
-
end
|
32
|
-
|
33
41
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
34
42
|
def reflect_on_all_aggregations
|
35
|
-
|
43
|
+
aggregate_reflections.values
|
36
44
|
end
|
37
45
|
|
38
46
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
@@ -40,8 +48,25 @@ module ActiveRecord
|
|
40
48
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
41
49
|
#
|
42
50
|
def reflect_on_aggregation(aggregation)
|
43
|
-
|
44
|
-
|
51
|
+
aggregate_reflections[aggregation.to_sym]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
|
55
|
+
#
|
56
|
+
# Account.reflections # => {balance: AggregateReflection}
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def reflections
|
60
|
+
ref = {}
|
61
|
+
_reflections.each do |name, reflection|
|
62
|
+
parent_name, parent_reflection = reflection.parent_reflection
|
63
|
+
if parent_name
|
64
|
+
ref[parent_name] = parent_reflection
|
65
|
+
else
|
66
|
+
ref[name] = reflection
|
67
|
+
end
|
68
|
+
end
|
69
|
+
ref
|
45
70
|
end
|
46
71
|
|
47
72
|
# Returns an array of AssociationReflection objects for all the
|
@@ -54,8 +79,9 @@ module ActiveRecord
|
|
54
79
|
# Account.reflect_on_all_associations # returns an array of all associations
|
55
80
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
56
81
|
#
|
82
|
+
# @api public
|
57
83
|
def reflect_on_all_associations(macro = nil)
|
58
|
-
association_reflections = reflections.values
|
84
|
+
association_reflections = reflections.values
|
59
85
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
60
86
|
end
|
61
87
|
|
@@ -64,12 +90,19 @@ module ActiveRecord
|
|
64
90
|
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
65
91
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
66
92
|
#
|
93
|
+
# @api public
|
67
94
|
def reflect_on_association(association)
|
68
|
-
|
69
|
-
|
95
|
+
reflections[association.to_sym]
|
96
|
+
end
|
97
|
+
|
98
|
+
# @api private
|
99
|
+
def _reflect_on_association(association) #:nodoc:
|
100
|
+
_reflections[association.to_sym]
|
70
101
|
end
|
71
102
|
|
72
103
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
104
|
+
#
|
105
|
+
# @api public
|
73
106
|
def reflect_on_all_autosave_associations
|
74
107
|
reflections.values.select { |reflection| reflection.options[:autosave] }
|
75
108
|
end
|
@@ -100,7 +133,7 @@ module ActiveRecord
|
|
100
133
|
# Returns the hash of options used for the macro.
|
101
134
|
#
|
102
135
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
103
|
-
# <tt>has_many :clients</tt> returns
|
136
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
104
137
|
attr_reader :options
|
105
138
|
|
106
139
|
attr_reader :active_record
|
@@ -113,10 +146,20 @@ module ActiveRecord
|
|
113
146
|
@scope = scope
|
114
147
|
@options = options
|
115
148
|
@active_record = active_record
|
149
|
+
@klass = options[:anonymous_class]
|
116
150
|
@plural_name = active_record.pluralize_table_names ?
|
117
151
|
name.to_s.pluralize : name.to_s
|
118
152
|
end
|
119
153
|
|
154
|
+
def autosave=(autosave)
|
155
|
+
@automatic_inverse_of = false
|
156
|
+
@options[:autosave] = autosave
|
157
|
+
_, parent_reflection = self.parent_reflection
|
158
|
+
if parent_reflection
|
159
|
+
parent_reflection.autosave = autosave
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
120
163
|
# Returns the class for the macro.
|
121
164
|
#
|
122
165
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
@@ -139,7 +182,7 @@ module ActiveRecord
|
|
139
182
|
super ||
|
140
183
|
other_aggregation.kind_of?(self.class) &&
|
141
184
|
name == other_aggregation.name &&
|
142
|
-
other_aggregation.options &&
|
185
|
+
!other_aggregation.options.nil? &&
|
143
186
|
active_record == other_aggregation.active_record
|
144
187
|
end
|
145
188
|
|
@@ -178,9 +221,16 @@ module ActiveRecord
|
|
178
221
|
@klass ||= active_record.send(:compute_type, class_name)
|
179
222
|
end
|
180
223
|
|
181
|
-
|
224
|
+
attr_reader :type, :foreign_type
|
225
|
+
attr_accessor :parent_reflection # [:name, Reflection]
|
226
|
+
|
227
|
+
def initialize(macro, name, scope, options, active_record)
|
182
228
|
super
|
183
229
|
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
|
230
|
+
@automatic_inverse_of = nil
|
231
|
+
@type = options[:as] && "#{options[:as]}_type"
|
232
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
233
|
+
@constructable = calculate_constructable(macro, options)
|
184
234
|
end
|
185
235
|
|
186
236
|
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
@@ -189,12 +239,16 @@ module ActiveRecord
|
|
189
239
|
klass.new(attributes, &block)
|
190
240
|
end
|
191
241
|
|
242
|
+
def constructable? # :nodoc:
|
243
|
+
@constructable
|
244
|
+
end
|
245
|
+
|
192
246
|
def table_name
|
193
|
-
|
247
|
+
klass.table_name
|
194
248
|
end
|
195
249
|
|
196
250
|
def quoted_table_name
|
197
|
-
|
251
|
+
klass.quoted_table_name
|
198
252
|
end
|
199
253
|
|
200
254
|
def join_table
|
@@ -205,16 +259,8 @@ module ActiveRecord
|
|
205
259
|
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
206
260
|
end
|
207
261
|
|
208
|
-
def foreign_type
|
209
|
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
|
210
|
-
end
|
211
|
-
|
212
|
-
def type
|
213
|
-
@type ||= options[:as] && "#{options[:as]}_type"
|
214
|
-
end
|
215
|
-
|
216
262
|
def primary_key_column
|
217
|
-
|
263
|
+
klass.columns_hash[klass.primary_key]
|
218
264
|
end
|
219
265
|
|
220
266
|
def association_foreign_key
|
@@ -238,20 +284,8 @@ module ActiveRecord
|
|
238
284
|
end
|
239
285
|
end
|
240
286
|
|
241
|
-
def columns(tbl_name)
|
242
|
-
@columns ||= klass.connection.columns(tbl_name)
|
243
|
-
end
|
244
|
-
|
245
|
-
def reset_column_information
|
246
|
-
@columns = nil
|
247
|
-
end
|
248
|
-
|
249
287
|
def check_validity!
|
250
288
|
check_validity_of_inverse!
|
251
|
-
|
252
|
-
if has_and_belongs_to_many? && association_foreign_key == foreign_key
|
253
|
-
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
|
254
|
-
end
|
255
289
|
end
|
256
290
|
|
257
291
|
def check_validity_of_inverse!
|
@@ -267,7 +301,7 @@ module ActiveRecord
|
|
267
301
|
end
|
268
302
|
|
269
303
|
def source_reflection
|
270
|
-
|
304
|
+
self
|
271
305
|
end
|
272
306
|
|
273
307
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
@@ -289,18 +323,18 @@ module ActiveRecord
|
|
289
323
|
alias :source_macro :macro
|
290
324
|
|
291
325
|
def has_inverse?
|
292
|
-
|
326
|
+
inverse_name
|
293
327
|
end
|
294
328
|
|
295
329
|
def inverse_of
|
296
|
-
|
297
|
-
|
298
|
-
|
330
|
+
return unless inverse_name
|
331
|
+
|
332
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
299
333
|
end
|
300
334
|
|
301
335
|
def polymorphic_inverse_of(associated_class)
|
302
336
|
if has_inverse?
|
303
|
-
if inverse_relationship = associated_class.
|
337
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
304
338
|
inverse_relationship
|
305
339
|
else
|
306
340
|
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
@@ -333,10 +367,6 @@ module ActiveRecord
|
|
333
367
|
macro == :belongs_to
|
334
368
|
end
|
335
369
|
|
336
|
-
def has_and_belongs_to_many?
|
337
|
-
macro == :has_and_belongs_to_many
|
338
|
-
end
|
339
|
-
|
340
370
|
def association_class
|
341
371
|
case macro
|
342
372
|
when :belongs_to
|
@@ -345,8 +375,6 @@ module ActiveRecord
|
|
345
375
|
else
|
346
376
|
Associations::BelongsToAssociation
|
347
377
|
end
|
348
|
-
when :has_and_belongs_to_many
|
349
|
-
Associations::HasAndBelongsToManyAssociation
|
350
378
|
when :has_many
|
351
379
|
if options[:through]
|
352
380
|
Associations::HasManyThroughAssociation
|
@@ -366,11 +394,96 @@ module ActiveRecord
|
|
366
394
|
options.key? :polymorphic
|
367
395
|
end
|
368
396
|
|
397
|
+
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
398
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
|
399
|
+
|
400
|
+
protected
|
401
|
+
|
402
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
403
|
+
self
|
404
|
+
end
|
405
|
+
|
369
406
|
private
|
407
|
+
|
408
|
+
def calculate_constructable(macro, options)
|
409
|
+
case macro
|
410
|
+
when :belongs_to
|
411
|
+
!options[:polymorphic]
|
412
|
+
when :has_one
|
413
|
+
!options[:through]
|
414
|
+
else
|
415
|
+
true
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# Attempts to find the inverse association name automatically.
|
420
|
+
# If it cannot find a suitable inverse association name, it returns
|
421
|
+
# nil.
|
422
|
+
def inverse_name
|
423
|
+
options.fetch(:inverse_of) do
|
424
|
+
if @automatic_inverse_of == false
|
425
|
+
nil
|
426
|
+
else
|
427
|
+
@automatic_inverse_of ||= automatic_inverse_of
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# returns either nil or the inverse association name that it finds.
|
433
|
+
def automatic_inverse_of
|
434
|
+
if can_find_inverse_of_automatically?(self)
|
435
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
|
436
|
+
|
437
|
+
begin
|
438
|
+
reflection = klass._reflect_on_association(inverse_name)
|
439
|
+
rescue NameError
|
440
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
441
|
+
# to find any associations either.
|
442
|
+
reflection = false
|
443
|
+
end
|
444
|
+
|
445
|
+
if valid_inverse_reflection?(reflection)
|
446
|
+
return inverse_name
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
false
|
451
|
+
end
|
452
|
+
|
453
|
+
# Checks if the inverse reflection that is returned from the
|
454
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
455
|
+
# make sure that the reflection's active_record name matches up
|
456
|
+
# with the current reflection's klass name.
|
457
|
+
#
|
458
|
+
# Note: klass will always be valid because when there's a NameError
|
459
|
+
# from calling +klass+, +reflection+ will already be set to false.
|
460
|
+
def valid_inverse_reflection?(reflection)
|
461
|
+
reflection &&
|
462
|
+
klass.name == reflection.active_record.name &&
|
463
|
+
can_find_inverse_of_automatically?(reflection)
|
464
|
+
end
|
465
|
+
|
466
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
467
|
+
# us from being able to guess the inverse automatically. First, the
|
468
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
469
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
470
|
+
# Third, we must not have options such as <tt>:polymorphic</tt> or
|
471
|
+
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
|
472
|
+
# inverse association.
|
473
|
+
#
|
474
|
+
# Anything with a scope can additionally ruin our attempt at finding an
|
475
|
+
# inverse, so we exclude reflections with scopes.
|
476
|
+
def can_find_inverse_of_automatically?(reflection)
|
477
|
+
reflection.options[:inverse_of] != false &&
|
478
|
+
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
|
479
|
+
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
|
480
|
+
!reflection.scope
|
481
|
+
end
|
482
|
+
|
370
483
|
def derive_class_name
|
371
|
-
class_name = name.to_s
|
484
|
+
class_name = name.to_s
|
372
485
|
class_name = class_name.singularize if collection?
|
373
|
-
class_name
|
486
|
+
class_name.camelize
|
374
487
|
end
|
375
488
|
|
376
489
|
def derive_foreign_key
|
@@ -398,7 +511,12 @@ module ActiveRecord
|
|
398
511
|
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
399
512
|
:active_record_primary_key, :type, :to => :source_reflection
|
400
513
|
|
401
|
-
|
514
|
+
def initialize(macro, name, scope, options, active_record)
|
515
|
+
super
|
516
|
+
@source_reflection_name = options[:source]
|
517
|
+
end
|
518
|
+
|
519
|
+
# Returns the source of the through reflection. It checks both a singularized
|
402
520
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
403
521
|
#
|
404
522
|
# class Post < ActiveRecord::Base
|
@@ -412,12 +530,13 @@ module ActiveRecord
|
|
412
530
|
# end
|
413
531
|
#
|
414
532
|
# tags_reflection = Post.reflect_on_association(:tags)
|
415
|
-
#
|
416
|
-
# taggings_reflection = tags_reflection.source_reflection
|
533
|
+
# tags_reflection.source_reflection
|
417
534
|
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
|
418
535
|
#
|
419
536
|
def source_reflection
|
420
|
-
|
537
|
+
if source_reflection_name
|
538
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
539
|
+
end
|
421
540
|
end
|
422
541
|
|
423
542
|
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
@@ -429,10 +548,11 @@ module ActiveRecord
|
|
429
548
|
# end
|
430
549
|
#
|
431
550
|
# tags_reflection = Post.reflect_on_association(:tags)
|
432
|
-
#
|
551
|
+
# tags_reflection.through_reflection
|
552
|
+
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
|
433
553
|
#
|
434
554
|
def through_reflection
|
435
|
-
|
555
|
+
active_record._reflect_on_association(options[:through])
|
436
556
|
end
|
437
557
|
|
438
558
|
# Returns an array of reflections which are involved in this association. Each item in the
|
@@ -454,7 +574,9 @@ module ActiveRecord
|
|
454
574
|
#
|
455
575
|
def chain
|
456
576
|
@chain ||= begin
|
457
|
-
|
577
|
+
a = source_reflection.chain
|
578
|
+
b = through_reflection.chain
|
579
|
+
chain = a + b
|
458
580
|
chain[0] = self # Use self so we don't lose the information from :source_type
|
459
581
|
chain
|
460
582
|
end
|
@@ -489,8 +611,11 @@ module ActiveRecord
|
|
489
611
|
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
490
612
|
|
491
613
|
if options[:source_type]
|
492
|
-
|
493
|
-
|
614
|
+
type = foreign_type
|
615
|
+
source_type = options[:source_type]
|
616
|
+
through_scope_chain.first << lambda { |object|
|
617
|
+
where(type => source_type)
|
618
|
+
}
|
494
619
|
end
|
495
620
|
|
496
621
|
# Recursively fill out the rest of the array from the through reflection
|
@@ -505,7 +630,7 @@ module ActiveRecord
|
|
505
630
|
|
506
631
|
# A through association is nested if there would be more than one join table
|
507
632
|
def nested?
|
508
|
-
chain.length > 2
|
633
|
+
chain.length > 2
|
509
634
|
end
|
510
635
|
|
511
636
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -514,12 +639,7 @@ module ActiveRecord
|
|
514
639
|
def association_primary_key(klass = nil)
|
515
640
|
# Get the "actual" source reflection if the immediate source reflection has a
|
516
641
|
# source reflection itself
|
517
|
-
|
518
|
-
while source_reflection.source_reflection
|
519
|
-
source_reflection = source_reflection.source_reflection
|
520
|
-
end
|
521
|
-
|
522
|
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
642
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
523
643
|
end
|
524
644
|
|
525
645
|
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
@@ -534,7 +654,32 @@ module ActiveRecord
|
|
534
654
|
# # => [:tag, :tags]
|
535
655
|
#
|
536
656
|
def source_reflection_names
|
537
|
-
|
657
|
+
(options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
|
658
|
+
end
|
659
|
+
|
660
|
+
def source_reflection_name # :nodoc:
|
661
|
+
return @source_reflection_name.to_sym if @source_reflection_name
|
662
|
+
|
663
|
+
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
|
664
|
+
names = names.find_all { |n|
|
665
|
+
through_reflection.klass._reflect_on_association(n)
|
666
|
+
}
|
667
|
+
|
668
|
+
if names.length > 1
|
669
|
+
example_options = options.dup
|
670
|
+
example_options[:source] = source_reflection_names.first
|
671
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
672
|
+
Ambiguous source reflection for through association. Please specify a :source
|
673
|
+
directive on your declaration like:
|
674
|
+
|
675
|
+
class #{active_record.name} < ActiveRecord::Base
|
676
|
+
#{macro} :#{name}, #{example_options}
|
677
|
+
end
|
678
|
+
|
679
|
+
eowarn
|
680
|
+
end
|
681
|
+
|
682
|
+
@source_reflection_name = names.first
|
538
683
|
end
|
539
684
|
|
540
685
|
def source_options
|
@@ -573,6 +718,12 @@ module ActiveRecord
|
|
573
718
|
check_validity_of_inverse!
|
574
719
|
end
|
575
720
|
|
721
|
+
protected
|
722
|
+
|
723
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
724
|
+
source_reflection.actual_source_reflection
|
725
|
+
end
|
726
|
+
|
576
727
|
private
|
577
728
|
def derive_class_name
|
578
729
|
# get the class_name of the belongs_to association of the through reflection
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
|
12
12
|
# specified by the +:batch_size+ option).
|
13
13
|
#
|
14
|
-
# Person.
|
14
|
+
# Person.find_each do |person|
|
15
15
|
# person.do_awesome_stuff
|
16
16
|
# end
|
17
17
|
#
|
@@ -19,53 +19,103 @@ module ActiveRecord
|
|
19
19
|
# person.party_all_night!
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
# Yields each batch of records that was found by the find +options+ as
|
31
|
-
# an array. The size of each batch is set by the +:batch_size+
|
32
|
-
# option; the default is 1000.
|
22
|
+
# If you do not provide a block to #find_each, it will return an Enumerator
|
23
|
+
# for chaining with other methods:
|
24
|
+
#
|
25
|
+
# Person.find_each.with_index do |person, index|
|
26
|
+
# person.award_trophy(index + 1)
|
27
|
+
# end
|
33
28
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
29
|
+
# ==== Options
|
30
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
31
|
+
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
|
32
|
+
# This is especially useful if you want multiple workers dealing with
|
33
|
+
# the same processing queue. You can make worker 1 handle all the records
|
34
|
+
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
35
|
+
# (by setting the +:start+ option on that worker).
|
40
36
|
#
|
41
|
-
#
|
37
|
+
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
|
38
|
+
# Person.find_each(start: 2000, batch_size: 2000) do |person|
|
39
|
+
# person.party_all_night!
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
42
43
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
43
44
|
# work. This also means that this method only works with integer-based
|
44
|
-
# primary keys.
|
45
|
+
# primary keys.
|
46
|
+
#
|
47
|
+
# NOTE: You can't set the limit either, that's used to control
|
45
48
|
# the batch sizes.
|
49
|
+
def find_each(options = {})
|
50
|
+
if block_given?
|
51
|
+
find_in_batches(options) do |records|
|
52
|
+
records.each { |record| yield record }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
enum_for :find_each, options do
|
56
|
+
options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Yields each batch of records that was found by the find +options+ as
|
62
|
+
# an array.
|
46
63
|
#
|
47
64
|
# Person.where("age > 21").find_in_batches do |group|
|
48
65
|
# sleep(50) # Make sure it doesn't get too crowded in there!
|
49
66
|
# group.each { |person| person.party_all_night! }
|
50
67
|
# end
|
51
68
|
#
|
69
|
+
# If you do not provide a block to #find_in_batches, it will return an Enumerator
|
70
|
+
# for chaining with other methods:
|
71
|
+
#
|
72
|
+
# Person.find_in_batches.with_index do |group, batch|
|
73
|
+
# puts "Processing group ##{batch}"
|
74
|
+
# group.each(&:recover_from_last_night!)
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# To be yielded each record one by one, use #find_each instead.
|
78
|
+
#
|
79
|
+
# ==== Options
|
80
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
81
|
+
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
|
82
|
+
# This is especially useful if you want multiple workers dealing with
|
83
|
+
# the same processing queue. You can make worker 1 handle all the records
|
84
|
+
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
85
|
+
# (by setting the +:start+ option on that worker).
|
86
|
+
#
|
52
87
|
# # Let's process the next 2000 records
|
53
|
-
# Person.
|
88
|
+
# Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
|
54
89
|
# group.each { |person| person.party_all_night! }
|
55
90
|
# end
|
91
|
+
#
|
92
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
93
|
+
# ascending on the primary key ("id ASC") to make the batch ordering
|
94
|
+
# work. This also means that this method only works with integer-based
|
95
|
+
# primary keys.
|
96
|
+
#
|
97
|
+
# NOTE: You can't set the limit either, that's used to control
|
98
|
+
# the batch sizes.
|
56
99
|
def find_in_batches(options = {})
|
57
100
|
options.assert_valid_keys(:start, :batch_size)
|
58
101
|
|
59
102
|
relation = self
|
103
|
+
start = options[:start]
|
104
|
+
batch_size = options[:batch_size] || 1000
|
105
|
+
|
106
|
+
unless block_given?
|
107
|
+
return to_enum(:find_in_batches, options) do
|
108
|
+
total = start ? where(table[primary_key].gteq(start)).size : size
|
109
|
+
(total - 1).div(batch_size) + 1
|
110
|
+
end
|
111
|
+
end
|
60
112
|
|
61
113
|
if logger && (arel.orders.present? || arel.taken.present?)
|
62
114
|
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
63
115
|
end
|
64
116
|
|
65
|
-
start = options.delete(:start)
|
66
|
-
batch_size = options.delete(:batch_size) || 1000
|
67
|
-
|
68
117
|
relation = relation.reorder(batch_order).limit(batch_size)
|
118
|
+
relation.reverse_order_value = false
|
69
119
|
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
|
70
120
|
|
71
121
|
while records.any?
|