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