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.
- data/CHANGELOG +25 -0
- data/lib/active_record/associations.rb +6 -5
- data/lib/active_record/associations/alias_tracker.rb +12 -24
- data/lib/active_record/associations/association.rb +56 -6
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -12
- data/lib/active_record/associations/collection_association.rb +44 -31
- data/lib/active_record/associations/collection_proxy.rb +11 -4
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -2
- data/lib/active_record/associations/preloader/association.rb +3 -2
- data/lib/active_record/associations/singular_association.rb +12 -4
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +3 -2
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +78 -30
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -4
- data/lib/active_record/connection_adapters/column.rb +2 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -0
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/migration.rb +6 -2
- data/lib/active_record/migration/command_recorder.rb +1 -1
- data/lib/active_record/named_scope.rb +19 -0
- data/lib/active_record/nested_attributes.rb +18 -12
- data/lib/active_record/persistence.rb +8 -3
- data/lib/active_record/query_cache.rb +8 -0
- data/lib/active_record/railtie.rb +23 -0
- data/lib/active_record/railties/databases.rake +18 -13
- data/lib/active_record/reflection.rb +26 -26
- data/lib/active_record/relation.rb +20 -12
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +11 -5
- data/lib/active_record/relation/finder_methods.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +16 -7
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/session_store.rb +19 -9
- data/lib/active_record/test_case.rb +0 -1
- data/lib/active_record/version.rb +1 -1
- 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.
|
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.
|
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.
|
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.
|
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.
|
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
|
59
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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(
|
88
|
+
target_scope.merge(association_scope)
|
88
89
|
end
|
89
90
|
|
90
|
-
#
|
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
|
97
|
+
def association_scope
|
97
98
|
if klass
|
98
|
-
@association_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
|
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
|
-
|
10
|
+
define_destroy_hook
|
11
11
|
reflection
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def
|
16
|
+
def define_destroy_hook
|
17
17
|
name = self.name
|
18
|
-
model.send(:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
121
|
-
records
|
122
|
-
|
123
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
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
|
-
|
448
|
-
|
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
|
-
|
451
|
-
|
441
|
+
delete_records(existing_records, method) if existing_records.any?
|
442
|
+
records.each { |record| target.delete(record) }
|
452
443
|
|
453
|
-
|
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.
|
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?(
|
68
|
+
def respond_to?(name, include_private = false)
|
62
69
|
super ||
|
63
|
-
(load_target && target.respond_to?(
|
64
|
-
@association.klass.respond_to?(
|
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)
|