activerecord 3.2.22.5 → 4.2.11.3

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.

Potentially problematic release.


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

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -6,6 +6,30 @@ module ActiveRecord
6
6
  # If the association has a <tt>:through</tt> option further specialization
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
+ include ForeignAssociation
10
+
11
+ def handle_dependency
12
+ case options[:dependent]
13
+ when :restrict_with_exception
14
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
15
+
16
+ when :restrict_with_error
17
+ unless empty?
18
+ record = klass.human_attribute_name(reflection.name).downcase
19
+ owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
20
+ false
21
+ end
22
+
23
+ else
24
+ if options[:dependent] == :destroy
25
+ # No point in executing the counter update since we're going to destroy the parent anyway
26
+ load_target.each { |t| t.destroyed_by_association = reflection }
27
+ destroy_all
28
+ else
29
+ delete_all
30
+ end
31
+ end
32
+ end
9
33
 
10
34
  def insert_record(record, validate = true, raise = false)
11
35
  set_owner_attributes(record)
@@ -18,6 +42,14 @@ module ActiveRecord
18
42
  end
19
43
  end
20
44
 
45
+ def empty?
46
+ if has_cached_counter?
47
+ size.zero?
48
+ else
49
+ super
50
+ end
51
+ end
52
+
21
53
  private
22
54
 
23
55
  # Returns the number of records in this collection.
@@ -35,11 +67,9 @@ module ActiveRecord
35
67
  # the loaded flag is set to true as well.
36
68
  def count_records
37
69
  count = if has_cached_counter?
38
- owner.send(:read_attribute, cached_counter_attribute_name)
39
- elsif options[:counter_sql] || options[:finder_sql]
40
- reflection.klass.count_by_sql(custom_counter_sql)
70
+ owner._read_attribute cached_counter_attribute_name
41
71
  else
42
- scoped.count
72
+ scope.count
43
73
  end
44
74
 
45
75
  # If there's nothing in the database and @target has no new records
@@ -47,23 +77,45 @@ module ActiveRecord
47
77
  # documented side-effect of the method that may avoid an extra SELECT.
48
78
  @target ||= [] and loaded! if count == 0
49
79
 
50
- [options[:limit], count].compact.min
80
+ [association_scope.limit_value, count].compact.min
81
+ end
82
+
83
+
84
+ # Returns whether a counter cache should be used for this association.
85
+ #
86
+ # The counter_cache option must be given on either the owner or inverse
87
+ # association, and the column must be present on the owner.
88
+ def has_cached_counter?(reflection = reflection())
89
+ if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
90
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
91
+ end
51
92
  end
52
93
 
53
- def has_cached_counter?(reflection = self.reflection)
54
- owner.attribute_present?(cached_counter_attribute_name(reflection))
94
+ def cached_counter_attribute_name(reflection = reflection())
95
+ if reflection.options[:counter_cache]
96
+ reflection.options[:counter_cache].to_s
97
+ else
98
+ "#{reflection.name}_count"
99
+ end
55
100
  end
56
101
 
57
- def cached_counter_attribute_name(reflection = self.reflection)
58
- "#{reflection.name}_count"
102
+ def update_counter(difference, reflection = reflection())
103
+ update_counter_in_database(difference, reflection)
104
+ update_counter_in_memory(difference, reflection)
59
105
  end
60
106
 
61
- def update_counter(difference, reflection = self.reflection)
107
+ def update_counter_in_database(difference, reflection = reflection())
62
108
  if has_cached_counter?(reflection)
63
109
  counter = cached_counter_attribute_name(reflection)
64
110
  owner.class.update_counters(owner.id, counter => difference)
111
+ end
112
+ end
113
+
114
+ def update_counter_in_memory(difference, reflection = reflection())
115
+ if counter_must_be_updated_by_has_many?(reflection)
116
+ counter = cached_counter_attribute_name(reflection)
65
117
  owner[counter] += difference
66
- owner.changed_attributes.delete(counter) # eww
118
+ owner.send(:clear_attribute_changes, counter) # eww
67
119
  end
68
120
  end
69
121
 
@@ -77,31 +129,70 @@ module ActiveRecord
77
129
  # it will be decremented twice.
78
130
  #
79
131
  # Hence this method.
80
- def inverse_updates_counter_cache?(reflection = self.reflection)
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
81
133
  counter_name = cached_counter_attribute_name(reflection)
