activerecord 3.1.12 → 3.2.22.1

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +804 -338
  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 +13 -45
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  11. data/lib/active_record/associations/builder/association.rb +6 -4
  12. data/lib/active_record/associations/builder/belongs_to.rb +7 -4
  13. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +4 -4
  15. data/lib/active_record/associations/builder/has_one.rb +5 -6
  16. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  17. data/lib/active_record/associations/collection_association.rb +65 -32
  18. data/lib/active_record/associations/collection_proxy.rb +8 -41
  19. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  20. data/lib/active_record/associations/has_many_association.rb +11 -7
  21. data/lib/active_record/associations/has_many_through_association.rb +19 -9
  22. data/lib/active_record/associations/has_one_association.rb +23 -13
  23. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  24. data/lib/active_record/associations/join_dependency.rb +3 -3
  25. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  26. data/lib/active_record/associations/preloader.rb +14 -10
  27. data/lib/active_record/associations/through_association.rb +8 -4
  28. data/lib/active_record/associations.rb +92 -76
  29. data/lib/active_record/attribute_assignment.rb +221 -0
  30. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  32. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  33. data/lib/active_record/attribute_methods/read.rb +73 -83
  34. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  36. data/lib/active_record/attribute_methods/write.rb +32 -6
  37. data/lib/active_record/attribute_methods.rb +231 -30
  38. data/lib/active_record/autosave_association.rb +44 -26
  39. data/lib/active_record/base.rb +227 -1708
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  41. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  49. data/lib/active_record/connection_adapters/column.rb +37 -11
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
  51. data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
  53. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  55. data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
  56. data/lib/active_record/counter_cache.rb +9 -4
  57. data/lib/active_record/dynamic_finder_match.rb +12 -0
  58. data/lib/active_record/dynamic_matchers.rb +84 -0
  59. data/lib/active_record/errors.rb +11 -1
  60. data/lib/active_record/explain.rb +86 -0
  61. data/lib/active_record/explain_subscriber.rb +25 -0
  62. data/lib/active_record/fixtures/file.rb +65 -0
  63. data/lib/active_record/fixtures.rb +57 -86
  64. data/lib/active_record/identity_map.rb +3 -4
  65. data/lib/active_record/inheritance.rb +174 -0
  66. data/lib/active_record/integration.rb +60 -0
  67. data/lib/active_record/locking/optimistic.rb +33 -26
  68. data/lib/active_record/locking/pessimistic.rb +23 -1
  69. data/lib/active_record/log_subscriber.rb +8 -4
  70. data/lib/active_record/migration/command_recorder.rb +8 -8
  71. data/lib/active_record/migration.rb +68 -35
  72. data/lib/active_record/model_schema.rb +368 -0
  73. data/lib/active_record/nested_attributes.rb +60 -24
  74. data/lib/active_record/persistence.rb +57 -11
  75. data/lib/active_record/query_cache.rb +6 -6
  76. data/lib/active_record/querying.rb +58 -0
  77. data/lib/active_record/railtie.rb +37 -29
  78. data/lib/active_record/railties/controller_runtime.rb +3 -1
  79. data/lib/active_record/railties/databases.rake +213 -117
  80. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  81. data/lib/active_record/readonly_attributes.rb +26 -0
  82. data/lib/active_record/reflection.rb +7 -15
  83. data/lib/active_record/relation/batches.rb +7 -4
  84. data/lib/active_record/relation/calculations.rb +55 -16
  85. data/lib/active_record/relation/delegation.rb +49 -0
  86. data/lib/active_record/relation/finder_methods.rb +16 -11
  87. data/lib/active_record/relation/predicate_builder.rb +8 -6
  88. data/lib/active_record/relation/query_methods.rb +75 -9
  89. data/lib/active_record/relation/spawn_methods.rb +48 -7
  90. data/lib/active_record/relation.rb +78 -32
  91. data/lib/active_record/result.rb +10 -4
  92. data/lib/active_record/sanitization.rb +194 -0
  93. data/lib/active_record/schema_dumper.rb +12 -5
  94. data/lib/active_record/scoping/default.rb +142 -0
  95. data/lib/active_record/scoping/named.rb +200 -0
  96. data/lib/active_record/scoping.rb +152 -0
  97. data/lib/active_record/serialization.rb +1 -43
  98. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  99. data/lib/active_record/session_store.rb +18 -16
  100. data/lib/active_record/store.rb +52 -0
  101. data/lib/active_record/test_case.rb +11 -7
  102. data/lib/active_record/timestamp.rb +17 -3
  103. data/lib/active_record/transactions.rb +27 -6
  104. data/lib/active_record/translation.rb +22 -0
  105. data/lib/active_record/validations/associated.rb +5 -4
  106. data/lib/active_record/validations/uniqueness.rb +8 -8
  107. data/lib/active_record/validations.rb +1 -1
  108. data/lib/active_record/version.rb +3 -3
  109. data/lib/active_record.rb +38 -3
  110. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  111. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  112. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  113. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  114. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  115. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  116. metadata +49 -28
  117. 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
