activerecord 3.2.19 → 5.0.0

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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  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 +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,12 +1,10 @@
1
- require 'active_support/core_ext/array/wrap'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Autosave Association
5
3
  #
6
- # +AutosaveAssociation+ is a module that takes care of automatically saving
4
+ # AutosaveAssociation is a module that takes care of automatically saving
7
5
  # associated records when their parent is saved. In addition to saving, it
8
6
  # also destroys any associated records that were marked for destruction.
9
- # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
7
+ # (See #mark_for_destruction and #marked_for_destruction?).
10
8
  #
11
9
  # Saving of the parent, its associations, and the destruction of marked
12
10
  # associations, all happen inside a transaction. This should never leave the
@@ -18,28 +16,27 @@ module ActiveRecord
18
16
  # Note that it also means that associations marked for destruction won't
19
17
  # be destroyed directly. They will however still be marked for destruction.
20
18
  #
21
- # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
22
- # When the <tt>:autosave</tt> option is not present new associations are saved.
19
+ # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
20
+ # When the <tt>:autosave</tt> option is not present then new association records are
21
+ # saved but the updated association records are not saved.
23
22
  #
24
23
  # == Validation
25
24
  #
26
- # Children records are validated unless <tt>:validate</tt> is +false+.
25
+ # Child records are validated unless <tt>:validate</tt> is +false+.
27
26
  #
28
27
  # == Callbacks
29
28
  #
30
29
  # Association with autosave option defines several callbacks on your
31
30
  # model (before_save, after_create, after_update). Please note that
32
31
  # callbacks are executed in the order they were defined in
33
- # model. You should avoid modyfing the association content, before
32
+ # model. You should avoid modifying the association content, before
34
33
  # autosave callbacks are executed. Placing your callbacks after
35
34
  # associations is usually a good practice.
36
35
  #
37
- # == Examples
38
- #
39
36
  # === One-to-one Example
40
37
  #
41
- # class Post
42
- # has_one :author, :autosave => true
38
+ # class Post < ActiveRecord::Base
39
+ # has_one :author, autosave: true
43
40
  # end
44
41
  #
45
42
  # Saving changes to the parent and its associated model can now be performed
@@ -66,167 +63,179 @@ module ActiveRecord
66
63
  # Note that the model is _not_ yet removed from the database:
67
64
  #
68
65
  # id = post.author.id
69
- # Author.find_by_id(id).nil? # => false
66
+ # Author.find_by(id: id).nil? # => false
70
67
  #
71
68
  # post.save
72
69
  # post.reload.author # => nil
73
70
  #
74
71
  # Now it _is_ removed from the database:
75
72
  #
76
- # Author.find_by_id(id).nil? # => true
73
+ # Author.find_by(id: id).nil? # => true
77
74
  #
78
75
  # === One-to-many Example
79
76
  #
80
77
  # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
81
78
  #
82
- # class Post
83
- # has_many :comments # :autosave option is no declared
79
+ # class Post < ActiveRecord::Base
80
+ # has_many :comments # :autosave option is not declared
84
81
  # end
85
82
  #
86
- # post = Post.new(:title => 'ruby rocks')
87
- # post.comments.build(:body => 'hello world')
83
+ # post = Post.new(title: 'ruby rocks')
84
+ # post.comments.build(body: 'hello world')
88
85
  # post.save # => saves both post and comment
89
86
  #
90
- # post = Post.create(:title => 'ruby rocks')
91
- # post.comments.build(:body => 'hello world')
87
+ # post = Post.create(title: 'ruby rocks')
88
+ # post.comments.build(body: 'hello world')
92
89
  # post.save # => saves both post and comment
93
90
  #
94
- # post = Post.create(:title => 'ruby rocks')
95
- # post.comments.create(:body => 'hello world')
91
+ # post = Post.create(title: 'ruby rocks')
92
+ # post.comments.create(body: 'hello world')
96
93
  # post.save # => saves both post and comment
97
94
  #
98
- # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
95
+ # When <tt>:autosave</tt> is true all children are saved, no matter whether they
96
+ # are new records or not:
99
97
  #
