activerecord 3.1.9 → 3.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +317 -336
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +3 -42
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +64 -31
  16. data/lib/active_record/associations/collection_proxy.rb +2 -37
  17. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  18. data/lib/active_record/associations/has_many_association.rb +5 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +28 -9
  20. data/lib/active_record/associations/has_one_association.rb +15 -13
  21. data/lib/active_record/associations/join_dependency.rb +2 -2
  22. data/lib/active_record/associations/preloader.rb +14 -10
  23. data/lib/active_record/associations/through_association.rb +7 -3
  24. data/lib/active_record/associations.rb +92 -76
  25. data/lib/active_record/attribute_assignment.rb +221 -0
  26. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  27. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  28. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  29. data/lib/active_record/attribute_methods/read.rb +73 -83
  30. data/lib/active_record/attribute_methods/serialization.rb +102 -0
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
  32. data/lib/active_record/attribute_methods/write.rb +31 -6
  33. data/lib/active_record/attribute_methods.rb +231 -30
  34. data/lib/active_record/autosave_association.rb +43 -22
  35. data/lib/active_record/base.rb +227 -1708
  36. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  37. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  38. data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
  39. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  40. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
  45. data/lib/active_record/connection_adapters/column.rb +37 -11
  46. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
  47. data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
  49. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  50. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  51. data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
  52. data/lib/active_record/counter_cache.rb +9 -4
  53. data/lib/active_record/dynamic_finder_match.rb +12 -0
  54. data/lib/active_record/dynamic_matchers.rb +84 -0
  55. data/lib/active_record/errors.rb +11 -1
  56. data/lib/active_record/explain.rb +85 -0
  57. data/lib/active_record/explain_subscriber.rb +25 -0
  58. data/lib/active_record/fixtures/file.rb +65 -0
  59. data/lib/active_record/fixtures.rb +56 -85
  60. data/lib/active_record/identity_map.rb +3 -4
  61. data/lib/active_record/inheritance.rb +174 -0
  62. data/lib/active_record/integration.rb +49 -0
  63. data/lib/active_record/locking/optimistic.rb +30 -25
  64. data/lib/active_record/locking/pessimistic.rb +23 -1
  65. data/lib/active_record/log_subscriber.rb +3 -3
  66. data/lib/active_record/migration/command_recorder.rb +8 -8
  67. data/lib/active_record/migration.rb +68 -35
  68. data/lib/active_record/model_schema.rb +366 -0
  69. data/lib/active_record/nested_attributes.rb +3 -2
  70. data/lib/active_record/persistence.rb +57 -11
  71. data/lib/active_record/querying.rb +58 -0
  72. data/lib/active_record/railtie.rb +31 -29
  73. data/lib/active_record/railties/controller_runtime.rb +3 -1
  74. data/lib/active_record/railties/databases.rake +191 -110
  75. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  76. data/lib/active_record/readonly_attributes.rb +26 -0
  77. data/lib/active_record/reflection.rb +7 -15
  78. data/lib/active_record/relation/batches.rb +5 -2
  79. data/lib/active_record/relation/calculations.rb +47 -15
  80. data/lib/active_record/relation/delegation.rb +49 -0
  81. data/lib/active_record/relation/finder_methods.rb +9 -7
  82. data/lib/active_record/relation/predicate_builder.rb +18 -7
  83. data/lib/active_record/relation/query_methods.rb +75 -9
  84. data/lib/active_record/relation/spawn_methods.rb +11 -2
  85. data/lib/active_record/relation.rb +78 -32
  86. data/lib/active_record/result.rb +1 -1
  87. data/lib/active_record/sanitization.rb +194 -0
  88. data/lib/active_record/schema_dumper.rb +12 -5
  89. data/lib/active_record/scoping/default.rb +142 -0
  90. data/lib/active_record/scoping/named.rb +202 -0
  91. data/lib/active_record/scoping.rb +152 -0
  92. data/lib/active_record/serialization.rb +1 -43
  93. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  94. data/lib/active_record/session_store.rb +17 -15
  95. data/lib/active_record/store.rb +52 -0
  96. data/lib/active_record/test_case.rb +11 -7
  97. data/lib/active_record/timestamp.rb +17 -3
  98. data/lib/active_record/transactions.rb +27 -6
  99. data/lib/active_record/translation.rb +22 -0
  100. data/lib/active_record/validations/associated.rb +5 -4
  101. data/lib/active_record/validations/uniqueness.rb +7 -7
  102. data/lib/active_record/validations.rb +1 -1
  103. data/lib/active_record/version.rb +2 -2
  104. data/lib/active_record.rb +38 -3
  105. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  106. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  107. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  108. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  109. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  110. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  111. metadata +30 -10
  112. data/lib/active_record/named_scope.rb +0 -200
