activerecord 3.1.0.rc4 → 3.1.0.rc5

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 (44) hide show
  1. data/CHANGELOG +25 -0
  2. data/lib/active_record/associations.rb +6 -5
  3. data/lib/active_record/associations/alias_tracker.rb +12 -24
  4. data/lib/active_record/associations/association.rb +56 -6
  5. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  6. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -12
  7. data/lib/active_record/associations/collection_association.rb +44 -31
  8. data/lib/active_record/associations/collection_proxy.rb +11 -4
  9. data/lib/active_record/associations/has_one_association.rb +1 -1
  10. data/lib/active_record/associations/join_helper.rb +1 -2
  11. data/lib/active_record/associations/preloader/association.rb +3 -2
  12. data/lib/active_record/associations/singular_association.rb +12 -4
  13. data/lib/active_record/attribute_methods.rb +1 -1
  14. data/lib/active_record/attribute_methods/primary_key.rb +1 -1
  15. data/lib/active_record/attribute_methods/read.rb +3 -2
  16. data/lib/active_record/autosave_association.rb +1 -1
  17. data/lib/active_record/base.rb +78 -30
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -6
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -4
  21. data/lib/active_record/connection_adapters/column.rb +2 -0
  22. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -0
  25. data/lib/active_record/locking/optimistic.rb +1 -1
  26. data/lib/active_record/migration.rb +6 -2
  27. data/lib/active_record/migration/command_recorder.rb +1 -1
  28. data/lib/active_record/named_scope.rb +19 -0
  29. data/lib/active_record/nested_attributes.rb +18 -12
  30. data/lib/active_record/persistence.rb +8 -3
  31. data/lib/active_record/query_cache.rb +8 -0
  32. data/lib/active_record/railtie.rb +23 -0
  33. data/lib/active_record/railties/databases.rake +18 -13
  34. data/lib/active_record/reflection.rb +26 -26
  35. data/lib/active_record/relation.rb +20 -12
  36. data/lib/active_record/relation/batches.rb +0 -2
  37. data/lib/active_record/relation/calculations.rb +11 -5
  38. data/lib/active_record/relation/finder_methods.rb +1 -1
  39. data/lib/active_record/relation/query_methods.rb +16 -7
  40. data/lib/active_record/relation/spawn_methods.rb +1 -1
  41. data/lib/active_record/session_store.rb +19 -9
  42. data/lib/active_record/test_case.rb +0 -1
  43. data/lib/active_record/version.rb +1 -1
  44. metadata +15 -29
data/CHANGELOG CHANGED
@@ -1,5 +1,30 @@
1
1
  *Rails 3.1.0 (unreleased)*
2
2
 
3
+ * Active Record's dynamic finder will now show a deprecation warning if you passing in less number of arguments than what you call in method signature. This behavior will raise ArgumentError in the next version of Rails [Prem Sichanugrist]
4
+
5
+ * Deprecated the AssociationCollection constant. CollectionProxy is now the appropriate constant
6
+ to use, though be warned that this is not really a public API.
7
+
8
+ This should solve upgrade problems with the will_paginate plugin (and perhaps others). Thanks
9
+ Paul Battley for reporting.
10
+
11
+ [Jon Leighton]
12
+
13
+ * ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature.
14
+
15
+ Before: def build_association(*options)
16
+ After: def build_association(*options, &block)
17
+
18
+ Users who are redefining this method to extend functionality should ensure that the block is
19
+ passed through to ActiveRecord::Base#new.
20
+
21
+ This change is necessary to fix https://github.com/rails/rails/issues/1842.
22
+
23
+ A deprecation warning and workaround has been added to 3.1, but authors will need to update
24
+ their code for it to work correctly in 3.2.
25
+
26
+ [Jon Leighton]
27
+
3
28
  * AR#pluralize_table_names can be used to singularize/pluralize table name of an individual model:
4
29
 
5
30
  class User < ActiveRecord::Base
@@ -119,6 +119,7 @@ module ActiveRecord
119
119
  autoload :SingularAssociation, 'active_record/associations/singular_association'
120
120
  autoload :CollectionAssociation, 'active_record/associations/collection_association'
121
121
  autoload :CollectionProxy, 'active_record/associations/collection_proxy'
122
+ autoload :AssociationCollection, 'active_record/associations/collection_proxy'
122
123
 
123
124
  autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
124
125
  autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
@@ -426,7 +427,7 @@ module ActiveRecord
426
427
  # end
427
428
  # end
428
429
  #
429
- # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
430
+ # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
430
431
  # person.first_name # => "David"
431
432
  # person.last_name # => "Heinemeier Hansson"
432
433
  #
