activerecord 3.2.22.4 → 4.0.13

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.

Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,12 +1,10 @@
1
- require 'active_support/concern'
2
- require 'active_support/core_ext/class/attribute'
3
1
 
4
2
  module ActiveRecord
5
3
  module ReadonlyAttributes
6
4
  extend ActiveSupport::Concern
7
5
 
8
6
  included do
9
- class_attribute :_attr_readonly, :instance_writer => false
7
+ class_attribute :_attr_readonly, instance_accessor: false
10
8
  self._attr_readonly = []
11
9
  end
12
10
 
@@ -22,5 +20,11 @@ module ActiveRecord
22
20
  self._attr_readonly
23
21
  end
24
22
  end
23
+
24
+ def _attr_readonly
25
+ message = "Instance level _attr_readonly method is deprecated, please use class level method."
26
+ ActiveSupport::Deprecation.warn message
27
+ defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
28
+ end
25
29
  end
26
30
  end
@@ -1,6 +1,3 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/inclusion'
3
-
4
1
  module ActiveRecord
5
2
  # = Active Record Reflection
6
3
  module Reflection # :nodoc:
@@ -20,13 +17,13 @@ module ActiveRecord
20
17
  # MacroReflection class has info for AggregateReflection and AssociationReflection
21
18
  # classes.
22
19
  module ClassMethods
23
- def create_reflection(macro, name, options, active_record)
20
+ def create_reflection(macro, name, scope, options, active_record)
24
21
  case macro
25
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
26
- klass = options[:through] ? ThroughReflection : AssociationReflection
27
- reflection = klass.new(macro, name, options, active_record)
28
- when :composed_of
29
- reflection = AggregateReflection.new(macro, name, options, active_record)
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)
30
27
  end
31
28
 
32
29
  self.reflections = self.reflections.merge(name => reflection)
@@ -43,7 +40,8 @@ module ActiveRecord
43
40
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
44
41
  #
45
42
  def reflect_on_aggregation(aggregation)
46
- reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
43
+ reflection = reflections[aggregation]
44
+ reflection if reflection.is_a?(AggregateReflection)
47
45
  end
48
46
 
49
47
  # Returns an array of AssociationReflection objects for all the
@@ -67,7 +65,8 @@ module ActiveRecord
67
65
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
68
66
  #
69
67
  def reflect_on_association(association)
70
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
68
+ reflection = reflections[association]
69
+ reflection if reflection.is_a?(AssociationReflection)
71
70
  end
72
71
 
73
72
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -76,25 +75,31 @@ module ActiveRecord
76
75
  end
77
76
  end
78
77
 
79
-
80
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
78
+ # Base class for AggregateReflection and AssociationReflection. Objects of
81
79
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
80
+ #
81
+ # MacroReflection
82
+ # AggregateReflection
83
+ # AssociationReflection
84
+ # ThroughReflection
82
85
  class MacroReflection
83
86
  # Returns the name of the macro.
84
87
  #
85
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
88
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
86
89
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
87
90
  attr_reader :name
88
91
 
89
92
  # Returns the macro type.
90
93
  #
91
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
94
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
92
95
  # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
93
96
  attr_reader :macro
94
97
 
98
+ attr_reader :scope
99
+
95
100
  # Returns the hash of options used for the macro.
96
101
  #
97
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
102
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
98
103
  # <tt>has_many :clients</tt> returns +{}+
99
104
  attr_reader :options
100
105
 
@@ -102,9 +107,10 @@ module ActiveRecord
102
107
 
103
108
  attr_reader :plural_name # :nodoc:
104
109
 
105
- def initialize(macro, name, options, active_record)
110
+ def initialize(macro, name, scope, options, active_record)
106
111
  @macro = macro
107
112
  @name = name
113
+ @scope = scope
108
114
  @options = options
109
115
  @active_record = active_record
110
116
  @plural_name = active_record.pluralize_table_names ?
@@ -113,7 +119,7 @@ module ActiveRecord
113
119
 
114
120
  # Returns the class for the macro.
115
121
  #
116
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
122
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
117
123
  # <tt>has_many :clients</tt> returns the Client class
118
124
  def klass
119
125
  @klass ||= class_name.constantize
@@ -121,7 +127,7 @@ module ActiveRecord
121
127
 
122
128
  # Returns the class name for the macro.
123
129
  #
124
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
130
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
125
131
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
126
132
  def class_name
127
133
  @class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -137,10 +143,6 @@ module ActiveRecord