data/README.rdoc CHANGED
@@ -197,13 +197,13 @@ Admit the Database:
197
197
 
198
198
  == Download and installation
199
199
 
200
- The latest version of Active Record can be installed with Rubygems:
200
+ The latest version of Active Record can be installed with RubyGems:
201
201
 
202
202
  % [sudo] gem install activerecord
203
203
 
204
204
  Source code can be downloaded as part of the Rails project on GitHub
205
205
 
206
- * https://github.com/rails/rails/tree/master/activerecord
206
+ * https://github.com/rails/rails/tree/3-2-stable/activerecord
207
207
 
208
208
 
209
209
  == License
@@ -215,7 +215,7 @@ Active Record is released under the MIT license.
215
215
 
216
216
  API documentation is at
217
217
 
218
- * http://api.rubyonrails.com
218
+ * http://api.rubyonrails.org
219
219
 
220
220
  Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
221
221
 
@@ -31,6 +31,14 @@ class Exhibit < ActiveRecord::Base
31
31
  def look; attributes end
32
32
  def feel; look; user.name end
33
33
 
34
+ def self.with_name
35
+ where("name IS NOT NULL")
36
+ end
37
+
38
+ def self.with_notes
39
+ where("notes IS NOT NULL")
40
+ end
41
+
34
42
  def self.look(exhibits) exhibits.each { |e| e.look } end
35
43
  def self.feel(exhibits) exhibits.each { |e| e.feel } end
36
44
  end
@@ -39,7 +47,14 @@ puts 'Generating data...'
39
47
 
40
48
  module ActiveRecord
41
49
  class Faker
42
- LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. Praesent varius tincidunt commodo".split
50
+ LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
51
+ Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
52
+ Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
53
+ tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
54
+ varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
55
+ tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
56
+ Praesent varius tincidunt commodo}.split
57
+
43
58
  def self.name
44
59
  LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
45
60
  end
@@ -114,6 +129,10 @@ Benchmark.ips(TIME) do |x|
114
129
  Exhibit.look Exhibit.limit(10000)
115
130
  end
116
131
 
132
+ x.report 'Model.named_scope' do
133
+ Exhibit.limit(10).with_name.with_notes
134
+ end
135
+
117
136
  x.report 'Model.create' do
118
137
  Exhibit.create(exhibit)
119
138
  end
@@ -176,7 +176,7 @@ module ActiveRecord
176
176
  # order in which mappings are defined determines the order in which attributes are sent to the
177
177
  # value class constructor.
178
178
  # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
179
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
179
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
180
180
  # mapped attributes.
181
181
  # This defaults to +false+.
182
182
  # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
@@ -5,12 +5,13 @@ module ActiveRecord
5
5
  # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
6
  # ActiveRecord::Associations::ThroughAssociationScope
7
7
  class AliasTracker # :nodoc:
8
- attr_reader :aliases, :table_joins
8
+ attr_reader :aliases, :table_joins, :connection
9
9
 
10
10
  # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
- def initialize(table_joins = [])
11
+ def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
12
12
  @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
13
  @table_joins = table_joins
14
+ @connection = connection
14
15
  end
15
16
 
16
17
  def aliased_table_for(table_name, aliased_name = nil)
@@ -70,10 +71,6 @@ module ActiveRecord
70
71
  def truncate(name)
71
72
  name.slice(0, connection.table_alias_length - 2)
72
73
  end
73
-
74
- def connection
75
- ActiveRecord::Base.connection
76
- end
77
74
  end
78
75
  end
79
76
  end
@@ -1,6 +1,5 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
2
  require 'active_support/core_ext/object/inclusion'