100
- # class Post
101
- # has_many :comments, :autosave => true
98
+ # class Post < ActiveRecord::Base
99
+ # has_many :comments, autosave: true
102
100
  # end
103
101
  #
104
- # post = Post.create(:title => 'ruby rocks')
105
- # post.comments.create(:body => 'hello world')
102
+ # post = Post.create(title: 'ruby rocks')
103
+ # post.comments.create(body: 'hello world')
106
104
  # post.comments[0].body = 'hi everyone'
107
- # post.save # => saves both post and comment, with 'hi everyone' as body
105
+ # post.comments.build(body: "good morning.")
106
+ # post.title += "!"
107
+ # post.save # => saves both post and comments.
108
108
  #
109
109
  # Destroying one of the associated models as part of the parent's save action
110
110
  # is as simple as marking it for destruction:
111
111
  #
112
- # post.comments.last.mark_for_destruction
113
- # post.comments.last.marked_for_destruction? # => true
112
+ # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
113
+ # post.comments[1].mark_for_destruction
114
+ # post.comments[1].marked_for_destruction? # => true
114
115
  # post.comments.length # => 2
115
116
  #
116
117
  # Note that the model is _not_ yet removed from the database:
117
118
  #
118
119
  # id = post.comments.last.id
119
- # Comment.find_by_id(id).nil? # => false
120
+ # Comment.find_by(id: id).nil? # => false
120
121
  #
121
122
  # post.save
122
123
  # post.reload.comments.length # => 1
123
124
  #
124
125
  # Now it _is_ removed from the database:
125
126
  #
126
- # Comment.find_by_id(id).nil? # => true
127
-
127
+ # Comment.find_by(id: id).nil? # => true
128
128
  module AutosaveAssociation
129
129
  extend ActiveSupport::Concern
130
130
 
131
- ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
132
-
133
131
  module AssociationBuilderExtension #:nodoc:
134
- def self.included(base)
135
- base.valid_options << :autosave
132
+ def self.build(model, reflection)
133
+ model.send(:add_autosave_association_callbacks, reflection)
136
134
  end
137
135
 
138
- def build
139
- reflection = super
140
- model.send(:add_autosave_association_callbacks, reflection)
141
- reflection
136
+ def self.valid_options
137
+ [ :autosave ]
142
138
  end
143
139
  end
144
140
 
145
141
  included do
146
- ASSOCIATION_TYPES.each do |type|
147
- Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
148
- end
142
+ Associations::Builder::Association.extensions << AssociationBuilderExtension
143
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
144
+ self.index_nested_attribute_errors = false
149
145
  end
150
146
 
151
- module ClassMethods
147
+ module ClassMethods # :nodoc:
152
148
  private
153
149
 
154
- def define_non_cyclic_method(name, reflection, &block)
155
- define_method(name) do |*args|
156
- result = true; @_already_called ||= {}
157
- # Loop prevention for validation of associations
158
- unless @_already_called[[name, reflection.name]]
159
- begin
160
- @_already_called[[name, reflection.name]]=true
161
- result = instance_eval(&block)
162
- ensure
163
- @_already_called[[name, reflection.name]]=false
150
+ def define_non_cyclic_method(name, &block)
151
+ return if method_defined?(name)
152
+ define_method(name) do |*args|
153
+ result = true; @_already_called ||= {}
154
+ # Loop prevention for validation of associations
155
+ unless @_already_called[name]
156
+ begin
157
+ @_already_called[name]=true
158
+ result = instance_eval(&block)
159
+ ensure
160
+ @_already_called[name]=false
161
+ end
164
162
  end
165
- end
166
163
 
167
- result
164
+ result
165
+ end
168
166
  end
169
- end
170
167
 
