activerecord 5.2.3

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