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,8 +1,6 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/object/try'
3
- require 'active_support/core_ext/object/blank'
4
3
  require 'active_support/core_ext/hash/indifferent_access'
5
- require 'active_support/core_ext/class/attribute'
6
4
 
7
5
  module ActiveRecord
8
6
  module NestedAttributes #:nodoc:
@@ -12,17 +10,17 @@ module ActiveRecord
12
10
  extend ActiveSupport::Concern
13
11
 
14
12
  included do
15
- class_attribute :nested_attributes_options, :instance_writer => false
13
+ class_attribute :nested_attributes_options, instance_writer: false
16
14
  self.nested_attributes_options = {}
17
15
  end
18
16
 
19
17
  # = Active Record Nested Attributes
20
18
  #
21
19
  # Nested attributes allow you to save attributes on associated records
22
- # through the parent. By default nested attribute updating is turned off,
23
- # you can enable it using the accepts_nested_attributes_for class method.
24
- # When you enable nested attributes an attribute writer is defined on
25
- # the model.
20
+ # through the parent. By default nested attribute updating is turned off
21
+ # and you can enable it using the accepts_nested_attributes_for class
22
+ # method. When you enable nested attributes an attribute writer is
23
+ # defined on the model.
26
24
  #
27
25
  # The attribute writer is named after the association, which means that
28
26
  # in the following example, two new methods are added to your model:
@@ -52,15 +50,15 @@ module ActiveRecord
52
50
  # Enabling nested attributes on a one-to-one association allows you to
53
51
  # create the member and avatar in one go:
54
52
  #
55
- # params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } }
53
+ # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
56
54
  # member = Member.create(params[:member])
57
55
  # member.avatar.id # => 2
58
56
  # member.avatar.icon # => 'smiling'
59
57
  #
60
58
  # It also allows you to update the avatar through the member:
61
59
  #
62
- # params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
63
- # member.update_attributes params[:member]
60
+ # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
61
+ # member.update params[:member]
64
62
  # member.avatar.icon # => 'sad'
65
63
  #
66
64
  # By default you will only be able to set and update attributes on the
@@ -70,13 +68,13 @@ module ActiveRecord
70
68
  #
71
69
  # class Member < ActiveRecord::Base
72
70
  # has_one :avatar
73
- # accepts_nested_attributes_for :avatar, :allow_destroy => true
71
+ # accepts_nested_attributes_for :avatar, allow_destroy: true
74
72
  # end
75
73
  #
76
74
  # Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
77
75
  # value that evaluates to +true+, you will destroy the associated model:
78
76
  #
79
- # member.avatar_attributes = { :id => '2', :_destroy => '1' }
77
+ # member.avatar_attributes = { id: '2', _destroy: '1' }
80
78
  # member.avatar.marked_for_destruction? # => true
81
79
  # member.save
82
80
  # member.reload.avatar # => nil
@@ -92,22 +90,23 @@ module ActiveRecord
92
90
  # accepts_nested_attributes_for :posts
93
91
  # end
94
92
  #
95
- # You can now set or update attributes on an associated post model through
96
- # the attribute hash.
93
+ # You can now set or update attributes on the associated posts through
94
+ # an attribute hash for a member: include the key +:posts_attributes+
95
+ # with an array of hashes of post attributes as a value.
97
96
  #
98
97
  # For each hash that does _not_ have an <tt>id</tt> key a new record will
99
98
  # be instantiated, unless the hash also contains a <tt>_destroy</tt> key
100
99
  # that evaluates to +true+.
101
100
  #
102
- # params = { :member => {
103
- # :name => 'joe', :posts_attributes => [
104
- # { :title => 'Kari, the awesome Ruby documentation browser!' },
105
- # { :title => 'The egalitarian assumption of the modern citizen' },
106
- # { :title => '', :_destroy => '1' } # this will be ignored
101
+ # params = { member: {
102
+ # name: 'joe', posts_attributes: [
103
+ # { title: 'Kari, the awesome Ruby documentation browser!' },
104
+ # { title: 'The egalitarian assumption of the modern citizen' },
105
+ # { title: '', _destroy: '1' } # this will be ignored
107
106
  # ]