171
- # Adds validation and save callbacks for the association as specified by
172
- # the +reflection+.
173
- #
174
- # For performance reasons, we don't check whether to validate at runtime.
175
- # However the validation and callback methods are lazy and those methods
176
- # get created when they are invoked for the very first time. However,
177
- # this can change, for instance, when using nested attributes, which is
178
- # called _after_ the association has been defined. Since we don't want
179
- # the callbacks to get defined multiple times, there are guards that
180
- # check if the save or validation methods have already been defined
181
- # before actually defining them.
182
- def add_autosave_association_callbacks(reflection)
183
- save_method = :"autosave_associated_records_for_#{reflection.name}"
184
- validation_method = :"validate_associated_records_for_#{reflection.name}"
185
- collection = reflection.collection?
186
-
187
- unless method_defined?(save_method)
188
- if collection
168
+ # Adds validation and save callbacks for the association as specified by
169
+ # the +reflection+.
170
+ #
171
+ # For performance reasons, we don't check whether to validate at runtime.
172
+ # However the validation and callback methods are lazy and those methods
173
+ # get created when they are invoked for the very first time. However,
174
+ # this can change, for instance, when using nested attributes, which is
175
+ # called _after_ the association has been defined. Since we don't want
176
+ # the callbacks to get defined multiple times, there are guards that
177
+ # check if the save or validation methods have already been defined
178
+ # before actually defining them.
179
+ def add_autosave_association_callbacks(reflection)
180
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
181
+
182
+ if reflection.collection?
189
183
  before_save :before_save_collection_association
190
184
 
191
- define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
185
+ define_non_cyclic_method(save_method) { save_collection_association(reflection) }
192
186
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
193
187
  after_create save_method
194
188
  after_update save_method
189
+ elsif reflection.has_one?
190
+ define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
191
+ # Configures two callbacks instead of a single after_save so that
192
+ # the model may rely on their execution order relative to its
193
+ # own callbacks.
194
+ #
195
+ # For example, given that after_creates run before after_saves, if
196
+ # we configured instead an after_save there would be no way to fire
197
+ # a custom after_create callback after the child association gets
198
+ # created.
199
+ after_create save_method
200
+ after_update save_method
195
201
  else
196
- if reflection.macro == :has_one
197
- define_method(save_method) { save_has_one_association(reflection) }
198
- # Configures two callbacks instead of a single after_save so that
199
- # the model may rely on their execution order relative to its
200
- # own callbacks.
201
- #
202
- # For example, given that after_creates run before after_saves, if
203
- # we configured instead an after_save there would be no way to fire
204
- # a custom after_create callback after the child association gets
205
- # created.
206
- after_create save_method
207
- after_update save_method
208
- else
209
- define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
210
- before_save save_method
211
- end
202
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
203
+ before_save save_method
212
204
  end
205
+
206
+ define_autosave_validation_callbacks(reflection)
213
207
  end
214
208
 
215
- if reflection.validate? && !method_defined?(validation_method)
216
- method = (collection ? :validate_collection_association : :validate_single_association)
217
- define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
218
- validate validation_method
209
+ def define_autosave_validation_callbacks(reflection)
210
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
211
+ if reflection.validate? && !method_defined?(validation_method)
212
+ if reflection.collection?
213
+ method = :validate_collection_association
214
+ else
215
+ method = :validate_single_association
216
+ end
217
+
218
+ define_non_cyclic_method(validation_method) do
219
+ send(method, reflection)
220
+ # TODO: remove the following line as soon as the return value of
221
+ # callbacks is ignored, that is, returning `false` does not
222
+ # display a deprecation warning or halts the callback chain.
223
+ true
224
+ end
225
+ validate validation_method
226
+ after_validation :_ensure_no_duplicate_errors
227
+ end
219
228
  end
220
- end
221
229
  end
222
230
 
223
231
  # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
224
232
  def reload(options = nil)
225
233
  @marked_for_destruction = false
234
+ @destroyed_by_association = nil
226
235
  super
227
236
  end
228
237
 
229
- # Marks this record to be destroyed as part of the parents save transaction.
238
+ # Marks this record to be destroyed as part of the parent's save transaction.
230
239
  # This does _not_ actually destroy the record instantly, rather child record will be destroyed
231
240
  # when <tt>parent.save</tt> is called.