3
- require 'active_support/deprecation'
4
3
 
5
4
  module ActiveRecord
6
5
  module Associations
@@ -231,48 +230,10 @@ module ActiveRecord
231
230
  end
232
231
 
233
232
  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
233
+ reflection.build_association(attributes, options) do |record|
234
+ attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
235
+ record.assign_attributes(attributes, :without_protection => true)
273
236
  end
274
-
275
- record
276
237
  end
277
238
  end
278
239
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
 
11
11
  def initialize(association)
12
12
  @association = association
13
- @alias_tracker = AliasTracker.new
13
+ @alias_tracker = AliasTracker.new klass.connection
14
14
  end
15
15
 
16
16
  def scope
@@ -20,31 +20,19 @@ module ActiveRecord
20
20
  # It's okay to just apply all these like this. The options will only be present if the
21
21
  # association supports that option; this is enforced by the association builder.
22
22
  scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset))
23
+ :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
24
24
 
25
25
  if options[:through] && !options[:include]
26
26
  scope = scope.includes(source_options[:include])
27
27
  end
28
28
 
29
- if select = select_value
30
- scope = scope.select(select)
31
- end
29
+ scope = scope.uniq if options[:uniq]
32
30
 
33
31
  add_constraints(scope)
34
32
  end
35
33
 
36
34
  private
37
35
 
38
- def select_value
39
- select_value = options[:select]
40
-
41
- if reflection.collection?
42
- select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
43
- end
44
-
45
- select_value
46
- end
47
-
48
36
  def add_constraints(scope)
49
37
  tables = construct_tables
50
38
 
@@ -16,6 +16,10 @@ module ActiveRecord::Associations::Builder
16
16
  @model, @name, @options = model, name, options
17
17
  end
18
18
 
19
+ def mixin
20
+ @model.generated_feature_methods
21
+ end
22
+
19
23
  def build
20
24
  validate_options
21
25
  reflection = model.create_reflection(self.class.macro, name, options, model)
@@ -36,16 +40,14 @@ module ActiveRecord::Associations::Builder
36
40
 
37
41
  def define_readers
38
42
  name = self.name
39
-
40
- model.redefine_method(name) do |*params|
43
+ mixin.redefine_method(name) do |*params|
41
44
  association(name).reader(*params)
42
45
  end
43
46
  end
44
47
 
45
48
  def define_writers
46
49
  name = self.name
47
-
48
- model.redefine_method("#{name}=") do |value|
50
+ mixin.redefine_method("#{name}=") do |value|
49
51
  association(name).writer(value)
50
52
  end
51
53
  end
@@ -25,14 +25,14 @@ module ActiveRecord::Associations::Builder
25
25
  name = self.name
26
26
 
27
27
  method_name = "belongs_to_counter_cache_after_create_for_#{name}"
28
- model.redefine_method(method_name) do
28
+ mixin.redefine_method(method_name) do
29
29
  record = send(name)
30
30
  record.class.increment_counter(cache_column, record.id) unless record.nil?
31
31
  end
32
32
  model.after_create(method_name)
33
33
 
34
34
  method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
35
- model.redefine_method(method_name) do
35
+ mixin.redefine_method(method_name) do
36
36
  record = send(name)
37
37
  record.class.decrement_counter(cache_column, record.id) unless record.nil?
38
38
  end
@@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder
48
48
  method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
49
49
  touch = options[:touch]
50
50
 
51
- model.redefine_method(method_name) do
51
+ mixin.redefine_method(method_name) do
52
52
  record = send(name)
53
53
 
54
54
  unless record.nil?
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
58
58
  super
59
59
 
60
60
  name = self.name
61
- model.redefine_method("#{name.to_s.singularize}_ids") do
61
+ mixin.redefine_method("#{name.to_s.singularize}_ids") do
62
62
  association(name).ids_reader
63
63
  end
64
64
  end
@@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder
67
67
  super
68
68
 
69
69
  name = self.name
70
- model.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
70
+ mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
71
71
  association(name).ids_writer(ids)
72
72
  end
73
73
  end
@@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder
28
28
 
29
29
  def define_destroy_dependency_method
30
30
  name = self.name
