activerecord 1.0.0 → 4.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,426 @@
1
+ module ActiveRecord
2
+ # = Active Record Autosave Association
3
+ #
4
+ # +AutosaveAssociation+ is a module that takes care of automatically saving
5
+ # associated records when their parent is saved. In addition to saving, it
6
+ # also destroys any associated records that were marked for destruction.
7
+ # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
8
+ #
9
+ # Saving of the parent, its associations, and the destruction of marked
10
+ # associations, all happen inside a transaction. This should never leave the
11
+ # database in an inconsistent state.
12
+ #
13
+ # If validations for any of the associations fail, their error messages will
14
+ # be applied to the parent.
15
+ #
16
+ # Note that it also means that associations marked for destruction won't
17
+ # be destroyed directly. They will however still be marked for destruction.
18
+ #
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.
22
+ #
23
+ # == Validation
24
+ #
25
+ # Children records are validated unless <tt>:validate</tt> is +false+.
26
+ #
27
+ # == Callbacks
28
+ #
29
+ # Association with autosave option defines several callbacks on your
30
+ # model (before_save, after_create, after_update). Please note that
31
+ # callbacks are executed in the order they were defined in
32
+ # model. You should avoid modifying the association content, before
33
+ # autosave callbacks are executed. Placing your callbacks after
34
+ # associations is usually a good practice.
35
+ #
36
+ # === One-to-one Example
37
+ #
38
+ # class Post
39
+ # has_one :author, autosave: true
40
+ # end
41
+ #
42
+ # Saving changes to the parent and its associated model can now be performed
43
+ # automatically _and_ atomically:
44
+ #
45
+ # post = Post.find(1)
46
+ # post.title # => "The current global position of migrating ducks"
47
+ # post.author.name # => "alloy"
48
+ #
49
+ # post.title = "On the migration of ducks"
50
+ # post.author.name = "Eloy Duran"
51
+ #
52
+ # post.save
53
+ # post.reload
54
+ # post.title # => "On the migration of ducks"
55
+ # post.author.name # => "Eloy Duran"
56
+ #
57
+ # Destroying an associated model, as part of the parent's save action, is as
58
+ # simple as marking it for destruction:
59
+ #
60
+ # post.author.mark_for_destruction
61
+ # post.author.marked_for_destruction? # => true
62
+ #
63
+ # Note that the model is _not_ yet removed from the database:
64
+ #
65
+ # id = post.author.id
66
+ # Author.find_by(id: id).nil? # => false
67
+ #
68
+ # post.save
69
+ # post.reload.author # => nil
70
+ #
71
+ # Now it _is_ removed from the database:
72
+ #
73
+ # Author.find_by(id: id).nil? # => true
74
+ #
75
+ # === One-to-many Example
76
+ #
77
+ # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
78
+ #
79
+ # class Post
80
+ # has_many :comments # :autosave option is not declared
81
+ # end
82
+ #
83
+ # post = Post.new(title: 'ruby rocks')
84
+ # post.comments.build(body: 'hello world')
85
+ # post.save # => saves both post and comment
86
+ #
87
+ # post = Post.create(title: 'ruby rocks')
88
+ # post.comments.build(body: 'hello world')
89
+ # post.save # => saves both post and comment
90
+ #
91
+ # post = Post.create(title: 'ruby rocks')
92
+ # post.comments.create(body: 'hello world')
93
+ # post.save # => saves both post and comment
94
+ #
95
+ # When <tt>:autosave</tt> is true all children are saved, no matter whether they
96
+ # are new records or not:
97
+ #
98
+ # class Post
99
+ # has_many :comments, autosave: true
100
+ # end
101
+ #
102
+ # post = Post.create(title: 'ruby rocks')
103
+ # post.comments.create(body: 'hello world')
104
+ # post.comments[0].body = 'hi everyone'
105
+ # post.save # => saves both post and comment, with 'hi everyone' as body
106
+ #
107
+ # Destroying one of the associated models as part of the parent's save action
108
+ # is as simple as marking it for destruction:
109
+ #
110
+ # post.comments.last.mark_for_destruction
111
+ # post.comments.last.marked_for_destruction? # => true
112
+ # post.comments.length # => 2
113
+ #
114
+ # Note that the model is _not_ yet removed from the database:
115
+ #
116
+ # id = post.comments.last.id
117
+ # Comment.find_by(id: id).nil? # => false
118
+ #
119
+ # post.save
120
+ # post.reload.comments.length # => 1
121
+ #
122
+ # Now it _is_ removed from the database:
123
+ #
124
+ # Comment.find_by(id: id).nil? # => true
125
+
126
+ module AutosaveAssociation
127
+ extend ActiveSupport::Concern
128
+
129
+ module AssociationBuilderExtension #:nodoc:
130
+ def build
131
+ model.send(:add_autosave_association_callbacks, reflection)
132
+ super
133
+ end
134
+ end
135
+
136
+ included do
137
+ Associations::Builder::Association.class_eval do
138
+ self.valid_options << :autosave
139
+ include AssociationBuilderExtension
140
+ end
141
+ end
142
+
143
+ module ClassMethods
144
+ private
145
+
146
+ def define_non_cyclic_method(name, reflection, &block)
147
+ define_method(name) do |*args|
148
+ result = true; @_already_called ||= {}
149
+ # Loop prevention for validation of associations
150
+ unless @_already_called[[name, reflection.name]]
151
+ begin
152
+ @_already_called[[name, reflection.name]]=true
153
+ result = instance_eval(&block)
154
+ ensure
155
+ @_already_called[[name, reflection.name]]=false
156
+ end
157
+ end
158
+
159
+ result
160
+ end
161
+ end
162
+
163
+ # Adds validation and save callbacks for the association as specified by
164
+ # the +reflection+.
165
+ #
166
+ # For performance reasons, we don't check whether to validate at runtime.
167
+ # However the validation and callback methods are lazy and those methods
168
+ # get created when they are invoked for the very first time. However,
169
+ # this can change, for instance, when using nested attributes, which is
170
+ # called _after_ the association has been defined. Since we don't want
171
+ # the callbacks to get defined multiple times, there are guards that
172
+ # check if the save or validation methods have already been defined
173
+ # before actually defining them.
174
+ def add_autosave_association_callbacks(reflection)
175
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
176
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
177
+ collection = reflection.collection?
178
+
179
+ unless method_defined?(save_method)
180
+ if collection
181
+ before_save :before_save_collection_association
182
+
183
+ define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
184
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
185
+ after_create save_method
186
+ after_update save_method
187
+ elsif reflection.macro == :has_one
188
+ define_method(save_method) { save_has_one_association(reflection) }
189
+ # Configures two callbacks instead of a single after_save so that
190
+ # the model may rely on their execution order relative to its
191
+ # own callbacks.
192
+ #
193
+ # For example, given that after_creates run before after_saves, if
194
+ # we configured instead an after_save there would be no way to fire
195
+ # a custom after_create callback after the child association gets
196
+ # created.
197
+ after_create save_method
198
+ after_update save_method
199
+ else
200
+ define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
201
+ before_save save_method
202
+ end
203
+ end
204
+
205
+ if reflection.validate? && !method_defined?(validation_method)
206
+ method = (collection ? :validate_collection_association : :validate_single_association)
207
+ define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
208
+ validate validation_method
209
+ end
210
+ end
211
+ end
212
+
213
+ # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
214
+ def reload(options = nil)
215
+ @marked_for_destruction = false
216
+ @destroyed_by_association = nil
217
+ super
218
+ end
219
+
220
+ # Marks this record to be destroyed as part of the parents save transaction.
221
+ # This does _not_ actually destroy the record instantly, rather child record will be destroyed
222
+ # when <tt>parent.save</tt> is called.
223
+ #
224
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
225
+ def mark_for_destruction
226
+ @marked_for_destruction = true
227
+ end
228
+
229
+ # Returns whether or not this record will be destroyed as part of the parents save transaction.
230
+ #
231
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
232
+ def marked_for_destruction?
233
+ @marked_for_destruction
234
+ end
235
+
236
+ # Records the association that is being destroyed and destroying this
237
+ # record in the process.
238
+ def destroyed_by_association=(reflection)
239
+ @destroyed_by_association = reflection
240
+ end
241
+
242
+ # Returns the association for the parent being destroyed.
243
+ #
244
+ # Used to avoid updating the counter cache unnecessarily.
245
+ def destroyed_by_association
246
+ @destroyed_by_association
247
+ end
248
+
249
+ # Returns whether or not this record has been changed in any way (including whether
250
+ # any of its nested autosave associations are likewise changed)
251
+ def changed_for_autosave?
252
+ new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
253
+ end
254
+
255
+ private
256
+
257
+ # Returns the record for an association collection that should be validated
258
+ # or saved. If +autosave+ is +false+ only new records will be returned,
259
+ # unless the parent is/was a new record itself.
260
+ def associated_records_to_validate_or_save(association, new_record, autosave)
261
+ if new_record
262
+ association && association.target
263
+ elsif autosave
264
+ association.target.find_all { |record| record.changed_for_autosave? }
265
+ else
266
+ association.target.find_all { |record| record.new_record? }
267
+ end
268
+ end
269
+
270
+ # go through nested autosave associations that are loaded in memory (without loading
271
+ # any new ones), and return true if is changed for autosave
272
+ def nested_records_changed_for_autosave?
273
+ self.class.reflect_on_all_autosave_associations.any? do |reflection|
274
+ association = association_instance_get(reflection.name)
275
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
276
+ end
277
+ end
278
+
279
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
280
+ # turned on for the association.
281
+ def validate_single_association(reflection)
282
+ association = association_instance_get(reflection.name)
283
+ record = association && association.reader
284
+ association_valid?(reflection, record) if record
285
+ end
286
+
287
+ # Validate the associated records if <tt>:validate</tt> or
288
+ # <tt>:autosave</tt> is turned on for the association specified by
289
+ # +reflection+.
290
+ def validate_collection_association(reflection)
291
+ if association = association_instance_get(reflection.name)
292
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
293
+ records.each { |record| association_valid?(reflection, record) }
294
+ end
295
+ end
296
+ end
297
+
298
+ # Returns whether or not the association is valid and applies any errors to
299
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
300
+ # enabled records if they're marked_for_destruction? or destroyed.
301
+ def association_valid?(reflection, record)
302
+ return true if record.destroyed? || record.marked_for_destruction?
303
+
304
+ unless valid = record.valid?
305
+ if reflection.options[:autosave]
306
+ record.errors.each do |attribute, message|
307
+ attribute = "#{reflection.name}.#{attribute}"
308
+ errors[attribute] << message
309
+ errors[attribute].uniq!
310
+ end
311
+ else
312
+ errors.add(reflection.name)
313
+ end
314
+ end
315
+ valid
316
+ end
317
+
318
+ # Is used as a before_save callback to check while saving a collection
319
+ # association whether or not the parent was a new record before saving.
320
+ def before_save_collection_association
321
+ @new_record_before_save = new_record?
322
+ true
323
+ end
324
+
325
+ # Saves any new associated records, or all loaded autosave associations if
326
+ # <tt>:autosave</tt> is enabled on the association.
327
+ #
328
+ # In addition, it destroys all children that were marked for destruction
329
+ # with mark_for_destruction.
330
+ #
331
+ # This all happens inside a transaction, _if_ the Transactions module is included into
332
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
333
+ def save_collection_association(reflection)
334
+ if association = association_instance_get(reflection.name)
335
+ autosave = reflection.options[:autosave]
336
+
337
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
338
+ records_to_destroy = []
339
+ records.each do |record|
340
+ next if record.destroyed?
341
+
342
+ saved = true
343
+
344
+ if autosave && record.marked_for_destruction?
345
+ records_to_destroy << record
346
+ elsif autosave != false && (@new_record_before_save || record.new_record?)
347
+ if autosave
348
+ saved = association.insert_record(record, false)
349
+ else
350
+ association.insert_record(record) unless reflection.nested?
351
+ end
352
+ elsif autosave
353
+ saved = record.save(:validate => false)
354
+ end
355
+
356
+ raise ActiveRecord::Rollback unless saved
357
+ end
358
+
359
+ records_to_destroy.each do |record|
360
+ association.destroy(record)
361
+ end
362
+ end
363
+
364
+ # reconstruct the scope now that we know the owner's id
365
+ association.reset_scope if association.respond_to?(:reset_scope)
366
+ end
367
+ end
368
+
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
391
+
392
+ saved = record.save(:validate => !autosave)
393
+ raise ActiveRecord::Rollback if !saved && autosave
394
+ saved
395
+ end
396
+ end
397
+ end
398
+ end
399
+
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
+ self[reflection.foreign_key] = nil
411
+ record.destroy
412
+ elsif autosave != false
413
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
414
+
415
+ if association.updated?
416
+ association_id = record.send(reflection.options[:primary_key] || :id)
417
+ self[reflection.foreign_key] = association_id
418
+ association.loaded!
419
+ end
420
+
421
+ saved if autosave
422
+ end
423
+ end
424
+ end
425
+ end
426
+ end