232
241
  #
@@ -235,13 +244,26 @@ module ActiveRecord
235
244
  @marked_for_destruction = true
236
245
  end
237
246
 
238
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
247
+ # Returns whether or not this record will be destroyed as part of the parent's save transaction.
239
248
  #
240
249
  # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
241
250
  def marked_for_destruction?
242
251
  @marked_for_destruction
243
252
  end
244
253
 
254
+ # Records the association that is being destroyed and destroying this
255
+ # record in the process.
256
+ def destroyed_by_association=(reflection)
257
+ @destroyed_by_association = reflection
258
+ end
259
+
260
+ # Returns the association for the parent being destroyed.
261
+ #
262
+ # Used to avoid updating the counter cache unnecessarily.
263
+ def destroyed_by_association
264
+ @destroyed_by_association
265
+ end
266
+
245
267
  # Returns whether or not this record has been changed in any way (including whether
246
268
  # any of its nested autosave associations are likewise changed)
247
269
  def changed_for_autosave?
@@ -250,91 +272,119 @@ module ActiveRecord
250
272
 
251
273
  private
252
274
 
253
- # Returns the record for an association collection that should be validated
254
- # or saved. If +autosave+ is +false+ only new records will be returned,
255
- # unless the parent is/was a new record itself.
256
- def associated_records_to_validate_or_save(association, new_record, autosave)
257
- if new_record
258
- association && association.target
259
- elsif autosave
260
- association.target.find_all { |record| record.changed_for_autosave? }
261
- else
262
- association.target.find_all { |record| record.new_record? }
275
+ # Returns the record for an association collection that should be validated
276
+ # or saved. If +autosave+ is +false+ only new records will be returned,
277
+ # unless the parent is/was a new record itself.
278
+ def associated_records_to_validate_or_save(association, new_record, autosave)
279
+ if new_record
280
+ association && association.target
281
+ elsif autosave
282
+ association.target.find_all(&:changed_for_autosave?)
283
+ else
284
+ association.target.find_all(&:new_record?)
285
+ end
263
286
  end
264
- end
265
287
 
266
- # go through nested autosave associations that are loaded in memory (without loading
267
- # any new ones), and return true if is changed for autosave
268
- def nested_records_changed_for_autosave?
269
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
270
- association = association_instance_get(reflection.name)
271
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
288
+ # go through nested autosave associations that are loaded in memory (without loading
289
+ # any new ones), and return true if is changed for autosave
290
+ def nested_records_changed_for_autosave?
291
+ @_nested_records_changed_for_autosave_already_called ||= false
292
+ return false if @_nested_records_changed_for_autosave_already_called
293
+ begin
294
+ @_nested_records_changed_for_autosave_already_called = true
295
+ self.class._reflections.values.any? do |reflection|
296
+ if reflection.options[:autosave]
297
+ association = association_instance_get(reflection.name)
298
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
299
+ end
300
+ end
301
+ ensure
302
+ @_nested_records_changed_for_autosave_already_called = false
303
+ end
272
304
  end
273
- end
274
305
 
275
- # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
276
- # turned on for the association.
277
- def validate_single_association(reflection)
278
- association = association_instance_get(reflection.name)
279
- record = association && association.reader
280
- association_valid?(reflection, record) if record
281
- end
306
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
307
+ # turned on for the association.
308
+ def validate_single_association(reflection)
309
+ association = association_instance_get(reflection.name)
310
+ record = association && association.reader
311
+ association_valid?(reflection, record) if record
312
+ end
282
313
 
283
- # Validate the associated records if <tt>:validate</tt> or
284
- # <tt>:autosave</tt> is turned on for the association specified by
285
- # +reflection+.
286
- def validate_collection_association(reflection)
287
- if association = association_instance_get(reflection.name)
288
- if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
289
- records.each { |record| association_valid?(reflection, record) }
314
+ # Validate the associated records if <tt>:validate</tt> or
315
+ # <tt>:autosave</tt> is turned on for the association specified by
316
+ # +reflection+.
317
+ def validate_collection_association(reflection)
318
+ if association = association_instance_get(reflection.name)
319
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
320
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
321
+ end
290
322
  end
