activerecord 3.0.20 → 3.1.0.beta1

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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -13,4 +13,4 @@ module ArJdbcMySQL
13
13
  alias_method :errno, :error_number
14
14
  alias_method :error, :message
15
15
  end
16
- end
16
+ end
@@ -1,8 +1,17 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/module/deprecation'
3
+ require 'active_support/core_ext/object/inclusion'
4
+
1
5
  module ActiveRecord
2
6
  # = Active Record Reflection
3
7
  module Reflection # :nodoc:
4
8
  extend ActiveSupport::Concern
5
9
 
10
+ included do
11
+ class_attribute :reflections
12
+ self.reflections = {}
13
+ end
14
+
6
15
  # Reflection enables to interrogate Active Record classes and objects
7
16
  # about their associations and aggregations. This information can,
8
17
  # for example, be used in a form builder that takes an Active Record object
@@ -20,23 +29,14 @@ module ActiveRecord
20
29
  when :composed_of
21
30
  reflection = AggregateReflection.new(macro, name, options, active_record)
22
31
  end
23
- write_inheritable_hash :reflections, name => reflection
24
- reflection
25
- end
26
32
 
27
- # Returns a hash containing all AssociationReflection objects for the current class.
28
- # Example:
29
- #
30
- # Invoice.reflections
31
- # Account.reflections
32
- #
33
- def reflections
34
- read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
33
+ self.reflections = self.reflections.merge(name => reflection)
34
+ reflection
35
35
  end
36
36
 
37
37
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
38
38
  def reflect_on_all_aggregations
39
- reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
39
+ reflections.values.grep(AggregateReflection)
40
40
  end
41
41
 
42
42
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -58,7 +58,7 @@ module ActiveRecord
58
58
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
59
59
  #
60
60
  def reflect_on_all_associations(macro = nil)
61
- association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
61
+ association_reflections = reflections.values.grep(AssociationReflection)
62
62
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
63
63
  end
64
64
 
@@ -164,7 +164,7 @@ module ActiveRecord
164
164
 
165
165
  def initialize(macro, name, options, active_record)
166
166
  super
167
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
167
+ @collection = macro.in?([:has_many, :has_and_belongs_to_many])
168
168
  end
169
169
 
170
170
  # Returns a new, unsaved instance of the associated class. +options+ will
@@ -198,8 +198,21 @@ module ActiveRecord
198
198
  @quoted_table_name ||= klass.quoted_table_name
199
199
  end
200
200
 
201
+ def foreign_key
202
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
203
+ end
204
+
201
205
  def primary_key_name
202
- @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
206
+ foreign_key
207
+ end
208
+ deprecate :primary_key_name => :foreign_key
209
+
210
+ def foreign_type
211
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
212
+ end
213
+
214
+ def type
215
+ @type ||= "#{options[:as]}_type"
203
216
  end
204
217
 
205
218
  def primary_key_column
@@ -211,7 +224,10 @@ module ActiveRecord
211
224
  end
212
225
 
213
226
  def association_primary_key
214
- @association_primary_key ||= options[:primary_key] || klass.primary_key
227
+ @association_primary_key ||=
228
+ options[:primary_key] ||
229
+ !options[:polymorphic] && klass.primary_key ||
230
+ 'id'
215
231
  end
216
232
 
217
233
  def active_record_primary_key
@@ -247,18 +263,32 @@ module ActiveRecord
247
263
  end
248
264
 
249
265
  def through_reflection
250
- false
251
- end
252
-
253
- def through_reflection_primary_key_name
266
+ nil
254
267
  end
255
268
 
256
269
  def source_reflection
257
270
  nil
258
271
  end
259
272
 
273
+ # A chain of reflections from this one back to the owner. For more see the explanation in
274
+ # ThroughReflection.
275
+ def chain
276
+ [self]
277
+ end
278
+
279
+ # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
280
+ # in the #chain. The inside arrays are simply conditions (and each condition may itself be
281
+ # a hash, array, arel predicate, etc...)
282
+ def conditions
283
+ conditions = [options[:conditions]].compact
284
+ conditions << { type => active_record.base_class.name } if options[:as]
285
+ [conditions]
286
+ end
287
+
288
+ alias :source_macro :macro
289
+
260
290
  def has_inverse?