108
107
  # }}
109
108
  #
110
- # member = Member.create(params['member'])
109
+ # member = Member.create(params[:member])
111
110
  # member.posts.length # => 2
112
111
  # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
113
112
  # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
@@ -116,48 +115,48 @@ module ActiveRecord
116
115
  # hashes if they fail to pass your criteria. For example, the previous
117
116
  # example could be rewritten as:
118
117
  #
119
- # class Member < ActiveRecord::Base
120
- # has_many :posts
121
- # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
122
- # end
118
+ # class Member < ActiveRecord::Base
119
+ # has_many :posts
120
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
121
+ # end
123
122
  #
124
- # params = { :member => {
125
- # :name => 'joe', :posts_attributes => [
126
- # { :title => 'Kari, the awesome Ruby documentation browser!' },
127
- # { :title => 'The egalitarian assumption of the modern citizen' },
128
- # { :title => '' } # this will be ignored because of the :reject_if proc
123
+ # params = { member: {
124
+ # name: 'joe', posts_attributes: [
125
+ # { title: 'Kari, the awesome Ruby documentation browser!' },
126
+ # { title: 'The egalitarian assumption of the modern citizen' },
127
+ # { title: '' } # this will be ignored because of the :reject_if proc
129
128
  # ]
130
129
  # }}
131
130
  #
132
- # member = Member.create(params['member'])
131
+ # member = Member.create(params[:member])
133
132
  # member.posts.length # => 2
134
133
  # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
135
134
  # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
136
135
  #
137
136
  # Alternatively, :reject_if also accepts a symbol for using methods:
138
137
  #
139
- # class Member < ActiveRecord::Base
140
- # has_many :posts
141
- # accepts_nested_attributes_for :posts, :reject_if => :new_record?
142
- # end
138
+ # class Member < ActiveRecord::Base
139
+ # has_many :posts
140
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
141
+ # end
143
142
  #
144
- # class Member < ActiveRecord::Base
145
- # has_many :posts
146
- # accepts_nested_attributes_for :posts, :reject_if => :reject_posts
143
+ # class Member < ActiveRecord::Base
144
+ # has_many :posts
145
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
147
146
  #
148
- # def reject_posts(attributed)
149
- # attributed['title'].blank?
150
- # end
151
- # end
147
+ # def reject_posts(attributed)
148
+ # attributed['title'].blank?
149
+ # end
150
+ # end
152
151
  #
153
152
  # If the hash contains an <tt>id</tt> key that matches an already
154
153
  # associated record, the matching record will be modified:
155
154
  #
156
155
  # member.attributes = {
157
- # :name => 'Joe',
158
- # :posts_attributes => [
159
- # { :id => 1, :title => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
160
- # { :id => 2, :title => '[UPDATED] other post' }
156
+ # name: 'Joe',
157
+ # posts_attributes: [
158
+ # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
159
+ # { id: 2, title: '[UPDATED] other post' }
161
160
  # ]
162
161
  # }
163
162
  #
@@ -172,37 +171,48 @@ module ActiveRecord
172
171
  #
173
172
  # class Member < ActiveRecord::Base
174
173
  # has_many :posts
175
- # accepts_nested_attributes_for :posts, :allow_destroy => true
174
+ # accepts_nested_attributes_for :posts, allow_destroy: true
176
175
  # end
177
176
  #
178
- # params = { :member => {
179
- # :posts_attributes => [{ :id => '2', :_destroy => '1' }]
177
+ # params = { member: {
178
+ # posts_attributes: [{ id: '2', _destroy: '1' }]
180
179
  # }}
181
180
  #
182
- # member.attributes = params['member']
181
+ # member.attributes = params[:member]
183
182
  # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
184
183
  # member.posts.length # => 2
185
184
  # member.save
186
185
  # member.reload.posts.length # => 1
187
186
  #
188
- # === Saving
187
+ # Nested attributes for an associated collection can also be passed in
188
+ # the form of a hash of hashes instead of an array of hashes:
189
189
  #