291
323
  end
292
- end
293
324
 
294
- # Returns whether or not the association is valid and applies any errors to
295
- # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
296
- # enabled records if they're marked_for_destruction? or destroyed.
297
- def association_valid?(reflection, record)
298
- return true if record.destroyed? || record.marked_for_destruction?
299
-
300
- unless valid = record.valid?
301
- if reflection.options[:autosave]
302
- record.errors.each do |attribute, message|
303
- attribute = "#{reflection.name}.#{attribute}"
304
- errors[attribute] << message
305
- errors[attribute].uniq!
325
+ # Returns whether or not the association is valid and applies any errors to
326
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
327
+ # enabled records if they're marked_for_destruction? or destroyed.
328
+ def association_valid?(reflection, record, index=nil)
329
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
330
+
331
+ validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
332
+ unless valid = record.valid?(validation_context)
333
+ if reflection.options[:autosave]
334
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
335
+
336
+ record.errors.each do |attribute, message|
337
+ if indexed_attribute
338
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
339
+ else
340
+ attribute = "#{reflection.name}.#{attribute}"
341
+ end
342
+ errors[attribute] << message
343
+ errors[attribute].uniq!
344
+ end
345
+
346
+ record.errors.details.each_key do |attribute|
347
+ if indexed_attribute
348
+ reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
349
+ else
350
+ reflection_attribute = "#{reflection.name}.#{attribute}"
351
+ end
352
+
353
+ record.errors.details[attribute].each do |error|
354
+ errors.details[reflection_attribute] << error
355
+ errors.details[reflection_attribute].uniq!
356
+ end
357
+ end
358
+ else
359
+ errors.add(reflection.name)
306
360
  end
307
- else
308
- errors.add(reflection.name)
309
361
  end
362
+ valid
310
363
  end
311
- valid
312
- end
313
364
 
314
- # Is used as a before_save callback to check while saving a collection
315
- # association whether or not the parent was a new record before saving.
316
- def before_save_collection_association
317
- @new_record_before_save = new_record?
318
- true
319
- end
365
+ # Is used as a before_save callback to check while saving a collection
366
+ # association whether or not the parent was a new record before saving.
367
+ def before_save_collection_association
368
+ @new_record_before_save = new_record?
369
+ true
370
+ end
320
371
 
321
- # Saves any new associated records, or all loaded autosave associations if
322
- # <tt>:autosave</tt> is enabled on the association.
323
- #
324
- # In addition, it destroys all children that were marked for destruction
325
- # with mark_for_destruction.
326
- #
327
- # This all happens inside a transaction, _if_ the Transactions module is included into
328
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
329
- def save_collection_association(reflection)
330
- if association = association_instance_get(reflection.name)
331
- autosave = reflection.options[:autosave]
332
-
333
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
334
- begin
372
+ # Saves any new associated records, or all loaded autosave associations if
373
+ # <tt>:autosave</tt> is enabled on the association.
374
+ #
375
+ # In addition, it destroys all children that were marked for destruction
376
+ # with #mark_for_destruction.
377
+ #
378
+ # This all happens inside a transaction, _if_ the Transactions module is included into
379
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
380
+ def save_collection_association(reflection)
381
+ if association = association_instance_get(reflection.name)
382
+ autosave = reflection.options[:autosave]
383
+
384
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
335
385
  if autosave
336
386
  records_to_destroy = records.select(&:marked_for_destruction?)
337
- records_to_destroy.each { |record| association.proxy.destroy(record) }
387
+ records_to_destroy.each { |record| association.destroy(record) }
338
388
  records -= records_to_destroy
339
389
  end
340
390
 
@@ -355,71 +405,83 @@ module ActiveRecord
355
405
 
356
406
  raise ActiveRecord::Rollback unless saved
357
407
  end
358
- rescue
359
- records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
360
- raise
361
408
  end
362
- end
363
409
 