137
143
  active_record == other_aggregation.active_record
138
144
  end
139
145
 
140
- def sanitized_conditions #:nodoc:
141
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
142
- end
143
-
144
146
  private
145
147
  def derive_class_name
146
148
  name.to_s.camelize
@@ -151,6 +153,10 @@ module ActiveRecord
151
153
  # Holds all the meta-data about an aggregation as it was specified in the
152
154
  # Active Record class.
153
155
  class AggregateReflection < MacroReflection #:nodoc:
156
+ def mapping
157
+ mapping = options[:mapping] || [name, name]
158
+ mapping.first.is_a?(Array) ? mapping : [mapping]
159
+ end
154
160
  end
155
161
 
156
162
  # Holds all the meta-data about an association as it was specified in the
@@ -172,15 +178,15 @@ module ActiveRecord
172
178
  @klass ||= active_record.send(:compute_type, class_name)
173
179
  end
174
180
 
175
- def initialize(macro, name, options, active_record)
181
+ def initialize(*args)
176
182
  super
177
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
183
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
178
184
  end
179
185
 
180
- # Returns a new, unsaved instance of the associated class. +options+ will
186
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
181
187
  # be passed to the class's constructor.
182
- def build_association(*options, &block)
183
- klass.new(*options, &block)
188
+ def build_association(attributes, &block)
189
+ klass.new(attributes, &block)
184
190
  end
185
191
 
186
192
  def table_name
@@ -191,6 +197,10 @@ module ActiveRecord
191
197
  @quoted_table_name ||= klass.quoted_table_name
192
198
  end
193
199
 
200
+ def join_table
201
+ @join_table ||= options[:join_table] || derive_join_table
202
+ end
203
+
194
204
  def foreign_key
195
205
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
196
206
  end
@@ -228,8 +238,8 @@ module ActiveRecord
228
238
  end
229
239
  end
230
240
 
231
- def columns(tbl_name, log_msg)
232
- @columns ||= klass.connection.columns(tbl_name, log_msg)
241
+ def columns(tbl_name)
242
+ @columns ||= klass.connection.columns(tbl_name)
233
243
  end
234
244
 
235
245
  def reset_column_information
@@ -238,6 +248,10 @@ module ActiveRecord
238
248
 
239
249
  def check_validity!
240
250
  check_validity_of_inverse!
251
+
252
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
253
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
254
+ end
241
255
  end
242
256
 
243
257
  def check_validity_of_inverse!
@@ -266,11 +280,10 @@ module ActiveRecord
266
280
  false
267
281
  end
268
282
 
269
- # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
270
- # in the #chain. The inside arrays are simply conditions (and each condition may itself be
271
- # a hash, array, arel predicate, etc...)
272
- def conditions
273
- [[options[:conditions]].compact]
283
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
284
+ # in the #chain.
285
+ def scope_chain
286
+ scope ? [[scope]] : [[]]
274
287
  end
275
288
 
276
289
  alias :source_macro :macro
@@ -306,10 +319,10 @@ module ActiveRecord
306
319
  # the parent's validation.
307
320
  #
308
321
  # Unless you explicitly disable validation with
309
- # <tt>:validate => false</tt>, validation will take place when:
322
+ # <tt>validate: false</tt>, validation will take place when:
310
323
  #
311
- # * you explicitly enable validation; <tt>:validate => true</tt>
312
- # * you use autosave; <tt>:autosave => true</tt>
324
+ # * you explicitly enable validation; <tt>validate: true</tt>
325
+ # * you use autosave; <tt>autosave: true</tt>
313
326
  # * the association is a +has_many+ association
314
327
  def validate?
315
328
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
@@ -320,6 +333,10 @@ module ActiveRecord
320
333
  macro == :belongs_to
321
334
  end
322
335
 
336
+ def has_and_belongs_to_many?
337
+ macro == :has_and_belongs_to_many
338
+ end
339
+
323
340
  def association_class
324
341
  case macro
325
342
  when :belongs_to
@@ -345,11 +362,15 @@ module ActiveRecord
345
362
  end
346
363
  end
347
364
 
365
+ def polymorphic?
366
+ options.key? :polymorphic
367
+ end
368
+
348
369
  private
349
370
  def derive_class_name
350
- class_name = name.to_s.camelize
371
+ class_name = name.to_s
351
372
  class_name = class_name.singularize if collection?
352
- class_name
373
+ class_name.camelize
353
374
  end
354
375
 
355
376
  def derive_foreign_key