31
- model.send(:define_method, dependency_method_name) do
31
+ mixin.redefine_method(dependency_method_name) do
32
32
  send(name).each do |o|
33
33
  # No point in executing the counter update since we're going to destroy the parent anyway
34
34
  counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
@@ -45,21 +45,21 @@ module ActiveRecord::Associations::Builder
45
45
 
46
46
  def define_delete_all_dependency_method
47
47
  name = self.name
48
- model.send(:define_method, dependency_method_name) do
48
+ mixin.redefine_method(dependency_method_name) do
49
49
  association(name).delete_all_on_destroy
50
50
  end
51
51
  end
52
52
 
53
53
  def define_nullify_dependency_method
54
54
  name = self.name
55
- model.send(:define_method, dependency_method_name) do
55
+ mixin.redefine_method(dependency_method_name) do
56
56
  send(name).delete_all
57
57
  end
58
58
  end
59
59
 
60
60
  def define_restrict_dependency_method
61
61
  name = self.name
62
- model.send(:define_method, dependency_method_name) do
62
+ mixin.redefine_method(dependency_method_name) do
63
63
  raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
64
64
  end
65
65
  end
@@ -44,18 +44,17 @@ module ActiveRecord::Associations::Builder
44
44
  end
45
45
 
46
46
  def define_destroy_dependency_method