364
- # reconstruct the scope now that we know the owner's id
365
- association.reset_scope if association.respond_to?(:reset_scope)
410
+ # reconstruct the scope now that we know the owner's id
411
+ association.reset_scope if association.respond_to?(:reset_scope)
412
+ end
366
413
  end
367
- end
368
414
 
369
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
370
- # on the association.
371
- #
372
- # In addition, it will destroy the association if it was marked for
373
- # destruction with mark_for_destruction.
374
- #
375
- # This all happens inside a transaction, _if_ the Transactions module is included into
376
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
377
- def save_has_one_association(reflection)
378
- association = association_instance_get(reflection.name)
379
- record = association && association.load_target
380
- if record && !record.destroyed?
381
- autosave = reflection.options[:autosave]
382
-
383
- if autosave && record.marked_for_destruction?
384
- record.destroy
385
- else
386
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
387
- if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
388
- unless reflection.through_reflection
389
- record[reflection.foreign_key] = key
390
- end
415
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
416
+ # on the association.
417
+ #
418
+ # In addition, it will destroy the association if it was marked for
419
+ # destruction with #mark_for_destruction.
420
+ #
421
+ # This all happens inside a transaction, _if_ the Transactions module is included into
422
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
423
+ def save_has_one_association(reflection)
424
+ association = association_instance_get(reflection.name)
425
+ record = association && association.load_target
426
+
427
+ if record && !record.destroyed?
428
+ autosave = reflection.options[:autosave]
391
429
 
392
- saved = record.save(:validate => !autosave)
393
- raise ActiveRecord::Rollback if !saved && autosave
394
- saved
430
+ if autosave && record.marked_for_destruction?
431
+ record.destroy
432
+ elsif autosave != false
433
+ key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
434
+
435
+ if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
436
+ unless reflection.through_reflection
437
+ record[reflection.foreign_key] = key
438
+ end
439
+
440
+ saved = record.save(:validate => !autosave)
441
+ raise ActiveRecord::Rollback if !saved && autosave
442
+ saved
443
+ end
395
444
  end
396
445
  end
397
446
  end
398
- end
399
447
 
400
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
401
- #
402
- # In addition, it will destroy the association if it was marked for destruction.
403
- def save_belongs_to_association(reflection)
404
- association = association_instance_get(reflection.name)
405
- record = association && association.load_target
406
- if record && !record.destroyed?
407
- autosave = reflection.options[:autosave]
408
-
409
- if autosave && record.marked_for_destruction?
410
- record.destroy
411
- elsif autosave != false
412
- saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
413
-
414
- if association.updated?
415
- association_id = record.send(reflection.options[:primary_key] || :id)
416
- self[reflection.foreign_key] = association_id
417
- association.loaded!
448
+ # If the record is new or it has changed, returns true.
449
+ def record_changed?(reflection, record, key)
450
+ record.new_record? ||
451
+ (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
452
+ record.attribute_changed?(reflection.foreign_key)
453
+ end
454
+
455
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
456
+ #
457
+ # In addition, it will destroy the association if it was marked for destruction.
458
+ def save_belongs_to_association(reflection)
459
+ association = association_instance_get(reflection.name)
460
+ record = association && association.load_target
461
+ if record && !record.destroyed?
462
+ autosave = reflection.options[:autosave]
463
+
464
+ if autosave && record.marked_for_destruction?
465
+ self[reflection.foreign_key] = nil
466
+ record.destroy
467
+ elsif autosave != false
468
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
469
+
470
+ if association.updated?
471
+ association_id = record.send(reflection.options[:primary_key] || :id)
472
+ self[reflection.foreign_key] = association_id
473
+ association.loaded!
474
+ end
475
+
476
+ saved if autosave
418
477
  end
478
+ end
479
+ end
419
480
 
420
- saved if autosave
481
+ def _ensure_no_duplicate_errors
482
+ errors.messages.each_key do |attribute|
483
+ errors[attribute].uniq!
421
484
  end
422
485
  end
423
- end
424
486
  end
425
487
  end