82
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
134
+ inverse_which_updates_counter_named(counter_name, reflection)
135
+ end
136
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
137
+
138
+ def inverse_which_updates_counter_named(counter_name, reflection)
139
+ reflection.klass._reflections.values.find { |inverse_reflection|
140
+ inverse_reflection.belongs_to? &&
83
141
  inverse_reflection.counter_cache_column == counter_name
84
142
  }
85
143
  end
144
+ alias inverse_updates_counter_named? inverse_which_updates_counter_named
145
+
146
+ def inverse_updates_counter_in_memory?(reflection)
147
+ inverse = inverse_which_updates_counter_cache(reflection)
148
+ inverse && inverse == reflection.inverse_of
149
+ end
150
+
151
+ def counter_must_be_updated_by_has_many?(reflection)
152
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
153
+ end
154
+
155
+ def delete_count(method, scope)
156
+ if method == :delete_all
157
+ scope.delete_all
158
+ else
159
+ scope.update_all(reflection.foreign_key => nil)
160
+ end
161
+ end
162
+
163
+ def delete_or_nullify_all_records(method)
164
+ count = delete_count(method, self.scope)
165
+ update_counter(-count)
166
+ end
86
167
 
87
168
  # Deletes the records according to the <tt>:dependent</tt> option.
88
169
  def delete_records(records, method)
89
170
  if method == :destroy
90
- records.each { |r| r.destroy }
171
+ records.each(&:destroy!)
91
172
  update_counter(-records.length) unless inverse_updates_counter_cache?
92
173
  else
93
- scope = self.scoped.where(reflection.klass.primary_key => records)
174
+ scope = self.scope.where(reflection.klass.primary_key => records)
175
+ update_counter(-delete_count(method, scope))
176
+ end
177
+ end
178
+
179
+ def concat_records(records, *)
180
+ update_counter_if_success(super, records.length)
181
+ end
94
182
 
95
- if method == :delete_all
96
- update_counter(-scope.delete_all)
97
- else
98
- update_counter(-scope.update_all(reflection.foreign_key => nil))
99
- end
183
+ def _create_record(attributes, *)
184
+ if attributes.is_a?(Array)
185
+ super
186
+ else
187
+ update_counter_if_success(super, 1)
100
188
  end
101
189
  end
102
190
 
103
- def foreign_key_present?
104
- owner.attribute_present?(reflection.association_primary_key)
191
+ def update_counter_if_success(saved_successfully, difference)
192
+ if saved_successfully
193
+ update_counter_in_memory(difference)
194
+ end
195
+ saved_successfully
105
196
  end
106
197
  end
107
198
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/string/filters'
2
2
 
3
3
  module ActiveRecord
4
4
  # = Active Record Has Many Through Association
@@ -13,31 +13,45 @@ module ActiveRecord
13
13
  @through_association = nil
14
14
  end
15
15
 
16
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
17
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
18
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
19
- # SELECT query if you use #length.
16
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
17
+ # if the collection hasn't been loaded, and by calling collection.size if
18
+ # it has. If the collection will likely have a size greater than zero,
19
+ # and if fetching the collection will be needed afterwards, one less
20
+ # SELECT query will be generated by using #length instead.
20
21
  def size
21
22
  if has_cached_counter?
22
- owner.send(:read_attribute, cached_counter_attribute_name)
23
+ owner._read_attribute cached_counter_attribute_name(reflection)
23
24
  elsif loaded?
24
25
  target.size
25
26
  else
26
- count
27
+ super
27
28
  end
28
29
  end
29
30
 
30
31
  def concat(*records)
31
32
  unless owner.new_record?
32
33
  records.flatten.each do |record|
33
- raise_on_type_mismatch(record)
34
- record.save! if record.new_record?
34
+ raise_on_type_mismatch!(record)
35
35
  end
36
36
  end
37
37
 
38
38
  super
39
39
  end
40
40
 
41
+ def concat_records(records)
42
+ ensure_not_nested
43
+
44
+ records = super(records, true)
45
+
46
+ if owner.new_record? && records
47
+ records.flatten.each do |record|
48
+ build_through_record(record)
49
+ end
50
+ end
51
+
52
+ records
53
+ end
54
+
41
55
  def insert_record(record, validate = true, raise = false)
42
56
  ensure_not_nested
43
57
 
@@ -50,53 +64,71 @@ module ActiveRecord
50
64
  end
51
65
 
52
66
  save_through_record(record)
53
- update_counter(1)
67
+ if has_cached_counter? && !through_reflection_updates_counter_cache?
68
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
69
+ Automatic updating of counter caches on through associations has been
70
+ deprecated, and will be removed in Rails 5. Instead, please set the
71
+ appropriate `counter_cache` options on the `has_many` and `belongs_to`
72
+ for your associations to #{through_reflection.name}.
73
+ MSG
74
+
75
+ update_counter_in_database(1)
76
+ end
54
77
  record