47
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
48
- def #{dependency_method_name}
49
- association(#{name.to_sym.inspect}).delete
50
- end
51
- eoruby
47
+ name = self.name
48
+ mixin.redefine_method(dependency_method_name) do
49
+ association(name).delete
50
+ end
52
51
  end
53
52
  alias :define_delete_dependency_method :define_destroy_dependency_method
54
53
  alias :define_nullify_dependency_method :define_destroy_dependency_method
55
54
 
56
55
  def define_restrict_dependency_method
57
56
  name = self.name
58
- model.redefine_method(dependency_method_name) do
57
+ mixin.redefine_method(dependency_method_name) do
59
58
  raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
60
59
  end
61
60
  end
@@ -13,31 +13,18 @@ module ActiveRecord::Associations::Builder
13
13
 
14
14
  private
15
15
 
16
- def define_readers
17
- super
18
- name = self.name
19
-
20
- model.redefine_method("#{name}_loaded?") do
21
- ActiveSupport::Deprecation.warn(
22
- "Calling obj.#{name}_loaded? is deprecated. Please use " \
23
- "obj.association(:#{name}).loaded? instead."
24
- )
25
- association(name).loaded?
26
- end
27
- end
28
-
29
16
  def define_constructors
30
17
  name = self.name
31
18
 
32
- model.redefine_method("build_#{name}") do |*params, &block|
19
+ mixin.redefine_method("build_#{name}") do |*params, &block|
33
20
  association(name).build(*params, &block)
34
21
  end
35
22
 
36
- model.redefine_method("create_#{name}") do |*params, &block|
23
+ mixin.redefine_method("create_#{name}") do |*params, &block|
37
24
  association(name).create(*params, &block)
38
25
  end
39
26
 
40
- model.redefine_method("create_#{name}!") do |*params, &block|
27
+ mixin.redefine_method("create_#{name}!") do |*params, &block|
41
28
  association(name).create!(*params, &block)
42
29
  end
43
30
  end
@@ -49,10 +49,18 @@ module ActiveRecord
49
49
  end
50
50
  else
51
51
  column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
52
+ relation = scoped
52
53
 
53
- scoped.select(column).except(:includes).map! do |record|
54
- record.send(reflection.association_primary_key)
54
+ including = (relation.eager_load_values + relation.includes_values).uniq
55
+
56
+ if including.any?
57
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
58
+ relation = join_dependency.join_associations.inject(relation) do |r, association|
59
+ association.join_relation(r)
60
+ end
55
61
  end
62
+
63
+ relation.pluck(column)
56
64
  end
57
65
  end
58
66
 
@@ -115,22 +123,16 @@ module ActiveRecord
115
123
  create_record(attributes, options, true, &block)
116
124
  end
117
125
 
118
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
126
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
119
127
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
120
128
  def concat(*records)
121
- result = true
122
129
  load_target if owner.new_record?
123
130
 
124
- transaction do
125
- records.flatten.each do |record|
126
- raise_on_type_mismatch(record)
127
- add_to_target(record) do |r|
128
- result &&= insert_record(record) unless owner.new_record?
129
- end
130
- end
131
+ if owner.new_record?
132
+ concat_records(records)
133
+ else
134
+ transaction { concat_records(records) }
131
135
  end
132
-
133
- result && records
134
136
  end
135
137
 
136
138
  # Starts a transaction in the association class's database connection.
@@ -188,6 +190,8 @@ module ActiveRecord
188
190
  # association, it will be used for the query. Otherwise, construct options and pass them with
189
191
  # scope to the target class's +count+.
190
192
  def count(column_name = nil, count_options = {})
193
+ return 0 if owner.new_record?
194
+
191
195
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
192
196
 
193
197
  if options[:counter_sql] || options[:finder_sql]
@@ -248,7 +252,7 @@ module ActiveRecord
248
252
  # This method is abstract in the sense that it relies on
249
253
  # +count_records+, which is a method descendants have to provide.
250
254
  def size
251
- if owner.new_record? || (loaded? && !options[:uniq])
255
+ if !find_target? || (loaded? && !options[:uniq])
252
256
  target.size
253
257
  elsif !loaded? && options[:group]
254
258
  load_target.size
@@ -306,14 +310,10 @@ module ActiveRecord
306
310
  other_array.each { |val| raise_on_type_mismatch(val) }
307
311
  original_target = load_target.dup
308
312
 
309
- transaction do
310
- delete(target - other_array)
311
-
312
- unless concat(other_array - target)
313
- @target = original_target
314
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
315
- "new records could not be saved."
316
- end
313
+ if owner.new_record?
314
+ replace_records(other_array, original_target)
315
+ else
316
+ transaction { replace_records(other_array, original_target) }
317
317
  end
318
318
  end
319
319
 
@@ -364,7 +364,7 @@ module ActiveRecord
364
364
  # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
365
365
  interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
366
366
  count_with = $2.to_s
367
- count_with = '*' if count_with.blank? || count_with =~ /,/
367
+ count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
368
368
  "SELECT #{$1}COUNT(#{count_with}) FROM"
369
369
  end
370
370
  end
@@ -409,7 +409,7 @@ module ActiveRecord
409
409
  if mem_index
410
410
  mem_record = memory.delete_at(mem_index)
411
411
 
412
- (record.attribute_names - mem_record.changes.keys).each do |name|
412
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
413
413
  mem_record[name] = record[name]
414
414
  end
415
415
 
@@ -453,14 +453,20 @@ module ActiveRecord
453
453
  records.each { |record| raise_on_type_mismatch(record) }
454
454
  existing_records = records.reject { |r| r.new_record? }
455
455
 
456
- transaction do
457
- records.each { |record| callback(:before_remove, record) }
456
+ if existing_records.empty?
457
+ remove_records(existing_records, records, method)
458
+ else
459
+ transaction { remove_records(existing_records, records, method) }
460
+ end
461
+ end
458
462
 
459
- delete_records(existing_records, method) if existing_records.any?
460
- records.each { |record| target.delete(record) }
463
+ def remove_records(existing_records, records, method)
464
+ records.each { |record| callback(:before_remove, record) }
461
465
 
462
- records.each { |record| callback(:after_remove, record) }
463
- end
466
+ delete_records(existing_records, method) if existing_records.any?
467
+ records.each { |record| target.delete(record) }
468
+
469
+ records.each { |record| callback(:after_remove, record) }
464
470
  end
465
471
 
466
472
  # Delete the given records from the association, using one of the methods :destroy,
@@ -469,6 +475,31 @@ module ActiveRecord
469
475
  raise NotImplementedError
470
476
  end
471
477
 
478
+ def replace_records(new_target, original_target)
479
+ delete(target - new_target)
480
+
481
+ unless concat(new_target - target)
482
+ @target = original_target
483
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
484
+ "new records could not be saved."
485
+ end
486
+
487
+ target
488
+ end
489
+
490
+ def concat_records(records)
491
+ result = true
492
+
493
+ records.flatten.each do |record|
494
+ raise_on_type_mismatch(record)
495
+ add_to_target(record) do |r|
496
+ result &&= insert_record(record) unless owner.new_record?
497
+ end
498
+ end
499
+
500
+ result && records
501
+ end
502
+
472
503
  def callback(method, record)
473
504
  callbacks_for(method).each do |callback|
474
505
  case callback
@@ -540,7 +571,9 @@ module ActiveRecord
540
571
  args.shift if args.first.is_a?(Hash) && args.first.empty?
541
572
 
542
573
  collection = fetch_first_or_last_using_find?(args) ? scoped : load_target
543
- collection.send(type, *args)
574
+ collection.send(type, *args).tap do |record|
575
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
576
+ end
544
577
  end
545
578
  end
546
579
  end
@@ -1,12 +1,5 @@
1
- require 'active_support/deprecation'
2
-
3
1
  module ActiveRecord
4
2
  module Associations
5
- AssociationCollection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
6
- 'ActiveRecord::Associations::AssociationCollection',
7
- 'ActiveRecord::Associations::CollectionProxy'
8
- )
9
-
10
3
  # Association proxies in Active Record are middlemen between the object that
