activerecord 4.1.16 → 4.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -31,6 +31,14 @@ module ActiveRecord
31
31
  @updated
32
32
  end
33
33
 
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
34
42
  private
35
43
 
36
44
  def find_target?
@@ -51,13 +59,15 @@ module ActiveRecord
51
59
  end
52
60
  end
53
61
 
54
- def decrement_counters
55
- with_cache_name { |name| decrement_counter name }
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
56
66
  end
57
67
 
58
- def decrement_counter counter_cache_name
68
+ def increment_counter(counter_cache_name)
59
69
  if foreign_key_present?
60
- klass.decrement_counter(counter_cache_name, target_id)
70
+ klass.increment_counter(counter_cache_name, target_id)
61
71
  end
62
72
  end
63
73
 
@@ -82,7 +92,7 @@ module ActiveRecord
82
92
  # has_one associations.
83
93
  def invertible_for?(record)
84
94
  inverse = inverse_reflection_for(record)
85
- inverse && inverse.macro == :has_one
95
+ inverse && inverse.has_one?
86
96
  end
87
97
 
88
98
  def target_id
@@ -21,7 +21,7 @@ module ActiveRecord::Associations::Builder
21
21
  end
22
22
  self.extensions = []
23
23
 
24
- self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
24
+ self.valid_options = [:class_name, :class, :foreign_key, :validate]
25
25
 
26
26
  attr_reader :name, :scope, :options
27
27
 
@@ -36,6 +36,7 @@ module ActiveRecord::Associations::Builder
36
36
  reflection = builder.build(model)
37
37
  define_accessors model, reflection
38
38
  define_callbacks model, reflection
39
+ define_validations model, reflection
39
40
  builder.define_extensions model
40
41
  reflection
41
42
  end
@@ -85,7 +86,11 @@ module ActiveRecord::Associations::Builder
85
86
  end
86
87
 
87
88
  def self.define_callbacks(model, reflection)
88
- add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
92
+ end
93
+
89
94
  Association.extensions.each do |extension|
90
95
  extension.build model, reflection
91
96
  end
@@ -120,17 +125,23 @@ module ActiveRecord::Associations::Builder
120
125
  CODE
121
126
  end
122
127
 
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
123
132
  def self.valid_dependent_options
124
133
  raise NotImplementedError
125
134
  end
126
135
 
127
136
  private
128
137
 
129
- def self.add_before_destroy_callbacks(model, reflection)
130
- unless valid_dependent_options.include? reflection.options[:dependent]
131
- raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
132
141
  end
142
+ end
133
143
 
144
+ def self.add_destroy_callbacks(model, reflection)
134
145
  name = reflection.name
135
146
  model.before_destroy lambda { |o| o.association(name).handle_dependency }
136
147
  end
@@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder
26
26
  private
27
27
 
28
28
  def self.add_counter_cache_methods(mixin)
29
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
30
30
 
31
31
  mixin.class_eval do
32
- def belongs_to_counter_cache_after_create(reflection)
33
- if record = send(reflection.name)
34
- cache_column = reflection.counter_cache_column
35
- record.class.increment_counter(cache_column, record.id)
36
- @_after_create_counter_called = true
37
- end
38
- end
39
-
40
- def belongs_to_counter_cache_before_destroy(reflection)
41
- foreign_key = reflection.foreign_key.to_sym
42
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
43
- record = send reflection.name
44
- if record && !self.destroyed?
45
- cache_column = reflection.counter_cache_column
46
- record.class.decrement_counter(cache_column, record.id)
47
- end
48
- end
49
- end
50
-
51
32
  def belongs_to_counter_cache_after_update(reflection)
52
33
  foreign_key = reflection.foreign_key
53
34
  cache_column = reflection.counter_cache_column
@@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder
73
54
  def self.add_counter_cache_callbacks(model, reflection)
74
55
  cache_column = reflection.counter_cache_column
75
56
 
76
- model.after_create lambda { |record|
77
- record.belongs_to_counter_cache_after_create(reflection)
78
- }
79
-
80
- model.before_destroy lambda { |record|
81
- record.belongs_to_counter_cache_before_destroy(reflection)
82
- }
83
-
84
57
  model.after_update lambda { |record|
85
58
  record.belongs_to_counter_cache_after_update(reflection)
86
59
  }
@@ -130,9 +103,14 @@ module ActiveRecord::Associations::Builder
130
103
  BelongsTo.touch_record(record, foreign_key, n, touch)
131
104
  }
132
105
 
133
- model.after_save callback
106
+ model.after_save callback, if: :changed?
134
107
  model.after_touch callback
135
108
  model.after_destroy callback
136
109
  end