@@ -483,7 +484,7 @@ module ActiveRecord
483
484
  # belongs_to :book
484
485
  # end
485
486
  #
486
- # @author = Author.find :first
487
+ # @author = Author.first
487
488
  # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
488
489
  # @author.books # selects all books by using the Authorship join model
489
490
  #
@@ -503,7 +504,7 @@ module ActiveRecord
503
504
  # belongs_to :client
504
505
  # end
505
506
  #
506
- # @firm = Firm.find :first
507
+ # @firm = Firm.first
507
508
  # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
508
509
  # @firm.invoices # selects all invoices by going through the Client join model
509
510
  #
@@ -738,7 +739,7 @@ module ActiveRecord
738
739
  # has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
739
740
  # end
740
741
  #
741
- # Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments.
742
+ # Picture.first(:include => :most_recent_comments).most_recent_comments # => returns all associated comments.
742
743
  #
743
744
  # When eager loaded, conditions are interpolated in the context of the model class, not
744
745
  # the model instance. Conditions are lazily interpolated before the actual model exists.
@@ -1200,7 +1201,7 @@ module ActiveRecord
1200
1201
  # === Example
1201
1202
  #
1202
1203
  # An Account class declares <tt>has_one :beneficiary</tt>, which will add:
1203
- # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
1204
+ # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>)
1204
1205
  # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
1205
1206
  # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
1206
1207
  # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
 
10
10
  # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
11
  def initialize(table_joins = [])
12
- @aliases = Hash.new
12
+ @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
13
  @table_joins = table_joins
14
14
  end
15
15
 
@@ -26,8 +26,6 @@ module ActiveRecord
26
26
  def aliased_name_for(table_name, aliased_name = nil)
27
27
  aliased_name ||= table_name
28
28
 
29
- initialize_count_for(table_name) if aliases[table_name].nil?
30
-
31
29
  if aliases[table_name].zero?
32
30
  # If it's zero, we can have our table_name
33
31
  aliases[table_name] = 1
@@ -36,8 +34,6 @@ module ActiveRecord
36
34
  # Otherwise, we need to use an alias
37
35
  aliased_name = connection.table_alias_for(aliased_name)
38
36
 
39
- initialize_count_for(aliased_name) if aliases[aliased_name].nil?
40
-
41
37
  # Update the count
42
38
  aliases[aliased_name] += 1
43
39
 
@@ -49,32 +45,24 @@ module ActiveRecord
49
45
  end
50
46
  end
51
47
 
52
- def pluralize(table_name, base)
53
- base.pluralize_table_names ? table_name.to_s.pluralize : table_name.to_s
54
- end
55
-
56
48
  private
57
49
 
58
- def initialize_count_for(name)
59
- aliases[name] = 0
60
-
61
- unless Arel::Table === table_joins
62
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
63
- quoted_name = connection.quote_table_name(name).downcase
50
+ def initial_count_for(name)
51
+ return 0 if Arel::Table === table_joins
64
52
 
65
- aliases[name] += table_joins.map { |join|
66
- # Table names + table aliases
67
- join.left.downcase.scan(
68
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
69
- ).size
70
- }.sum
71
- end
53
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
54
+ quoted_name = connection.quote_table_name(name).downcase
72
55
 
73
- aliases[name]
56
+ table_joins.map { |join|
57
+ # Table names + table aliases
58
+ join.left.downcase.scan(
59
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
60
+ ).size
61
+ }.sum
74
62
  end
75
63
 
76
64
  def truncate(name)
77
- name[0..connection.table_alias_length-3]
65
+ name.slice(0, connection.table_alias_length - 2)
78
66
  end
79
67
 
80
68
  def connection
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
2
  require 'active_support/core_ext/object/inclusion'
3
+ require 'active_support/deprecation'
3
4
 
4
5
  module ActiveRecord
5
6
  module Associations
@@ -30,7 +31,7 @@ module ActiveRecord
30
31
  @updated = false
31
32
 
32
33
  reset
33
- construct_scope
34
+ reset_scope
34
35
  end
35
36
 
36
37
  # Returns the name of the table of the related class:
@@ -51,7 +52,7 @@ module ActiveRecord
51
52
  # Reloads the \target and returns +self+ on success.
52
53
  def reload
53
54
  reset
54
- construct_scope
55
+ reset_scope
55
56
  load_target
56
57
  self unless target.nil?
57
58
  end
@@ -84,21 +85,25 @@ module ActiveRecord
84
85
  end
85
86
 
86
87
  def scoped
87
- target_scope.merge(@association_scope)
88
+ target_scope.merge(association_scope)
88
89
  end
89
90
 
90
- # Construct the scope for this association.
91
+ # The scope for this association.
91
92
  #