261
- !@options[:inverse_of].nil?
291
+ @options[:inverse_of]
262
292
  end
263
293
 
264
294
  def inverse_of
@@ -297,22 +327,36 @@ module ActiveRecord
297
327
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
298
328
  end
299
329
 
300
- def dependent_conditions(record, base_class, extra_conditions)
301
- dependent_conditions = []
302
- dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
303
- dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
304
- dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
305
- dependent_conditions << extra_conditions if extra_conditions
306
- dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
307
- dependent_conditions = dependent_conditions.gsub('@', '\@')
308
- dependent_conditions
309
- end
310
-
311
330
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
312
331
  def belongs_to?
313
332
  macro == :belongs_to
314
333
  end
315
334
 
335
+ def association_class
336
+ case macro
337
+ when :belongs_to
338
+ if options[:polymorphic]
339
+ Associations::BelongsToPolymorphicAssociation
340
+ else
341
+ Associations::BelongsToAssociation
342
+ end
343
+ when :has_and_belongs_to_many
344
+ Associations::HasAndBelongsToManyAssociation
345
+ when :has_many
346
+ if options[:through]
347
+ Associations::HasManyThroughAssociation
348
+ else
349
+ Associations::HasManyAssociation
350
+ end
351
+ when :has_one
352
+ if options[:through]
353
+ Associations::HasOneThroughAssociation
354
+ else
355
+ Associations::HasOneAssociation
356
+ end
357
+ end
358
+ end
359
+
316
360
  private
317
361
  def derive_class_name
318
362
  class_name = name.to_s.camelize
@@ -320,7 +364,7 @@ module ActiveRecord
320
364
  class_name
321
365
  end
322
366
 
323
- def derive_primary_key_name
367
+ def derive_foreign_key
324
368
  if belongs_to?
325
369
  "#{name}_id"
326
370
  elsif options[:as]
@@ -334,6 +378,8 @@ module ActiveRecord
334
378
  # Holds all the meta-data about a :through association as it was specified
335
379
  # in the Active Record class.
336
380
  class ThroughReflection < AssociationReflection #:nodoc:
381
+ delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :to => :source_reflection
382
+
337
383
  # Gets the source of the through reflection. It checks both a singularized
338
384
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
339
385
  #
@@ -361,6 +407,88 @@ module ActiveRecord
361
407
  @through_reflection ||= active_record.reflect_on_association(options[:through])
362
408
  end
363
409
 
410
+ # Returns an array of reflections which are involved in this association. Each item in the
411
+ # array corresponds to a table which will be part of the query for this association.
412
+ #
413
+ # The chain is built by recursively calling #chain on the source reflection and the through
414
+ # reflection. The base case for the recursion is a normal association, which just returns
415
+ # [self] as its #chain.
416
+ def chain
417
+ @chain ||= begin
418
+ chain = source_reflection.chain + through_reflection.chain
419
+ chain[0] = self # Use self so we don't lose the information from :source_type
420
+ chain
421
+ end
422
+ end
423
+
424
+ # Consider the following example:
425
+ #
426
+ # class Person
427
+ # has_many :articles
428
+ # has_many :comment_tags, :through => :articles
429
+ # end
430
+ #
431
+ # class Article
432
+ # has_many :comments
433
+ # has_many :comment_tags, :through => :comments, :source => :tags
434
+ # end
435
+ #
436
+ # class Comment
437
+ # has_many :tags
438
+ # end
439
+ #
440
+ # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
441
+ # but only Comment.tags will be represented in the #chain. So this method creates an array
442
+ # of conditions corresponding to the chain. Each item in the #conditions array corresponds
443
+ # to an item in the #chain, and is itself an array of conditions from an arbitrary number
444
+ # of relevant reflections, plus any :source_type or polymorphic :as constraints.
445
+ def conditions
446
+ @conditions ||= begin
447
+ conditions = source_reflection.conditions
448
+
449
+ # Add to it the conditions from this reflection if necessary.
450
+ conditions.first << options[:conditions] if options[:conditions]
451
+
452
+ through_conditions = through_reflection.conditions
453
+
454
+ if options[:source_type]
455
+ through_conditions.first << { foreign_type => options[:source_type] }
456
+ end
457
+
458
+ # Recursively fill out the rest of the array from the through reflection
459
+ conditions += through_conditions
460
+
461
+ # And return
462
+ conditions
463
+ end
464
+ end
465
+
466
+ # The macro used by the source association
467
+ def source_macro
468
+ source_reflection.source_macro
469
+ end
470
+
471
+ # A through association is nested iff there would be more than one join table
472
+ def nested?
473
+ chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
474
+ end
475
+
476
+ # We want to use the klass from this reflection, rather than just delegate straight to
477
+ # the source_reflection, because the source_reflection may be polymorphic. We still
478
+ # need to respect the source_reflection's :primary_key option, though.
479
+ def association_primary_key
480
+ @association_primary_key ||= begin
481
+ # Get the "actual" source reflection if the immediate source reflection has a
482
+ # source reflection itself
483
+ source_reflection = self.source_reflection
484
+ while source_reflection.source_reflection
485
+ source_reflection = source_reflection.source_reflection
486
+ end
487
+
488
+ source_reflection.options[:primary_key] || klass.primary_key
489
+ end
490
+ end
491
+
364
492
  # Gets an array of possible <tt>:through</tt> source reflection names:
365
493
  #
366
494
  # [:singularized, :pluralized]
@@ -369,11 +497,23 @@ module ActiveRecord
369
497
  @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
370
498
  end
371
499
 
500
+ def source_options
501
+ source_reflection.options
502
+ end
503
+
504
+ def through_options
505
+ through_reflection.options
506
+ end
507
+
372
508
  def check_validity!
373
509
  if through_reflection.nil?
374
510
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
375
511
  end
376
512
 
513
+ if through_reflection.options[:polymorphic]
514
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
515
+ end
516
+
377
517
  if source_reflection.nil?
378
518
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
379
519
  end
@@ -383,24 +523,16 @@ module ActiveRecord
383
523
  end
384
524
 
385
525
  if source_reflection.options[:polymorphic] && options[:source_type].nil?
386
- raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
526
+ raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
387
527
  end
388
528
 
389
- unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
390
- raise HasManyThroughSourceAssociationMacroError.new(self)
529
+ if macro == :has_one && through_reflection.collection?
530
+ raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
391
531
  end
392
532
 
393
533
  check_validity_of_inverse!
394
534
  end
395
535
 
396
- def through_reflection_primary_key
397
- through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
398
- end
399
-
400
- def through_reflection_primary_key_name
401
- through_reflection.primary_key_name if through_reflection.belongs_to?
402
- end
403
-
404
536
  private
405
537
  def derive_class_name
406
538
  # get the class_name of the belongs_to association of the through reflection
@@ -5,29 +5,75 @@ module ActiveRecord
5
5
  class Relation
6
6
  JoinOperation = Struct.new(:relation, :join_class, :on)
7
7
  ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
8
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
9
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
8
+ MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
9
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder]
10
10
 
11
11
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
12
12
 
13
+ # These are explicitly delegated to improve performance (avoids method_missing)
13
14
  delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
14
- delegate :insert, :to => :arel
15
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
15
16
 
16
17
  attr_reader :table, :klass, :loaded
17
- attr_accessor :extensions
18
+ attr_accessor :extensions, :default_scoped
18
19
  alias :loaded? :loaded
20
+ alias :default_scoped? :default_scoped
19
21
 
20
22
  def initialize(klass, table)
21
23
  @klass, @table = klass, table
22
24
 
23
25
  @implicit_readonly = nil
24
26
  @loaded = false
27
+ @default_scoped = false
25
28
 
26
29
  SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