110
+
111
+ def self.add_destroy_callbacks(model, reflection)
112
+ name = reflection.name
113
+ model.after_destroy lambda { |o| o.association(name).handle_dependency }
114
+ end
137
115
  end
138
116
  end
@@ -72,22 +72,13 @@ module ActiveRecord::Associations::Builder
72
72
  self.right_reflection = _reflect_on_association(rhs_name)
73
73
  end
74
74
 
75
- def hash
76
- object_id.hash
77
- end
78
-
79
- def ==(other)
80
- equal?(other)
81
- end
82
- alias :eql? :==
83
-
84
75
  }
85
76
 
86
77
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
87
78
  join_model.table_name_resolver = habtm
88
79
  join_model.class_resolver = lhs_model
89
80
 
90
- join_model.add_left_association :left_side, anonymous_class: lhs_model
81
+ join_model.add_left_association :left_side, class: lhs_model
91
82
  join_model.add_right_association association_name, belongs_to_options(options)
92
83
  join_model
93
84
  end
@@ -107,7 +98,7 @@ module ActiveRecord::Associations::Builder
107
98
 
108
99
  def middle_options(join_model)
109
100
  middle_options = {}
110
- middle_options[:anonymous_class] = join_model
101
+ middle_options[:class] = join_model
111
102
  middle_options[:source] = join_model.left_reflection.name
112
103
  if options.key? :foreign_key
113
104
  middle_options[:foreign_key] = options[:foreign_key]
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- valid = super + [:order, :as]
8
+ valid = super + [:as]
9
9
  valid += [:through, :source, :source_type] if options[:through]
10
10
  valid
11
11
  end
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
16
16
 
17
17
  private
18
18
 
19
- def self.add_before_destroy_callbacks(model, reflection)
19
+ def self.add_destroy_callbacks(model, reflection)
20
20
  super unless reflection.options[:through]
21
21
  end
22
22
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord::Associations::Builder
4
4
  class SingularAssociation < Association #:nodoc:
5
5
  def valid_options
6
- super + [:remote, :dependent, :primary_key, :inverse_of]
6
+ super + [:dependent, :primary_key, :inverse_of, :required]
7
7
  end
8
8
 
9
9
  def self.define_accessors(model, reflection)
@@ -27,5 +27,12 @@ module ActiveRecord::Associations::Builder
27
27
  end
28
28
  CODE
29
29
  end
30
+
31
+ def self.define_validations(model, reflection)
32
+ super
33
+ if reflection.options[:required]
34
+ model.validates_presence_of reflection.name
35
+ end
36
+ end
30
37
  end
31
38
  end
@@ -33,13 +33,7 @@ module ActiveRecord
33
33
  reload
34
34
  end
35
35
 
36
- if owner.new_record?
37
- # Cache the proxy separately before the owner has an id
38
- # or else a post-save proxy will still lack the id
39
- @new_record_proxy ||= CollectionProxy.create(klass, self)
40
- else
41
- @proxy ||= CollectionProxy.create(klass, self)
42
- end
36
+ @proxy ||= CollectionProxy.create(klass, self)
43
37
  end
44
38
 
45
39
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -61,9 +55,9 @@ module ActiveRecord
61
55
 
62
56
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
57
  def ids_writer(ids)
64
- pk_column = reflection.primary_key_column
58
+ pk_type = reflection.primary_key_type
65
59
  ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_column.type_cast(i) }
60
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
67
61
  replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
68
62
  end
69
63
 
@@ -129,16 +123,6 @@ module ActiveRecord
129
123
  first_nth_or_last(:last, *args)
130
124
  end
131
125
 
132
- def take(n = nil)
133
- if loaded?
134
- n ? target.take(n) : target.first
135
- else
136
- scope.take(n).tap do |record|
137
- set_inverse_instance record if record.is_a? ActiveRecord::Base
138
- end
139
- end
140
- end
141
-
142
126
  def build(attributes = {}, &block)
143
127
  if attributes.is_a?(Array)
144
128
  attributes.collect { |attr| build(attr, &block) }
@@ -161,9 +145,8 @@ module ActiveRecord
161
145
  # be chained. Since << flattens its argument list and inserts each record,
162
146
  # +push+ and +concat+ behave identically.
163
147
  def concat(*records)
164
- load_target if owner.new_record?
165
-
166
148
  if owner.new_record?
149
+ load_target
167
150
  concat_records(records)
168
151
  else
169
152
  transaction { concat_records(records) }
@@ -199,11 +182,11 @@ module ActiveRecord
199
182
  #
200
183
  # See delete for more info.
201
184
  def delete_all(dependent = nil)