92
93
  # Note that the association_scope is merged into the target_scope only when the
93
94
  # scoped method is called. This is because at that point the call may be surrounded
94
95
  # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
95
96
  # actually gets built.
96
- def construct_scope
97
+ def association_scope
97
98
  if klass
98
- @association_scope = AssociationScope.new(self).scope
99
+ @association_scope ||= AssociationScope.new(self).scope
99
100
  end
100
101
  end
101
102
 
103
+ def reset_scope
104
+ @association_scope = nil
105
+ end
106
+
102
107
  # Set the inverse association, if possible
103
108
  def set_inverse_instance(record)
104
109
  if record && invertible_for?(record)
@@ -224,6 +229,51 @@ module ActiveRecord
224
229
  def association_class
225
230
  @reflection.klass
226
231
  end
232
+
233
+ def build_record(attributes, options)
234
+ reflection.original_build_association_called = false
235
+
236
+ record = reflection.build_association(attributes, options) do |r|
237
+ r.assign_attributes(
238
+ create_scope.except(*r.changed),
239
+ :without_protection => true
240
+ )
241
+ end
242
+
243
+ if !reflection.original_build_association_called &&
244
+ (record.changed & create_scope.keys) != create_scope.keys
245
+ # We have detected that there is an overridden AssociationReflection#build_association
246
+ # method, but it looks like it has not passed through the block above. So try again and
247
+ # show a noisy deprecation warning.
248
+
249
+ record.assign_attributes(
250
+ create_scope.except(*record.changed),
251
+ :without_protection => true
252
+ )
253
+
254
+ method = reflection.method(:build_association)
255
+ if RUBY_VERSION >= '1.9.2'
256
+ source = method.source_location
257
+ debug_info = "It looks like the method is defined in #{source[0]} at line #{source[1]}."
258
+ else
259
+ debug_info = "This might help you find the method: #{method}. If you run this on Ruby 1.9.2 we can tell you exactly where the method is."
260
+ end
261
+
262
+ ActiveSupport::Deprecation.warn <<-WARN
263
+ It looks like ActiveRecord::Reflection::AssociationReflection#build_association has been redefined, either by you or by a plugin or library that you are using. The signature of this method has changed.
264
+
265
+ Before: def build_association(*options)
266
+ After: def build_association(*options, &block)
267
+
268
+ The block argument now needs to be passed through to ActiveRecord::Base#new when this method is overridden, or else your associations will not function correctly in Rails 3.2.
269
+
270
+ #{debug_info}
271
+
272
+ WARN
273
+ end
274
+
275
+ record
276
+ end
227
277
  end
228
278
  end
229
279
  end
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  # Checks whether record is different to the current target, without loading it
38
38
  def different_target?(record)
39
39
  record.nil? && owner[reflection.foreign_key] ||
40
- record.id != owner[reflection.foreign_key]
40
+ record && record.id != owner[reflection.foreign_key]
41
41
  end
42
42
 
43
43
  def replace_keys(record)
@@ -7,24 +7,22 @@ module ActiveRecord::Associations::Builder
7
7
  def build
8
8
  reflection = super
9
9
  check_validity(reflection)
10
- define_after_destroy_method
10
+ define_destroy_hook
11
11
  reflection
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- def define_after_destroy_method
16
+ def define_destroy_hook
17
17
  name = self.name
