activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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