202
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
185
+ if dependent && ![:nullify, :delete_all].include?(dependent)
203
186
  raise ArgumentError, "Valid values are :nullify or :delete_all"
204
187
  end
205
188
 
206
- dependent = if dependent.present?
189
+ dependent = if dependent
207
190
  dependent
208
191
  elsif options[:dependent] == :destroy
209
192
  :delete_all
@@ -211,7 +194,7 @@ module ActiveRecord
211
194
  options[:dependent]
212
195
  end
213
196
 
214
- delete(:all, dependent: dependent).tap do
197
+ delete_or_nullify_all_records(dependent).tap do
215
198
  reset
216
199
  loaded!
217
200
  end
@@ -261,19 +244,12 @@ module ActiveRecord
261
244
  # are actually removed from the database, that depends precisely on
262
245
  # +delete_records+. They are in any case removed from the collection.
263
246
  def delete(*records)
247
+ return if records.empty?
264
248
  _options = records.extract_options!
265
249
  dependent = _options[:dependent] || options[:dependent]
266
250
 
267
- if records.first == :all
268
- if (loaded? || dependent == :destroy) && dependent != :delete_all
269
- delete_or_destroy(load_target, dependent)
270
- else
271
- delete_records(:all, dependent)
272
- end
273
- else
274
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
275
- delete_or_destroy(records, dependent)
276
- end
251
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
252
+ delete_or_destroy(records, dependent)
277
253
  end
278
254
 
279
255
  # Deletes the +records+ and removes them from this association calling
@@ -282,6 +258,7 @@ module ActiveRecord
282
258
  # Note that this method removes records from the database ignoring the
283
259
  # +:dependent+ option.
284
260
  def destroy(*records)
261
+ return if records.empty?
285
262
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
286
263
  delete_or_destroy(records, :destroy)
287
264
  end
@@ -375,7 +352,9 @@ module ActiveRecord
375
352
  if owner.new_record?
376
353
  replace_records(other_array, original_target)
377
354
  else
378
- transaction { replace_records(other_array, original_target) }
355
+ if other_array != original_target
356
+ transaction { replace_records(other_array, original_target) }
357
+ end
379
358
  end
380
359
  end
381
360
 
@@ -384,7 +363,7 @@ module ActiveRecord
384
363
  if record.new_record?
385
364
  include_in_memory?(record)
386
365
  else
387
- loaded? ? target.include?(record) : scope.exists?(record)
366
+ loaded? ? target.include?(record) : scope.exists?(record.id)
388
367
  end
389
368
  else
390
369
  false
@@ -427,9 +406,23 @@ module ActiveRecord
427
406
  end
428
407
 
429
408
  private
409
+ def get_records
410
+ return scope.to_a if reflection.scope_chain.any?(&:any?)
411
+
412
+ conn = klass.connection
413
+ sc = reflection.association_scope_cache(conn, owner) do
414
+ StatementCache.create(conn) { |params|
415
+ as = AssociationScope.create { params.bind }
416
+ target_scope.merge as.scope(self, conn)
417
+ }
418
+ end
419
+
420
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
421
+ sc.execute binds, klass, klass.connection
422
+ end
430
423
 
431
424
  def find_target
432
- records = scope.to_a
425
+ records = get_records
433
426
  records.each { |record| set_inverse_instance(record) }
434
427
  records
435
428
  end
@@ -576,13 +569,8 @@ module ActiveRecord
576
569
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
577
570
  assoc = owner.association(reflection.through_reflection.name)
578
571
  assoc.reader.any? { |source|
579
- target_association = source.send(reflection.source_reflection.name)
580
-
581
- if target_association.respond_to?(:include?)
582
- target_association.include?(record)
583
- else
584
- target_association == record
585
- end
572
+ target = source.send(reflection.source_reflection.name)
573
+ target.respond_to?(:include?) ? target.include?(record) : target == record
586
574
  } || target.include?(record)
587
575
  else
588
576
  target.include?(record)
@@ -226,10 +226,6 @@ module ActiveRecord
226
226
  @association.last(*args)
227
227
  end
228
228
 
229
- def take(n = nil)
230
- @association.take(n)
231
- end
232
-
233
229
  # Returns a new object of the collection type that has been instantiated
234
230
  # with +attributes+ and linked to this object, but have not yet been saved.
235
231
  # You can pass an array of attributes hashes, this will return an array
@@ -361,7 +357,7 @@ module ActiveRecord
361
357
 
362
358
  # Deletes all the records from the collection. For +has_many+ associations,
363
359
  # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
364
- # option. Returns an array with the deleted records.
360
+ # option.
365
361
  #
366
362
  # If no <tt>:dependent</tt> option is given, then it will follow the
367
363
  # default strategy. The default strategy is <tt>:nullify</tt>. This