55
78
  end
56
79
 
57
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
58
- # SQL-only implementation.
59
- alias delete_all_on_destroy delete_all
60
-
61
80
  private
62
81
 
63
82
  def through_association
64
83
  @through_association ||= owner.association(through_reflection.name)
65
84
  end
66
85
 
67
- # We temporarily cache through record that has been build, because if we build a
68
- # through record in build_record and then subsequently call insert_record, then we
69
- # want to use the exact same object.
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
70
88
  #
71
- # However, after insert_record has been called, we clear the cache entry because
72
- # we want it to be possible to have multiple instances of the same record in an
73
- # association
89
+ # However, after insert_record has been called, the cache is cleared in
90
+ # order to allow multiple instances of the same record in an association.
74
91
  def build_through_record(record)
75
92
  @through_records[record.object_id] ||= begin
76
93
  ensure_mutable
77
94
 
78
- through_record = through_association.build
95
+ through_record = through_association.build(*options_for_through_record)
79
96
  through_record.send("#{source_reflection.name}=", record)
97
+
98
+ if options[:source_type]
99
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
+ end
101
+
80
102
  through_record
81
103
  end
82
104
  end
83
105
 
106
+ def options_for_through_record
107
+ [through_scope_attributes]
108
+ end
109
+
110
+ def through_scope_attributes
111
+ scope.where_values_hash(through_association.reflection.name.to_s).
112
+ except!(through_association.reflection.foreign_key,
113
+ through_association.reflection.klass.inheritance_column)
114
+ end
115
+
84
116
  def save_through_record(record)
85
117
  build_through_record(record).save!
86
118
  ensure
87
119
  @through_records.delete(record.object_id)
88
120
  end
89
121
 
90
- def build_record(attributes, options = {})
122
+ def build_record(attributes)
91
123
  ensure_not_nested
92
124
 
93
- record = super(attributes, options)
125
+ record = super(attributes)
94
126
 
95
127
  inverse = source_reflection.inverse_of
96
128
  if inverse
97
- if inverse.macro == :has_many
129
+ if inverse.collection?
98
130
  record.send(inverse.name) << build_through_record(record)
99
- elsif inverse.macro == :has_one
131
+ elsif inverse.has_one?
100
132
  record.send("#{inverse.name}=", build_through_record(record))
101
133
  end
102
134
  end
@@ -105,11 +137,7 @@ module ActiveRecord
105
137
  end
106
138
 
107
139
  def target_reflection_has_associated_record?
108
- if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?
109
- false
110
- else
111
- true
112
- end
140
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
113
141
  end
114
142
 
115
143
  def update_through_counter?(method)
@@ -123,14 +151,33 @@ module ActiveRecord
123
151
  end
124
152
  end
125
153
 
154
+ def delete_or_nullify_all_records(method)
155
+ delete_records(load_target, method)
156
+ end
157
+
126
158
  def delete_records(records, method)
127
159
  ensure_not_nested
128
160
 
129
- scope = through_association.scoped.where(construct_join_attributes(*records))
161
+ scope = through_association.scope
162
+ scope.where! construct_join_attributes(*records)
130
163
 
131
164
  case method
132
165
  when :destroy
133
- count = scope.destroy_all.length
166
+ if scope.klass.primary_key
167
+ count = scope.destroy_all.length
168
+ else
169
+ scope.each do |record|
170
+ record._run_destroy_callbacks
171
+ end
172
+
173
+ arel = scope.arel
174
+
175
+ stmt = Arel::DeleteManager.new arel.engine
176
+ stmt.from scope.klass.arel_table
177
+ stmt.wheres = arel.constraints
178
+
179
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
180
+ end
134
181
  when :nullify
135
182
  count = scope.update_all(source_reflection.foreign_key => nil)
136
183
  else
@@ -139,29 +186,33 @@ module ActiveRecord
139
186
 
140
187
  delete_through_records(records)
141
188
 
142
- if source_reflection.options[:counter_cache]
189
+ if source_reflection.options[:counter_cache] && method != :destroy
143
190
  counter = source_reflection.counter_cache_column
144
191
  klass.decrement_counter counter, records.map(&:id)
145
192
  end
146
193
 
147
- if through_reflection.macro == :has_many && update_through_counter?(method)
194
+ if through_reflection.collection? && update_through_counter?(method)
148
195
  update_counter(-count, through_reflection)