@@ -47,6 +46,7 @@ module ActiveRecord
47
46
  @loaded = false
48
47
  IdentityMap.remove(target) if IdentityMap.enabled? && target
49
48
  @target = nil
49
+ @stale_state = nil
50
50
  end
51
51
 
52
52
  # Reloads the \target and returns +self+ on success.
@@ -129,16 +129,21 @@ module ActiveRecord
129
129
  # This method is abstract in the sense that it relies on +find_target+,
130
130
  # which is expected to be provided by descendants.
131
131
  #
132
- # If the \target is already \loaded it is just returned. Thus, you can call
133
- # +load_target+ unconditionally to get the \target.
132
+ # If the \target is stale(the target no longer points to the record(s) that the
133
+ # relevant foreign_key(s) refers to.), force reload the \target.
134
+ #
135
+ # Otherwise if the \target is already \loaded it is just returned. Thus, you can
136
+ # call +load_target+ unconditionally to get the \target.
134
137
  #
135
138
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
136
139
  # not reraised. The proxy is \reset and +nil+ is the return value.
137
140
  def load_target
138
- if find_target?
141
+ if (@stale_state && stale_target?) || find_target?
139
142
  begin
140
143
  if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
141
144
  @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
145
+ elsif @stale_state && stale_target?
146
+ @target = find_target
142
147
  end
143
148
  rescue NameError
144
149
  nil
@@ -231,48 +236,11 @@ module ActiveRecord
231
236
  end
232
237
 
233
238
  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
- )
239
+ reflection.build_association(attributes, options) do |record|
240
+ skip_assign = [reflection.foreign_key, reflection.type].compact
241
+ attributes = create_scope.except(*(record.changed - skip_assign))
242
+ record.assign_attributes(attributes, :without_protection => true)
241
243
  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
244
  end
277
245
  end
278
246
  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
 
@@ -72,7 +72,7 @@ module ActiveRecord
72
72
  end
73
73
 
74
74
  def stale_state
75
- owner[reflection.foreign_key].to_s
75
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
76
76
  end
77
77
  end
78
78
  end
@@ -27,7 +27,8 @@ module ActiveRecord
27
27
  end
28
28
 
29
29
  def stale_state
30
- [super, owner[reflection.foreign_type].to_s]
30
+ foreign_key = super
31
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
31
32
  end
32
33
  end
33
34
  end
@@ -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,16 +25,19 @@ 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
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
37
+
38
+ if record && !self.destroyed?
39
+ record.class.decrement_counter(cache_column, record.id)
40
+ end
38
41
  end
39
42
  model.before_destroy(method_name)
40
43
 
@@ -48,7 +51,7 @@ module ActiveRecord::Associations::Builder
48
51
  method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
49
52
  touch = options[:touch]
50
53
 
51
- model.redefine_method(method_name) do
54
+ mixin.redefine_method(method_name) do
52
55
  record = send(name)
53
56
 
54
57
  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
@@ -43,16 +43,24 @@ module ActiveRecord
43
43
 
44
44
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
45
  def ids_reader
46
- if loaded? || options[:finder_sql]
46
+ if owner.new_record? || loaded? || options[:finder_sql]
47
47
  load_target.map do |record|
48
48
  record.send(reflection.association_primary_key)
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