27
30
  (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
28
31
  @extensions = []
29
32
  end
30
33
 
34
+ def insert(values)
35
+ primary_key_value = nil
36
+
37
+ if primary_key && Hash === values
38
+ primary_key_value = values[values.keys.find { |k|
39
+ k.name == primary_key
40
+ }]
41
+
42
+ if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
43
+ primary_key_value = connection.next_sequence_value(klass.sequence_name)
44
+ values[klass.arel_table[klass.primary_key]] = primary_key_value
45
+ end
46
+ end
47
+
48
+ im = arel.create_insert
49
+ im.into @table
50
+
51
+ conn = @klass.connection
52
+
53
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
54
+ binds = substitutes.map do |arel_attr, value|
55
+ [@klass.columns_hash[arel_attr.name], value]
56
+ end
57
+
58
+ substitutes.each_with_index do |tuple, i|
59
+ tuple[1] = conn.substitute_at(binds[i][0], i)
60
+ end
61
+
62
+ if values.empty? # empty insert
63
+ im.values = Arel.sql(connection.empty_insert_statement_value)
64
+ else
65
+ im.insert substitutes
66
+ end
67
+
68
+ conn.insert(
69
+ im.to_sql,
70
+ 'SQL',
71
+ primary_key,
72
+ primary_key_value,
73
+ nil,
74
+ binds)
75
+ end
76
+
31
77
  def new(*args, &block)
32
78
  scoping { @klass.new(*args, &block) }
33
79
  end
@@ -47,25 +93,28 @@ module ActiveRecord
47
93
  end
48
94
 
49
95
  def respond_to?(method, include_private = false)
50
- return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private)
51
-
52
- if match = DynamicFinderMatch.match(method)
53
- return true if @klass.send(:all_attributes_exists?, match.attribute_names)
54
- elsif match = DynamicScopeMatch.match(method)
55
- return true if @klass.send(:all_attributes_exists?, match.attribute_names)
56
- else
96
+ arel.respond_to?(method, include_private) ||
97
+ Array.method_defined?(method) ||
98
+ @klass.respond_to?(method, include_private) ||
57
99
  super
58
- end
59
100
  end
60
101
 
61
102
  def to_a
62
103
  return @records if loaded?
63
104
 
64
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
105
+ @records = if @readonly_value.nil? && !@klass.locking_enabled?
106
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
107
+ else
108
+ IdentityMap.without do
109
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
110
+ end
111
+ end
65
112
 
66
113
  preload = @preload_values
67
114
  preload += @includes_values unless eager_loading?
68
- preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
115
+ preload.each do |associations|
116
+ ActiveRecord::Associations::Preloader.new(@records, associations).run
117
+ end
69
118
 
70
119
  # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
71
120
  # are JOINS and no explicit SELECT.
@@ -120,12 +169,7 @@ module ActiveRecord
120
169
  # Please check unscoped if you want to remove all previous scopes (including
121
170
  # the default_scope) during the execution of a block.
122
171
  def scoping
123
- @klass.scoped_methods << self
124
- begin
125
- yield
126
- ensure
127
- @klass.scoped_methods.pop
128
- end
172
+ @klass.send(:with_scope, self, :overwrite) { yield }
129
173
  end
130
174
 
131
175
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -153,16 +197,30 @@ module ActiveRecord
153
197
  #
154
198
  # # Update all books that match conditions, but limit it to 5 ordered by date
155
199
  # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
200
+ #
201
+ # # Conditions from the current relation also works
202
+ # Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
203
+ #
204
+ # # The same idea applies to limit and order
205
+ # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
156
206
  def update_all(updates, conditions = nil, options = {})
207
+ IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
157
208
  if conditions || options.present?
158
209
  where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
159
210
  else
211
+ limit = nil
212
+ order = []
160
213
  # Apply limit and order only if they're both present
161
214
  if @limit_value.present? == @order_values.present?
162
- arel.update(Arel::SqlLiteral.new(@klass.send(:sanitize_sql_for_assignment, updates)))
163
- else
164
- except(:limit, :order).update_all(updates)
215
+ limit = arel.limit
216
+ order = arel.orders
165
217
  end
218
+
219
+ stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
220
+ stmt.take limit if limit
221
+ stmt.order(*order)
222
+ stmt.key = table[primary_key]
223
+ @klass.connection.update stmt.to_sql, 'SQL', bind_values
166
224
  end
167
225
  end
168
226
 
@@ -219,6 +277,7 @@ module ActiveRecord
219
277
  #
220
278
  # Person.destroy_all("last_login < '2004-04-04'")
221
279
  # Person.destroy_all(:status => "inactive")
280
+ # Person.where(:age => 0..18).destroy_all
222
281
  def destroy_all(conditions = nil)
223
282
  if conditions
224
283
  where(conditions).destroy_all
@@ -268,12 +327,23 @@ module ActiveRecord
268
327
  #
269
328
  # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
270
329
  # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
330
+ # Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
271
331
  #
272
332
  # Both calls delete the affected posts all at once with a single DELETE statement.