196
+ else
197
+ update_counter(-count)
149
198
  end
150
-
151
- update_counter(-count)
152
199
  end
153
200
 
154
201
  def through_records_for(record)
155
202
  attributes = construct_join_attributes(record)
156
203
  candidates = Array.wrap(through_association.target)
157
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
204
+ candidates.find_all do |c|
205
+ attributes.all? do |key, value|
206
+ c.public_send(key) == value
207
+ end
208
+ end
158
209
  end
159
210
 
160
211
  def delete_through_records(records)
161
212
  records.each do |record|
162
213
  through_records = through_records_for(record)
163
214
 
164
- if through_reflection.macro == :has_many
215
+ if through_reflection.collection?
165
216
  through_records.each { |r| through_association.target.delete(r) }
166
217
  else
167
218
  if through_records.include?(through_association.target)
@@ -175,13 +226,22 @@ module ActiveRecord
175
226
 
176
227
  def find_target
177
228
  return [] unless target_reflection_has_associated_record?
178
- scoped.all
229
+ get_records
179
230
  end
180
231
 
181
232
  # NOTE - not sure that we can actually cope with inverses here
182
233
  def invertible_for?(record)
183
234
  false
184
235
  end
236
+
237
+ def has_cached_counter?(reflection = reflection())
238
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
239
+ end
240
+
241
+ def through_reflection_updates_counter_cache?
242
+ counter_name = cached_counter_attribute_name
243
+ inverse_updates_counter_named?(counter_name, through_reflection)
244
+ end
185
245
  end
186
246
  end
187
247
  end
@@ -1,24 +1,44 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Belongs To Has One Association
5
3
  module Associations
6
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
6
+
7
+ def handle_dependency
8
+ case options[:dependent]
9
+ when :restrict_with_exception
10
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
11
+
12
+ when :restrict_with_error
13
+ if load_target
14
+ record = klass.human_attribute_name(reflection.name).downcase
15
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
16
+ false
17
+ end
18
+
19
+ else
20
+ delete
21
+ end
22
+ end
23
+
7
24
  def replace(record, save = true)
8
- raise_on_type_mismatch(record) if record
25
+ raise_on_type_mismatch!(record) if record
9
26
  load_target
10
27
 
11
- # If target and record are nil, or target is equal to record,
12
- # we don't need to have transaction.
13
- if (target || record) && target != record
28
+ return self.target if !(target || record)
29
+
30
+ assigning_another_record = target != record
31
+ if assigning_another_record || record.changed?
32
+ save &&= owner.persisted?
33
+
14
34
  transaction_if(save) do
15
- remove_target!(options[:dependent]) if target && !target.destroyed?
16
-
35
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
36
+
17
37
  if record
18
38
  set_owner_attributes(record)
19
39
  set_inverse_instance(record)
20
-
21
- if owner.persisted? && save && !record.save
40
+
41
+ if save && !record.save
22
42
  nullify_owner_attributes(record)
23
43
  set_owner_attributes(target) if target
24
44
  raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
@@ -38,7 +58,7 @@ module ActiveRecord
38
58
  when :destroy
39
59
  target.destroy
40
60
  when :nullify
41
- target.update_attribute(reflection.foreign_key, nil)
61
+ target.update_columns(reflection.foreign_key => nil)
42
62
  end
43
63
  end
44
64
  end
@@ -54,16 +74,19 @@ module ActiveRecord
54
74
  end
55
75
 
56
76
  def remove_target!(method)
57
- if method.in?([:delete, :destroy])
58
- target.send(method)
59
- else
60
- nullify_owner_attributes(target)
77
+ case method
78
+ when :delete
79
+ target.delete
80
+ when :destroy
81
+ target.destroy
82
+ else
83
+ nullify_owner_attributes(target)
61
84
 
62
- if target.persisted? && owner.persisted? && !target.save
63
- set_owner_attributes(target)
64
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
65
- "The record failed to save when after its foreign key was set to nil."
66
- end
85
+ if target.persisted? && owner.persisted? && !target.save
86
+ set_owner_attributes(target)
87
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
88
+ "The record failed to save after its foreign key was set to nil."
89
+ end
67
90
  end
68
91
  end
69
92
 
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  attributes = construct_join_attributes(record)
24
24
 
25
25
  if through_record
26
- through_record.update_attributes(attributes)
26
+ through_record.update(attributes)
27
27
  elsif owner.new_record?
28
28
  through_proxy.build(attributes)
29
29
  else