@@ -439,11 +435,6 @@ module ActiveRecord
439
435
  # # ]
440
436
  #
441
437
  # person.pets.delete_all
442
- # # => [
443
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
444
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
445
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
446
- # # ]
447
438
  #
448
439
  # Pet.find(1, 2, 3)
449
440
  # # => ActiveRecord::RecordNotFound
@@ -41,6 +41,14 @@ module ActiveRecord
41
41
  end
42
42
  end
43
43
 
44
+ def empty?
45
+ if has_cached_counter?
46
+ size.zero?
47
+ else
48
+ super
49
+ end
50
+ end
51
+
44
52
  private
45
53
 
46
54
  # Returns the number of records in this collection.
@@ -58,7 +66,7 @@ module ActiveRecord
58
66
  # the loaded flag is set to true as well.
59
67
  def count_records
60
68
  count = if has_cached_counter?
61
- owner.send(:read_attribute, cached_counter_attribute_name)
69
+ owner.read_attribute cached_counter_attribute_name
62
70
  else
63
71
  scope.count
64
72
  end
@@ -80,11 +88,22 @@ module ActiveRecord
80
88
  end
81
89
 
82
90
  def update_counter(difference, reflection = reflection())
91
+ update_counter_in_database(difference, reflection)
92
+ update_counter_in_memory(difference, reflection)
93
+ end
94
+
95
+ def update_counter_in_database(difference, reflection = reflection())
83
96
  if has_cached_counter?(reflection)
84
97
  counter = cached_counter_attribute_name(reflection)
85
98
  owner.class.update_counters(owner.id, counter => difference)
99
+ end
100
+ end
101
+
102
+ def update_counter_in_memory(difference, reflection = reflection())
103
+ if has_cached_counter?(reflection)
104
+ counter = cached_counter_attribute_name(reflection)
86
105
  owner[counter] += difference
87
- owner.changed_attributes.delete(counter) # eww
106
+ owner.send(:clear_attribute_changes, counter) # eww
88
107
  end
89
108
  end
90
109
 
@@ -100,29 +119,37 @@ module ActiveRecord
100
119
  # Hence this method.
101
120
  def inverse_updates_counter_cache?(reflection = reflection())
102
121
  counter_name = cached_counter_attribute_name(reflection)
122
+ inverse_updates_counter_named?(counter_name, reflection)
123
+ end
124
+
125
+ def inverse_updates_counter_named?(counter_name, reflection = reflection())
103
126
  reflection.klass._reflections.values.any? { |inverse_reflection|
104
- :belongs_to == inverse_reflection.macro &&
127
+ inverse_reflection.belongs_to? &&
105
128
  inverse_reflection.counter_cache_column == counter_name
106
129
  }
107
130
  end
108
131
 
132
+ def delete_count(method, scope)
133
+ if method == :delete_all
134
+ scope.delete_all
135
+ else
136
+ scope.update_all(reflection.foreign_key => nil)
137
+ end
138
+ end
139
+
140
+ def delete_or_nullify_all_records(method)
141
+ count = delete_count(method, self.scope)
142
+ update_counter(-count)
143
+ end
144
+
109
145
  # Deletes the records according to the <tt>:dependent</tt> option.
110
146
  def delete_records(records, method)
111
147
  if method == :destroy
112
148
  records.each(&:destroy!)
113
149
  update_counter(-records.length) unless inverse_updates_counter_cache?
114
150
  else
115
- if records == :all || !reflection.klass.primary_key
116
- scope = self.scope
117
- else
118
- scope = self.scope.where(reflection.klass.primary_key => records)
119
- end
120
-
121
- if method == :delete_all
122
- update_counter(-scope.delete_all)
123
- else
124
- update_counter(-scope.update_all(reflection.foreign_key => nil))
125
- end
151
+ scope = self.scope.where(reflection.klass.primary_key => records)
152
+ update_counter(-delete_count(method, scope))
126
153
  end
127
154
  end
128
155
 
@@ -133,6 +160,25 @@ module ActiveRecord
133
160
  false
134
161
  end
135
162
  end
163
+
164
+ def concat_records(records, *)
165
+ update_counter_if_success(super, records.length)
166
+ end
167
+
168
+ def _create_record(attributes, *)
169
+ if attributes.is_a?(Array)
170
+ super
171
+ else
172
+ update_counter_if_success(super, 1)
173
+ end
174
+ end
175
+
176
+ def update_counter_if_success(saved_successfully, difference)
177
+ if saved_successfully
178
+ update_counter_in_memory(difference)
179
+ end
180
+ saved_successfully
181
+ end
136
182
  end
137
183
  end
138
184
  end