273
333
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
274
334
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
275
335
  def delete_all(conditions = nil)
276
- conditions ? where(conditions).delete_all : arel.delete.tap { reset }
336
+ IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
337
+ if conditions
338
+ where(conditions).delete_all
339
+ else
340
+ statement = arel.compile_delete
341
+ affected = @klass.connection.delete(
342
+ statement.to_sql, 'SQL', bind_values)
343
+
344
+ reset
345
+ affected
346
+ end
277
347
  end
278
348
 
279
349
  # Deletes the row with a primary key matching the +id+ argument, using a
@@ -297,7 +367,8 @@ module ActiveRecord
297
367
  # # Delete multiple rows
298
368
  # Todo.delete([2,3,4])
299
369
  def delete(id_or_array)
300
- where(@klass.primary_key => id_or_array).delete_all
370
+ IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
371
+ where(primary_key => id_or_array).delete_all
301
372
  end
302
373
 
303
374
  def reload
@@ -313,33 +384,20 @@ module ActiveRecord
313
384
  self
314
385
  end
315
386
 
316
- def primary_key
317
- @primary_key ||= table[@klass.primary_key]
318
- end
319
-
320
387
  def to_sql
321
388
  @to_sql ||= arel.to_sql
322
389
  end
323
390
 
324
391
  def where_values_hash
325
- Hash[@where_values.find_all { |w|
326
- w.respond_to?(:operator) && w.operator == :== && w.left.relation.name == table_name
327
- }.map { |where|
328
- [
329
- where.left.name,
330
- where.right.respond_to?(:value) ? where.right.value : where.right
331
- ]
332
- }]
392
+ equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
393
+ node.left.relation.name == table_name
394
+ }
395
+
396
+ Hash[equalities.map { |where| [where.left.name, where.right] }]
333
397
  end
334
398
 
335
399
  def scope_for_create
336
- @scope_for_create ||= begin
337
- if @create_with_value
338
- @create_with_value.reverse_merge(where_values_hash)
339
- else
340
- where_values_hash
341
- end
342
- end
400
+ @scope_for_create ||= where_values_hash.merge(@create_with_value || {})
343
401
  end
344
402
 
345
403
  def eager_loading?
@@ -351,7 +409,7 @@ module ActiveRecord
351
409
  when Relation
352
410
  other.to_sql == to_sql
353
411
  when Array
354
- to_a == other.to_a
412
+ to_a == other
355
413
  end
356
414
  end
357
415
 
@@ -359,13 +417,20 @@ module ActiveRecord
359
417
  to_a.inspect
360
418
  end
361
419
 
420
+ def with_default_scope #:nodoc:
421
+ if default_scoped?
422
+ default_scope = @klass.send(:build_default_scope)
423
+ default_scope ? default_scope.merge(self) : self
424
+ else
425
+ self
426
+ end
427
+ end
428
+
362
429
  protected
363
430
 
364
431
  def method_missing(method, *args, &block)
365
432
  if Array.method_defined?(method)
366
433
  to_a.send(method, *args, &block)
367
- elsif @klass.scopes[method]
368
- merge(@klass.send(method, *args, &block))
369
434
  elsif @klass.respond_to?(method)
370
435
  scoping { @klass.send(method, *args, &block) }
371
436
  elsif arel.respond_to?(method)
@@ -378,8 +443,19 @@ module ActiveRecord
378
443
  private
379
444
 
380
445
  def references_eager_loaded_tables?
446
+ joined_tables = arel.join_sources.map do |join|
447
+ if join.is_a?(Arel::Nodes::StringJoin)
448
+ tables_in_string(join.left)
449
+ else
450
+ [join.left.table_name, join.left.table_alias]
451
+ end
452
+ end
453
+
454
+ joined_tables += [table.name, table.table_alias]
455
+
381
456
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
382
- joined_tables = (tables_in_string(arel.join_sql) + [table.name, table.table_alias]).compact.map{ |t| t.downcase }.uniq
457
+ joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
458
+
383
459
  (tables_in_string(to_sql) - joined_tables).any?
384
460
  end
385
461
 
@@ -387,7 +463,7 @@ module ActiveRecord
387
463
  return [] if string.blank?
388
464
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
389
465
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
390
- string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
466
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
391
467
  end
392
468
 
393
469
  end