@@ -362,6 +383,10 @@ module ActiveRecord
362
383
  end
363
384
  end
364
385
 
386
+ def derive_join_table
387
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
388
+ end
389
+
365
390
  def primary_key(klass)
366
391
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
367
392
  end
@@ -378,9 +403,19 @@ module ActiveRecord
378
403
  #
379
404
  # class Post < ActiveRecord::Base
380
405
  # has_many :taggings
381
- # has_many :tags, :through => :taggings
406
+ # has_many :tags, through: :taggings
382
407
  # end
383
408
  #
409
+ # class Tagging < ActiveRecord::Base
410
+ # belongs_to :post
411
+ # belongs_to :tag
412
+ # end
413
+ #
414
+ # tags_reflection = Post.reflect_on_association(:tags)
415
+ #
416
+ # taggings_reflection = tags_reflection.source_reflection
417
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
418
+ #
384
419
  def source_reflection
385
420
  @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
386
421
  end
@@ -390,7 +425,7 @@ module ActiveRecord
390
425
  #
391
426
  # class Post < ActiveRecord::Base
392
427
  # has_many :taggings
393
- # has_many :tags, :through => :taggings
428
+ # has_many :tags, through: :taggings
394
429
  # end
395
430
  #
396
431
  # tags_reflection = Post.reflect_on_association(:tags)
@@ -406,6 +441,17 @@ module ActiveRecord
406
441
  # The chain is built by recursively calling #chain on the source reflection and the through
407
442
  # reflection. The base case for the recursion is a normal association, which just returns
408
443
  # [self] as its #chain.
444
+ #
445
+ # class Post < ActiveRecord::Base
446
+ # has_many :taggings
447
+ # has_many :tags, through: :taggings
448
+ # end
449
+ #
450
+ # tags_reflection = Post.reflect_on_association(:tags)
451
+ # tags_reflection.chain
452
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
453
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
454
+ #
409
455
  def chain
410
456
  @chain ||= begin
411
457
  chain = source_reflection.chain + through_reflection.chain
@@ -418,41 +464,37 @@ module ActiveRecord
418
464
  #
419
465
  # class Person
420
466
  # has_many :articles
421
- # has_many :comment_tags, :through => :articles
467
+ # has_many :comment_tags, through: :articles
422
468
  # end
423
469
  #
424
470
  # class Article
425
471
  # has_many :comments
426
- # has_many :comment_tags, :through => :comments, :source => :tags
472
+ # has_many :comment_tags, through: :comments, source: :tags
427
473
  # end
428
474
  #
429
475
  # class Comment
430
476
  # has_many :tags
431
477
  # end
432
478
  #
433
- # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
479
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
434
480
  # but only Comment.tags will be represented in the #chain. So this method creates an array
435
- # of conditions corresponding to the chain. Each item in the #conditions array corresponds
436
- # to an item in the #chain, and is itself an array of conditions from an arbitrary number
437
- # of relevant reflections, plus any :source_type or polymorphic :as constraints.
438
- def conditions
439
- @conditions ||= begin
440
- conditions = source_reflection.conditions.map { |c| c.dup }
481
+ # of scopes corresponding to the chain.
482
+ def scope_chain
483
+ @scope_chain ||= begin
484
+ scope_chain = source_reflection.scope_chain.map(&:dup)
441
485
 
442
- # Add to it the conditions from this reflection if necessary.
443
- conditions.first << options[:conditions] if options[:conditions]
486
+ # Add to it the scope from this reflection (if any)
487
+ scope_chain.first << scope if scope
444
488
 
445
- through_conditions = through_reflection.conditions
489
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
446
490
 
447
491
  if options[:source_type]
448
- through_conditions.first << { foreign_type => options[:source_type] }
492
+ through_scope_chain.first <<
493
+ through_reflection.klass.where(foreign_type => options[:source_type])
449
494
  end
450
495
 
451
496
  # Recursively fill out the rest of the array from the through reflection
452
- conditions += through_conditions
453
-
454
- # And return
455
- conditions
497
+ scope_chain + through_scope_chain
456
498
  end
457
499
  end
458
500
 
@@ -480,9 +522,16 @@ module ActiveRecord
480
522
  source_reflection.options[:primary_key] || primary_key(klass || self.klass)
481
523
  end
482
524
 
483
- # Gets an array of possible <tt>:through</tt> source reflection names:
525
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
526
+ #
527
+ # class Post < ActiveRecord::Base
528
+ # has_many :taggings
529
+ # has_many :tags, through: :taggings
530
+ # end
484
531
  #