190
- # All changes to models, including the destruction of those marked for
191
- # destruction, are saved and destroyed automatically and atomically when
192
- # the parent model is saved. This happens inside the transaction initiated
193
- # by the parents save method. See ActiveRecord::AutosaveAssociation.
190
+ # Member.create(name: 'joe',
191
+ # posts_attributes: { first: { title: 'Foo' },
192
+ # second: { title: 'Bar' } })
194
193
  #
195
- # === Using with attr_accessible
194
+ # has the same effect as
196
195
  #
197
- # The use of <tt>attr_accessible</tt> can interfere with nested attributes
198
- # if you're not careful. For example, if the <tt>Member</tt> model above
199
- # was using <tt>attr_accessible</tt> like this:
196
+ # Member.create(name: 'joe',
197
+ # posts_attributes: [ { title: 'Foo' },
198
+ # { title: 'Bar' } ])
200
199
  #
201
- # attr_accessible :name
200
+ # The keys of the hash which is the value for +:posts_attributes+ are
201
+ # ignored in this case.
202
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
203
+ # such keys, otherwise the hash will be wrapped in an array and
204
+ # interpreted as an attribute hash for a single post.
202
205
  #
203
- # You would need to modify it to look like this:
206
+ # Passing attributes for an associated collection in the form of a hash
207
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
208
+ # where there maybe no natural way to submit an array of hashes.
209
+ #
210
+ # === Saving
204
211
  #
205
- # attr_accessible :name, :posts_attributes
212
+ # All changes to models, including the destruction of those marked for
213
+ # destruction, are saved and destroyed automatically and atomically when
214
+ # the parent model is saved. This happens inside the transaction initiated
215
+ # by the parents save method. See ActiveRecord::AutosaveAssociation.
206
216
  #
207
217
  # === Validating the presence of a parent model
208
218
  #
@@ -211,20 +221,39 @@ module ActiveRecord
211
221
  # <tt>inverse_of</tt> as this example illustrates:
212
222
  #
213
223
  # class Member < ActiveRecord::Base
214
- # has_many :posts, :inverse_of => :member
224
+ # has_many :posts, inverse_of: :member
215
225
  # accepts_nested_attributes_for :posts
216
226
  # end
217
227
  #
218
228
  # class Post < ActiveRecord::Base
219
- # belongs_to :member, :inverse_of => :posts
229
+ # belongs_to :member, inverse_of: :posts
220
230
  # validates_presence_of :member
221
231
  # end
232
+ #
233
+ # Note that if you do not specify the <tt>inverse_of</tt> option, then
234
+ # Active Record will try to automatically guess the inverse association
235
+ # based on heuristics.
236
+ #
237
+ # For one-to-one nested associations, if you build the new (in-memory)
238
+ # child object yourself before assignment, then this module will not
239
+ # overwrite it, e.g.:
240
+ #
241
+ # class Member < ActiveRecord::Base
242
+ # has_one :avatar
243
+ # accepts_nested_attributes_for :avatar
244
+ #
245
+ # def avatar
246
+ # super || build_avatar(width: 200)
247
+ # end
248
+ # end
249
+ #
250
+ # member = Member.new
251
+ # member.avatar_attributes = {icon: 'sad'}
252
+ # member.avatar.width # => 200
222
253
  module ClassMethods
223
- REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
254
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
224
255
 
225
- # Defines an attributes writer for the specified association(s). If you
226
- # are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
227
- # will need to add the attribute writer to the allowed list.
256
+ # Defines an attributes writer for the specified association(s).
228
257
  #
229
258
  # Supported options:
230
259
  # [:allow_destroy]
@@ -239,26 +268,36 @@ module ActiveRecord
239
268
  # is specified, a record will be built for all attribute hashes that
240
269
  # do not have a <tt>_destroy</tt> value that evaluates to true.
241
270
  # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
242
- # that will reject a record where all the attributes are blank.
271
+ # that will reject a record where all the attributes are blank excluding
272
+ # any value for _destroy.
243
273
  # [:limit]
244
274
  # Allows you to specify the maximum number of the associated records that