11
4
  # holds the association, known as the <tt>@owner</tt>, and the actual associated
12
5
  # object, known as the <tt>@target</tt>. The kind of association any proxy is
@@ -46,7 +39,7 @@ module ActiveRecord
46
39
  instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
47
40
 
48
41
  delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
49
- :lock, :readonly, :having, :to => :scoped
42
+ :lock, :readonly, :having, :pluck, :to => :scoped
50
43
 
51
44
  delegate :target, :load_target, :loaded?, :to => :@association
52
45
 
@@ -89,9 +82,8 @@ module ActiveRecord
89
82
  proxy_association.send :add_to_target, r
90
83
  yield(r) if block_given?
91
84
  end
92
- end
93
85
 
94
- if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
86
+ elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
95
87
  if load_target
96
88
  if target.respond_to?(method)
97
89
  target.send(method, *args, &block)
@@ -134,33 +126,6 @@ module ActiveRecord
134
126
  proxy_association.reload
135
127
  self
136
128
  end
137
-
138
- def proxy_owner
139
- ActiveSupport::Deprecation.warn(
140
- "Calling record.#{@association.reflection.name}.proxy_owner is deprecated. Please use " \
141
- "record.association(:#{@association.reflection.name}).owner instead. Or, from an " \
142
- "association extension you can access proxy_association.owner."
143
- )
144
- proxy_association.owner
145
- end
146
-
147
- def proxy_target
148
- ActiveSupport::Deprecation.warn(
149
- "Calling record.#{@association.reflection.name}.proxy_target is deprecated. Please use " \
150
- "record.association(:#{@association.reflection.name}).target instead. Or, from an " \
151
- "association extension you can access proxy_association.target."
152
- )
153
- proxy_association.target
154
- end
155
-
156
- def proxy_reflection
157
- ActiveSupport::Deprecation.warn(
158
- "Calling record.#{@association.reflection.name}.proxy_reflection is deprecated. Please use " \
159
- "record.association(:#{@association.reflection.name}).reflection instead. Or, from an " \
160
- "association extension you can access proxy_association.reflection."
161
- )
162
- proxy_association.reflection
163
- end
164
129
  end
165
130
  end
166
131
  end
@@ -44,6 +44,7 @@ module ActiveRecord
44
44
 
45
45
  def delete_records(records, method)
46
46
  if sql = options[:delete_sql]
47
+ records = load_target if records == :all
47
48
  records.each { |record| owner.connection.delete(interpolate(sql, record)) }
48
49
  else
49
50
  relation = join_table
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  #
24
24
  # If the association has a counter cache it gets that value. Otherwise
25
25
  # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
26
- # there's one. Some configuration options like :group make it impossible
26
+ # there's one. Some configuration options like :group make it impossible
27
27
  # to do an SQL count, in those cases the array count will be used.
28
28
  #
29
29
  # That does not depend on whether the collection has already been loaded
@@ -99,6 +99,10 @@ module ActiveRecord
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ def foreign_key_present?
104
+ owner.attribute_present?(reflection.association_primary_key)
105
+ end
102
106
  end
103
107
  end
104
108
  end