485
- # [:singularized, :pluralized]
532
+ # tags_reflection = Post.reflect_on_association(:tags)
533
+ # tags_reflection.source_reflection_names
534
+ # # => [:tag, :tags]
486
535
  #
487
536
  def source_reflection_names
488
537
  @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
@@ -1,21 +1,26 @@
1
- require 'active_support/core_ext/object/blank'
2
1
 
3
2
  module ActiveRecord
4
3
  module Batches
5
- # Yields each record that was found by the find +options+. The find is
6
- # performed by find_in_batches with a batch size of 1000 (or as
7
- # specified by the <tt>:batch_size</tt> option).
4
+ # Looping through a collection of records from the database
5
+ # (using the +all+ method, for example) is very inefficient
6
+ # since it will try to instantiate all the objects at once.
8
7
  #
9
- # Example:
8
+ # In that case, batch processing methods allow you to work
9
+ # with the records in batches, thereby greatly reducing memory consumption.
10
+ #
11
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
12
+ # specified by the +:batch_size+ option).
13
+ #
14
+ # Person.all.find_each do |person|
15
+ # person.do_awesome_stuff
16
+ # end
10
17
  #
11
18
  # Person.where("age > 21").find_each do |person|
12
19
  # person.party_all_night!
13
20
  # end
14
21
  #
15
- # Note: This method is only intended to use for batch processing of
16
- # large amounts of records that wouldn't fit in memory all at once. If
17
- # you just need to loop over less than 1000 records, it's probably
18
- # better just to use the regular find methods.
22
+ # You can also pass the +:start+ option to specify
23
+ # an offset to control the starting point.
19
24
  def find_each(options = {})
20
25
  find_in_batches(options) do |records|
21
26
  records.each { |record| yield record }
@@ -23,40 +28,38 @@ module ActiveRecord
23
28
  end
24
29
 
25
30
  # Yields each batch of records that was found by the find +options+ as
26
- # an array. The size of each batch is set by the <tt>:batch_size</tt>
31
+ # an array. The size of each batch is set by the +:batch_size+
27
32
  # option; the default is 1000.
28
33
  #
29
34
  # You can control the starting point for the batch processing by
30
- # supplying the <tt>:start</tt> option. This is especially useful if you
35
+ # supplying the +:start+ option. This is especially useful if you
31
36
  # want multiple workers dealing with the same processing queue. You can
32
37
  # make worker 1 handle all the records between id 0 and 10,000 and
33
- # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
38
+ # worker 2 handle from 10,000 and beyond (by setting the +:start+
34
39
  # option on that worker).
35
40
  #
36
41
  # It's not possible to set the order. That is automatically set to
37
42
  # ascending on the primary key ("id ASC") to make the batch ordering
38
- # work. This also mean that this method only works with integer-based
43
+ # work. This also means that this method only works with integer-based
39
44
  # primary keys. You can't set the limit either, that's used to control
40
45
  # the batch sizes.
41
46
  #
42
- # Example:
43
- #
44
47
  # Person.where("age > 21").find_in_batches do |group|
45
48
  # sleep(50) # Make sure it doesn't get too crowded in there!
46
49
  # group.each { |person| person.party_all_night! }
47
50
  # end
51
+ #
52
+ # # Let's process the next 2000 records
53
+ # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
54
+ # group.each { |person| person.party_all_night! }
55
+ # end
48
56
  def find_in_batches(options = {})
49
- relation = self
57
+ options.assert_valid_keys(:start, :batch_size)
50
58
 
51
- unless arel.orders.blank? && arel.taken.blank?
52
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
53
- end
54
-
55
- if (finder_options = options.except(:start, :batch_size)).present?
56
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
57
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
59
+ relation = self
58
60
 
59
- relation = apply_finder_options(finder_options)
61
+ if logger && (arel.orders.present? || arel.taken.present?)
62
+ logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
60
63
  end
61
64
 
62
65
  start = options.delete(:start)
@@ -68,16 +71,13 @@ module ActiveRecord
68
71
  while records.any?
69
72
  records_size = records.size
70
73
  primary_key_offset = records.last.id
74
+ raise "Primary key not included in the custom select clause" unless primary_key_offset
71
75
 
72
76
  yield records
73
77
 
74
78
  break if records_size < batch_size
75
79
 
76
- if primary_key_offset
77
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
78
- else
79
- raise "Primary key not included in the custom select clause"
80
- end
80
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
81
81
  end
82
82
  end
83
83