18
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
19
- def #{after_destroy_method_name}
20
- association(#{name.to_sym.inspect}).delete_all
21
- end
22
- eoruby
23
- model.after_destroy after_destroy_method_name
24
- end
25
-
26
- def after_destroy_method_name
27
- "has_and_belongs_to_many_after_destroy_for_#{name}"
18
+ model.send(:include, Module.new {
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ def destroy_associations
21
+ association(#{name.to_sym.inspect}).delete_all
22
+ super
23
+ end
24
+ RUBY
25
+ })
28
26
  end
29
27
 
30
28
  # TODO: These checks should probably be moved into the Reflection, and we should not be
@@ -114,19 +114,13 @@ module ActiveRecord
114
114
  # Add +records+ to this association. Returns +self+ so method calls may be chained.
115
115
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
116
116
  def concat(*records)
117
- result = true
118
117
  load_target if owner.new_record?
119
118
 
120
- transaction do
121
- records.flatten.each do |record|
122
- raise_on_type_mismatch(record)
123
- add_to_target(record) do |r|
124
- result &&= insert_record(record) unless owner.new_record?
125
- end
126
- end
119
+ if owner.new_record?
120
+ concat_records(records)
121
+ else
122
+ transaction { concat_records(records) }
127
123
  end
128
-
129
- result && records
130
124
  end
131
125
 
132
126
  # Starts a transaction in the association class's database connection.
@@ -295,14 +289,10 @@ module ActiveRecord
295
289
  other_array.each { |val| raise_on_type_mismatch(val) }
296
290
  original_target = load_target.dup
297
291
 
298
- transaction do
299
- delete(target - other_array)
300
-
301
- unless concat(other_array - target)
302
- @target = original_target
303
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
304
- "new records could not be saved."
305
- end
292
+ if owner.new_record?
293
+ replace_records(other_array, original_target)
294
+ else
295
+ transaction { replace_records(other_array, original_target) }
306
296
  end
307
297
  end
308
298
 
@@ -388,7 +378,7 @@ module ActiveRecord
388
378
 
389
379
  persisted.map! do |record|
390
380
  # Unfortunately we cannot simply do memory.delete(record) since on 1.8 this returns
391
- # record rather than memory.at(memory.index(record)). The behaviour is fixed in 1.9.
381
+ # record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
392
382
  mem_index = memory.index(record)
393
383
 
394
384
  if mem_index
@@ -433,25 +423,25 @@ module ActiveRecord
433
423
  scoped.scope_for_create.stringify_keys
434
424
  end
435
425
 
436
- def build_record(attributes, options)
437
- record = reflection.build_association(attributes, options)
438
- record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
439
- record
440
- end
441
-
442
426
  def delete_or_destroy(records, method)
443
427
  records = records.flatten
444
428
  records.each { |record| raise_on_type_mismatch(record) }
445
429
  existing_records = records.reject { |r| r.new_record? }
446
430
 
447
- transaction do
448
- records.each { |record| callback(:before_remove, record) }
431
+ if existing_records.empty?
432
+ remove_records(existing_records, records, method)
433
+ else
434
+ transaction { remove_records(existing_records, records, method) }
435
+ end
436
+ end
437
+
438
+ def remove_records(existing_records, records, method)
439
+ records.each { |record| callback(:before_remove, record) }
449
440
 
450
- delete_records(existing_records, method) if existing_records.any?
451
- records.each { |record| target.delete(record) }
441
+ delete_records(existing_records, method) if existing_records.any?
442
+ records.each { |record| target.delete(record) }
452
443
 
453
- records.each { |record| callback(:after_remove, record) }
454
- end
444
+ records.each { |record| callback(:after_remove, record) }
455
445
  end
456
446
 
457
447
  # Delete the given records from the association, using one of the methods :destroy,
@@ -460,6 +450,29 @@ module ActiveRecord
460
450
  raise NotImplementedError
461
451
  end
462
452
 
453
+ def replace_records(new_target, original_target)
454
+ delete(target - new_target)
455
+
456
+ unless concat(new_target - target)
457
+ @target = original_target
458
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
459
+ "new records could not be saved."
460
+ end
461
+ end
462
+
463
+ def concat_records(records)
464
+ result = true
465
+
466
+ records.flatten.each do |record|
467
+ raise_on_type_mismatch(record)
468
+ add_to_target(record) do |r|
469
+ result &&= insert_record(record) unless owner.new_record?
470
+ end
471
+ end
472
+
473
+ result && records
474
+ end
475
+
463
476
  def callback(method, record)
464
477
  callbacks_for(method).each do |callback|
465
478
  case callback
@@ -1,5 +1,12 @@
1
+ require 'active_support/deprecation'
2
+
1
3
  module ActiveRecord
2
4
  module Associations
5
+ AssociationCollection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
6
+ 'ActiveRecord::Associations::AssociationCollection',
7
+ 'ActiveRecord::Associations::CollectionProxy'
8
+ )
9
+
3
10
  # Association proxies in Active Record are middlemen between the object that
4
11
  # holds the association, known as the <tt>@owner</tt>, and the actual associated
5
12
  # object, known as the <tt>@target</tt>. The kind of association any proxy is
@@ -12,7 +19,7 @@ module ActiveRecord
12
19
  # has_many :posts
13
20
  # end
14
21
  #
15
- # blog = Blog.find(:first)
22
+ # blog = Blog.first
16
23
  #
17
24
  # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
18
25
  # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
@@ -58,10 +65,10 @@ module ActiveRecord
58
65
 
59
66
  alias_method :new, :build
60
67
 
61
- def respond_to?(*args)
68
+ def respond_to?(name, include_private = false)
62
69
  super ||
63
- (load_target && target.respond_to?(*args)) ||
64
- @association.klass.respond_to?(*args)
70
+ (load_target && target.respond_to?(name, include_private)) ||
71
+ @association.klass.respond_to?(name, include_private)
65
72
  end
66
73
 
67
74
  def method_missing(method, *args, &block)