245
- # can be processed with the nested attributes. If the size of the
275
+ # can be processed with the nested attributes. Limit also can be specified as a
276
+ # Proc or a Symbol pointing to a method that should return number. If the size of the
246
277
  # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
247
278
  # exception is raised. If omitted, any number associations can be processed.
248
279
  # Note that the :limit option is only applicable to one-to-many associations.
249
280
  # [:update_only]
250
- # Allows you to specify that an existing record may only be updated.
251
- # A new record may only be created when there is no existing record.
252
- # This option only works for one-to-one associations and is ignored for
253
- # collection associations. This option is off by default.
281
+ # For a one-to-one association, this option allows you to specify how
282
+ # nested attributes are to be used when an associated record already
283
+ # exists. In general, an existing record may either be updated with the
284
+ # new set of attribute values or be replaced by a wholly new record
285
+ # containing those values. By default the :update_only option is +false+
286
+ # and the nested attributes are used to update the existing record only
287
+ # if they include the record's <tt>:id</tt> value. Otherwise a new
288
+ # record will be instantiated and used to replace the existing one.
289
+ # However if the :update_only option is +true+, the nested attributes
290
+ # are used to update the record's attributes always, regardless of
291
+ # whether the <tt>:id</tt> is present. The option is ignored for collection
292
+ # associations.
254
293
  #
255
294
  # Examples:
256
295
  # # creates avatar_attributes=
257
- # accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
296
+ # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
258
297
  # # creates avatar_attributes=
259
- # accepts_nested_attributes_for :avatar, :reject_if => :all_blank
298
+ # accepts_nested_attributes_for :avatar, reject_if: :all_blank
260
299
  # # creates avatar_attributes= and posts_attributes=
261
- # accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
300
+ # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
262
301
  def accepts_nested_attributes_for(*attr_names)
263
302
  options = { :allow_destroy => false, :update_only => false }
264
303
  options.update(attr_names.extract_options!)
@@ -266,32 +305,45 @@ module ActiveRecord
266
305
  options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
267
306
 
268
307
  attr_names.each do |association_name|
269
- if reflection = reflect_on_association(association_name)
270
- reflection.options[:autosave] = true
271
- add_autosave_association_callbacks(reflection)
308
+ if reflection = _reflect_on_association(association_name)
309
+ reflection.autosave = true
310
+ define_autosave_validation_callbacks(reflection)
272
311
 
273
312
  nested_attributes_options = self.nested_attributes_options.dup
274
313
  nested_attributes_options[association_name.to_sym] = options
275
314
  self.nested_attributes_options = nested_attributes_options
276
315
 
277
316
  type = (reflection.collection? ? :collection : :one_to_one)
278
-
279
- # def pirate_attributes=(attributes)
280
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
281
- # end
282
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
283
- if method_defined?(:#{association_name}_attributes=)
284
- remove_method(:#{association_name}_attributes=)
285
- end
286
- def #{association_name}_attributes=(attributes)
287
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
288
- end
289
- eoruby
317
+ generate_association_writer(association_name, type)
290
318
  else
291
319
  raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
292
320
  end
293
321
  end
294
322
  end
323
+
324
+ private
325
+
326
+ # Generates a writer method for this association. Serves as a point for
327
+ # accessing the objects in the association. For example, this method
328
+ # could generate the following:
329
+ #
330
+ # def pirate_attributes=(attributes)
331
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
332
+ # end
333
+ #
334
+ # This redirects the attempts to write objects in an association through
335
+ # the helper methods defined below. Makes it seem like the nested
336
+ # associations are just regular associations.
337
+ def generate_association_writer(association_name, type)
338
+ generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
339
+ if method_defined?(:#{association_name}_attributes=)
340
+ remove_method(:#{association_name}_attributes=)
341
+ end
342
+ def #{association_name}_attributes=(attributes)
343
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
344
+ end
345
+ eoruby
346
+ end
295
347
  end
296
348
 
297
349
  # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
@@ -311,31 +363,42 @@ module ActiveRecord
311
363
 
312
364
  # Assigns the given attributes to the association.
313
365
  #
314
- # If update_only is false and the given attributes include an <tt>:id</tt>
315
- # that matches the existing record's id, then the existing record will be
316
- # modified. If update_only is true, a new record is only created when no
317
- # object exists. Otherwise a new record will be built.
366
+ # If an associated record does not yet exist, one will be instantiated. If
367
+ # an associated record already exists, the method's behavior depends on
368
+ # the value of the update_only option. If update_only is +false+ and the
369
+ # given attributes include an <tt>:id</tt> that matches the existing record's
370
+ # id, then the existing record will be modified. If no <tt>:id</tt> is provided
371
+ # it will be replaced with a new record. If update_only is +true+ the existing
372
+ # record will be modified regardless of whether an <tt>:id</tt> is provided.
318
373
  #
319
374
  # If the given attributes include a matching <tt>:id</tt> attribute, or
320
375
  # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
321
376
  # then the existing record will be marked for destruction.
322
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
377
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
323
378
  options = self.nested_attributes_options[association_name]
324
379
  attributes = attributes.with_indifferent_access
380
+ existing_record = send(association_name)
325
381
 
326
- if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
327
- (options[:update_only] || record.id.to_s == attributes['id'].to_s)
328
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
382
+ if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
383
+ (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
384
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
329
385
 
330
- elsif attributes['id'].present? && !assignment_opts[:without_protection]
331
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
386
+ elsif attributes['id'].present?
387
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
332
388
 
333
389
  elsif !reject_new_record?(association_name, attributes)
334
- method = "build_#{association_name}"
335
- if respond_to?(method)
336
- send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
390
+ assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
391
+
392
+ if existing_record && existing_record.new_record?
393
+ existing_record.assign_attributes(assignable_attributes)
394
+ association(association_name).initialize_attributes(existing_record)
337
395
  else
338
- raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
396
+ method = "build_#{association_name}"
397
+ if respond_to?(method)
398
+ send(method, assignable_attributes)
399
+ else
400
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
401
+ end
339
402
  end
340
403
  end
341
404
  end
@@ -351,37 +414,35 @@ module ActiveRecord
351
414
  # For example:
352
415
  #
353
416
  # assign_nested_attributes_for_collection_association(:people, {
354
- # '1' => { :id => '1', :name => 'Peter' },
355
- # '2' => { :name => 'John' },
356
- # '3' => { :id => '2', :_destroy => true }
417
+ # '1' => { id: '1', name: 'Peter' },
418
+ # '2' => { name: 'John' },
419
+ # '3' => { id: '2', _destroy: true }
357
420
  # })
358
421
  #
359
422
  # Will update the name of the Person with ID 1, build a new associated
360
- # person with the name `John', and mark the associated Person with ID 2
423
+ # person with the name 'John', and mark the associated Person with ID 2
361
424
  # for destruction.
362
425
  #
363
426
  # Also accepts an Array of attribute hashes:
364
427
  #
365
428
  # assign_nested_attributes_for_collection_association(:people, [
366
- # { :id => '1', :name => 'Peter' },
367
- # { :name => 'John' },
368
- # { :id => '2', :_destroy => true }
429
+ # { id: '1', name: 'Peter' },
430
+ # { name: 'John' },
431
+ # { id: '2', _destroy: true }
369
432
  # ])
370
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
433
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
371
434
  options = self.nested_attributes_options[association_name]
372
435
 
373
436
  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
374
437
  raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
375
438
  end
376
439
 
377
- if options[:limit] && attributes_collection.size > options[:limit]
378
- raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
379
- end
440
+ check_record_limit!(options[:limit], attributes_collection)
380
441
 
381
442
  if attributes_collection.is_a? Hash
382
443
  keys = attributes_collection.keys
383
444
  attributes_collection = if keys.include?('id') || keys.include?(:id)
384
- Array.wrap(attributes_collection)
445
+ [attributes_collection]
385
446
  else
386
447
  attributes_collection.values
387
448
  end
@@ -393,7 +454,7 @@ module ActiveRecord
393
454
  association.target
394
455
  else
395
456
  attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
396
- attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
457
+ attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
397
458
  end
398
459
 
399
460
  attributes_collection.each do |attributes|
@@ -401,54 +462,78 @@ module ActiveRecord
401
462
 
402
463
  if attributes['id'].blank?
403
464
  unless reject_new_record?(association_name, attributes)
404
- association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
465
+ association.build(attributes.except(*UNASSIGNABLE_KEYS))
405
466
  end
406
467
  elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
407
- unless association.loaded? || call_reject_if(association_name, attributes)
468
+ unless call_reject_if(association_name, attributes)
408
469
  # Make sure we are operating on the actual object which is in the association's
409
470
  # proxy_target array (either by finding it, or adding it if not found)
410
- target_record = association.target.detect { |record| record == existing_record }
411
-
471
+ # Take into account that the proxy_target may have changed due to callbacks
472
+ target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s }
412
473
  if target_record
413
474
  existing_record = target_record
414
475
  else
415
- association.add_to_target(existing_record)
476
+ association.add_to_target(existing_record, :skip_callbacks)
416
477
  end
417
478
 
479
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
418
480
  end
481
+ else
482
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
483
+ end
484
+ end
485
+ end
419
486
 
420
- if !call_reject_if(association_name, attributes)
421
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
422
- end
423
- elsif assignment_opts[:without_protection]
424
- association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
487
+ # Takes in a limit and checks if the attributes_collection has too many
488
+ # records. It accepts limit in the form of symbol, proc, or
489
+ # number-like object (anything that can be compared with an integer).
490
+ #
491
+ # Raises TooManyRecords error if the attributes_collection is
492
+ # larger than the limit.
493
+ def check_record_limit!(limit, attributes_collection)
494
+ if limit
495
+ limit = case limit
496
+ when Symbol
497
+ send(limit)
498
+ when Proc
499
+ limit.call
425
500
  else
426
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
501
+ limit
502
+ end
503
+
504
+ if limit && attributes_collection.size > limit
505
+ raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
427
506
  end
428
507
  end
429
508
  end
430
509
 
431
510
  # Updates a record with the +attributes+ or marks it for destruction if
432
511
  # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
433
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts)
434
- record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
512
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
513
+ record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
435
514
  record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
436
515
  end
437
516
 
438
517
  # Determines if a hash contains a truthy _destroy key.
439
518
  def has_destroy_flag?(hash)
440
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
519
+ Type::Boolean.new.type_cast_from_user(hash['_destroy'])
441
520
  end
442
521
 
443
- # Determines if a new record should be build by checking for
522
+ # Determines if a new record should be rejected by checking
444
523
  # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
445
524
  # association and evaluates to +true+.
446
525
  def reject_new_record?(association_name, attributes)
447
- has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
526
+ will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
448
527
  end
449
528
 
529
+ # Determines if a record with the particular +attributes+ should be
530
+ # rejected by calling the reject_if Symbol or Proc (if defined).
531
+ # The reject_if option is defined by +accepts_nested_attributes_for+.
532
+ #
533
+ # Returns false if there is a +destroy_flag+ on the attributes.
450
534
  def call_reject_if(association_name, attributes)
451
- return false if has_destroy_flag?(attributes)
535
+ return false if will_be_destroyed?(association_name, attributes)
536
+
452
537
  case callback = self.nested_attributes_options[association_name][:reject_if]
453
538
  when Symbol
454
539
  method(callback).arity == 0 ? send(callback) : send(callback, attributes)
@@ -457,12 +542,17 @@ module ActiveRecord
457
542
  end
458
543
  end
459
544
 
460
- def raise_nested_attributes_record_not_found(association_name, record_id)
461
- raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
545
+ # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
546
+ def will_be_destroyed?(association_name, attributes)
547
+ allow_destroy?(association_name) && has_destroy_flag?(attributes)
548
+ end
549
+
550
+ def allow_destroy?(association_name)
551
+ self.nested_attributes_options[association_name][:allow_destroy]
462
552
  end
463
553
 
464
- def unassignable_keys(assignment_opts)
465
- assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
554
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
555
+ raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
466
556
  end
467
557
  end
468
558
  end