activerecord 4.2.11.1 → 5.2.4.5

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +594 -1620
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -87
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +135 -88
  133. data/lib/active_record/errors.rb +179 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +94 -32
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -340
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +199 -124
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +24 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -49
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -1,33 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute/user_provided_default"
4
+
1
5
  module ActiveRecord
2
- module Attributes # :nodoc:
6
+ # See ActiveRecord::Attributes::ClassMethods for documentation
7
+ module Attributes
3
8
  extend ActiveSupport::Concern
4
9
 
5
- Type = ActiveRecord::Type
6
-
7
10
  included do
8
- class_attribute :user_provided_columns, instance_accessor: false # :internal:
9
- class_attribute :user_provided_defaults, instance_accessor: false # :internal:
10
- self.user_provided_columns = {}
11
- self.user_provided_defaults = {}
12
-
13
- delegate :persistable_attribute_names, to: :class
11
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
14
12
  end
15
13
 
16
- module ClassMethods # :nodoc:
17
- # Defines or overrides a attribute on this model. This allows customization of
18
- # Active Record's type casting behavior, as well as adding support for user defined
19
- # types.
14
+ module ClassMethods
15
+ # Defines an attribute with a type on this model. It will override the
16
+ # type of existing attributes if needed. This allows control over how
17
+ # values are converted to and from SQL when assigned to a model. It also
18
+ # changes the behavior of values passed to
19
+ # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
20
+ # your domain objects across much of Active Record, without having to
21
+ # rely on implementation details or monkey patching.
20
22
  #
21
- # +name+ The name of the methods to define attribute methods for, and the column which
22
- # this will persist to.
23
+ # +name+ The name of the methods to define attribute methods for, and the
24
+ # column which this will persist to.
23
25
  #
24
- # +cast_type+ A type object that contains information about how to type cast the value.
25
- # See the examples section for more information.
26
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
27
+ # to be used for this attribute. See the examples below for more
28
+ # information about providing custom type objects.
26
29
  #
27
30
  # ==== Options
28
- # The options hash accepts the following options:
29
31
  #
30
- # +default+ is the default value that the column should use on a new record.
32
+ # The following options are accepted:
33
+ #
34
+ # +default+ The default value to use when no value is provided. If this option
35
+ # is not passed, the previous default value (if any) will be used.
36
+ # Otherwise, the default will be +nil+.
37
+ #
38
+ # +array+ (PostgreSQL only) specifies that the type should be an array (see the
39
+ # examples below).
40
+ #
41
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
+ # examples below).
31
43
  #
32
44
  # ==== Examples
33
45
  #
@@ -45,103 +57,210 @@ module ActiveRecord
45
57
  # store_listing = StoreListing.new(price_in_cents: '10.1')
46
58
  #
47
59
  # # before
48
- # store_listing.price_in_cents # => BigDecimal.new(10.1)
60
+ # store_listing.price_in_cents # => BigDecimal(10.1)
49
61
  #
50
62
  # class StoreListing < ActiveRecord::Base
51
- # attribute :price_in_cents, Type::Integer.new
63
+ # attribute :price_in_cents, :integer
52
64
  # end
53
65
  #
54
66
  # # after
55
67
  # store_listing.price_in_cents # => 10
56
68
  #
57
- # Users may also define their own custom types, as long as they respond to the methods
58
- # defined on the value type. The `type_cast` method on your type object will be called
59
- # with values both from the database, and from your controllers. See
60
- # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
61
- # type objects inherit from an existing type, or the base value type.
69
+ # A default can also be provided.
70
+ #
71
+ # # db/schema.rb
72
+ # create_table :store_listings, force: true do |t|
73
+ # t.string :my_string, default: "original default"
74
+ # end
75
+ #
76
+ # StoreListing.new.my_string # => "original default"
77
+ #
78
+ # # app/models/store_listing.rb
79
+ # class StoreListing < ActiveRecord::Base
80
+ # attribute :my_string, :string, default: "new default"
81
+ # end
82
+ #
83
+ # StoreListing.new.my_string # => "new default"
84
+ #
85
+ # class Product < ActiveRecord::Base
86
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
87
+ # end
88
+ #
89
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
90
+ # sleep 1
91
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
92
+ #
93
+ # \Attributes do not need to be backed by a database column.
94
+ #
95
+ # # app/models/my_model.rb
96
+ # class MyModel < ActiveRecord::Base
97
+ # attribute :my_string, :string
98
+ # attribute :my_int_array, :integer, array: true
99
+ # attribute :my_float_range, :float, range: true
100
+ # end
101
+ #
102
+ # model = MyModel.new(
103
+ # my_string: "string",
104
+ # my_int_array: ["1", "2", "3"],
105
+ # my_float_range: "[1,3.5]",
106
+ # )
107
+ # model.attributes
108
+ # # =>
109
+ # {
110
+ # my_string: "string",
111
+ # my_int_array: [1, 2, 3],
112
+ # my_float_range: 1.0..3.5
113
+ # }
114
+ #
115
+ # ==== Creating Custom Types
116
+ #
117
+ # Users may also define their own custom types, as long as they respond
118
+ # to the methods defined on the value type. The method +deserialize+ or
119
+ # +cast+ will be called on your type object, with raw input from the
120
+ # database or from your controllers. See ActiveModel::Type::Value for the
121
+ # expected API. It is recommended that your type objects inherit from an
122
+ # existing type, or from ActiveRecord::Type::Value
62
123
  #
63
124
  # class MoneyType < ActiveRecord::Type::Integer
64
- # def type_cast(value)
65
- # if value.include?('$')
125
+ # def cast(value)
126
+ # if !value.kind_of?(Numeric) && value.include?('$')
66
127
  # price_in_dollars = value.gsub(/\$/, '').to_f
67
- # price_in_dollars * 100
128
+ # super(price_in_dollars * 100)
68
129
  # else
69
- # value.to_i
130
+ # super
70
131
  # end
71
132
  # end
72
133
  # end
73
134
  #
135
+ # # config/initializers/types.rb
136
+ # ActiveRecord::Type.register(:money, MoneyType)
137
+ #
138
+ # # app/models/store_listing.rb
74
139
  # class StoreListing < ActiveRecord::Base
75
- # attribute :price_in_cents, MoneyType.new
140
+ # attribute :price_in_cents, :money
76
141
  # end
77
142
  #
78
143
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
79
144
  # store_listing.price_in_cents # => 1000
80
- def attribute(name, cast_type, options = {})
145
+ #
146
+ # For more details on creating custom types, see the documentation for
147
+ # ActiveModel::Type::Value. For more details on registering your types
148
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
149
+ # also pass a type object directly, in place of a symbol.
150
+ #
151
+ # ==== \Querying
152
+ #
153
+ # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
154
+ # use the type defined by the model class to convert the value to SQL,
155
+ # calling +serialize+ on your type object. For example:
156
+ #
157
+ # class Money < Struct.new(:amount, :currency)
158
+ # end
159
+ #
160
+ # class MoneyType < Type::Value
161
+ # def initialize(currency_converter:)
162
+ # @currency_converter = currency_converter
163
+ # end
164
+ #
165
+ # # value will be the result of +deserialize+ or
166
+ # # +cast+. Assumed to be an instance of +Money+ in
167
+ # # this case.
168
+ # def serialize(value)
169
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
170
+ # value_in_bitcoins.amount
171
+ # end
172
+ # end
173
+ #
174
+ # # config/initializers/types.rb
175
+ # ActiveRecord::Type.register(:money, MoneyType)
176
+ #
177
+ # # app/models/product.rb
178
+ # class Product < ActiveRecord::Base
179
+ # currency_converter = ConversionRatesFromTheInternet.new
180
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
181
+ # end
182
+ #
183
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
184
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
185
+ #
186
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
187
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
188
+ #
189
+ # ==== Dirty Tracking
190
+ #
191
+ # The type of an attribute is given the opportunity to change how dirty
192
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
193
+ # will be called from ActiveModel::Dirty. See the documentation for those
194
+ # methods in ActiveModel::Type::Value for more details.
195
+ def attribute(name, cast_type = Type::Value.new, **options)
81
196
  name = name.to_s
82
- clear_caches_calculated_from_columns
83
- # Assign a new hash to ensure that subclasses do not share a hash
84
- self.user_provided_columns = user_provided_columns.merge(name => cast_type)
85
-
86
- if options.key?(:default)
87
- self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
88
- end
89
- end
90
-
91
- # Returns an array of column objects for the table associated with this class.
92
- def columns
93
- @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
94
- end
197
+ reload_schema_from_cache
95
198
 
96
- # Returns a hash of column objects for the table associated with this class.
97
- def columns_hash
98
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
199
+ self.attributes_to_define_after_schema_loads =
200
+ attributes_to_define_after_schema_loads.merge(
201
+ name => [cast_type, options]
202
+ )
99
203
  end
100
204
 
101
- def persistable_attribute_names # :nodoc:
102
- @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
205
+ # This is the low level API which sits beneath +attribute+. It only
206
+ # accepts type objects, and will do its work immediately instead of
207
+ # waiting for the schema to load. Automatic schema detection and
208
+ # ClassMethods#attribute both call this under the hood. While this method
209
+ # is provided so it can be used by plugin authors, application code
210
+ # should probably use ClassMethods#attribute.
211
+ #
212
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
213
+ #
214
+ # +cast_type+ The type object to use for this attribute.
215
+ #
216
+ # +default+ The default value to use when no value is provided. If this option
217
+ # is not passed, the previous default value (if any) will be used.
218
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
219
+ # will be called once each time a new value is needed.
220
+ #
221
+ # +user_provided_default+ Whether the default value should be cast using
222
+ # +cast+ or +deserialize+.
223
+ def define_attribute(
224
+ name,
225
+ cast_type,
226
+ default: NO_DEFAULT_PROVIDED,
227
+ user_provided_default: true
228
+ )
229
+ attribute_types[name] = cast_type
230
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
103
231
  end
104
232
 
105
- def reset_column_information # :nodoc:
233
+ def load_schema! # :nodoc:
106
234
  super
107
- clear_caches_calculated_from_columns
235
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
236
+ if type.is_a?(Symbol)
237
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
238
+ end
239
+
240
+ define_attribute(name, type, **options.slice(:default))
241
+ end
108
242
  end
109
243
 
110
244
  private
111
245
 
112
- def add_user_provided_columns(schema_columns)
113
- existing_columns = schema_columns.map do |column|
114
- new_type = user_provided_columns[column.name]
115
- if new_type
116
- column.with_type(new_type)
246
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
247
+ private_constant :NO_DEFAULT_PROVIDED
248
+
249
+ def define_default_attribute(name, value, type, from_user:)
250
+ if value == NO_DEFAULT_PROVIDED
251
+ default_attribute = _default_attributes[name].with_type(type)
252
+ elsif from_user
253
+ default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
254
+ name,
255
+ value,
256
+ type,
257
+ _default_attributes.fetch(name.to_s) { nil },
258
+ )
117
259
  else
118
- column
260
+ default_attribute = ActiveModel::Attribute.from_database(name, value, type)
119
261
  end
262
+ _default_attributes[name] = default_attribute
120
263
  end
121
-
122
- existing_column_names = existing_columns.map(&:name)
123
- new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
124
- connection.new_column(name, nil, type)
125
- end
126
-
127
- existing_columns + new_columns
128
- end
129
-
130
- def clear_caches_calculated_from_columns
131
- @attributes_builder = nil
132
- @column_names = nil
133
- @column_types = nil
134
- @columns = nil
135
- @columns_hash = nil
136
- @content_columns = nil
137
- @default_attributes = nil
138
- @persistable_attribute_names = nil
139
- @attribute_names = nil
140
- end
141
-
142
- def raw_default_values
143
- super.merge(user_provided_defaults)
144
- end
145
264
  end
146
265
  end
147
266
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  # = Active Record Autosave Association
3
5
  #
4
- # +AutosaveAssociation+ is a module that takes care of automatically saving
6
+ # AutosaveAssociation is a module that takes care of automatically saving
5
7
  # associated records when their parent is saved. In addition to saving, it
6
8
  # also destroys any associated records that were marked for destruction.
7
- # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
9
+ # (See #mark_for_destruction and #marked_for_destruction?).
8
10
  #
9
11
  # Saving of the parent, its associations, and the destruction of marked
10
12
  # associations, all happen inside a transaction. This should never leave the
@@ -22,7 +24,7 @@ module ActiveRecord
22
24
  #
23
25
  # == Validation
24
26
  #
25
- # Children records are validated unless <tt>:validate</tt> is +false+.
27
+ # Child records are validated unless <tt>:validate</tt> is +false+.
26
28
  #
27
29
  # == Callbacks
28
30
  #
@@ -125,7 +127,6 @@ module ActiveRecord
125
127
  # Now it _is_ removed from the database:
126
128
  #
127
129
  # Comment.find_by(id: id).nil? # => true
128
-
129
130
  module AutosaveAssociation
130
131
  extend ActiveSupport::Concern
131
132
 
@@ -141,9 +142,10 @@ module ActiveRecord
141
142
 
142
143
  included do
143
144
  Associations::Builder::Association.extensions << AssociationBuilderExtension
145
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
144
146
  end
145
147
 
146
- module ClassMethods
148
+ module ClassMethods # :nodoc:
147
149
  private
148
150
 
149
151
  def define_non_cyclic_method(name, &block)
@@ -153,10 +155,10 @@ module ActiveRecord
153
155
  # Loop prevention for validation of associations
154
156
  unless @_already_called[name]
155
157
  begin
156
- @_already_called[name]=true
158
+ @_already_called[name] = true
157
159
  result = instance_eval(&block)
158
160
  ensure
159
- @_already_called[name]=false
161
+ @_already_called[name] = false
160
162
  end
161
163
  end
162
164
 
@@ -180,6 +182,7 @@ module ActiveRecord
180
182
 
181
183
  if reflection.collection?
182
184
  before_save :before_save_collection_association
185
+ after_save :after_save_collection_association
183
186
 
184
187
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
185
188
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
@@ -198,7 +201,7 @@ module ActiveRecord
198
201
  after_create save_method
199
202
  after_update save_method
200
203
  else
201
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
204
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
202
205
  before_save save_method
203
206
  end
204
207
 
@@ -216,6 +219,7 @@ module ActiveRecord
216
219
 
217
220
  define_non_cyclic_method(validation_method) { send(method, reflection) }
218
221
  validate validation_method
222
+ after_validation :_ensure_no_duplicate_errors
219
223
  end
220
224
  end
221
225
  end
@@ -227,7 +231,7 @@ module ActiveRecord
227
231
  super
228
232
  end
229
233
 
230
- # Marks this record to be destroyed as part of the parents save transaction.
234
+ # Marks this record to be destroyed as part of the parent's save transaction.
231
235
  # This does _not_ actually destroy the record instantly, rather child record will be destroyed
232
236
  # when <tt>parent.save</tt> is called.
233
237
  #
@@ -236,7 +240,7 @@ module ActiveRecord
236
240
  @marked_for_destruction = true
237
241
  end
238
242
 
239
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
243
+ # Returns whether or not this record will be destroyed as part of the parent's save transaction.
240
244
  #
241
245
  # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
242
246
  def marked_for_destruction?
@@ -259,7 +263,7 @@ module ActiveRecord
259
263
  # Returns whether or not this record has been changed in any way (including whether
260
264
  # any of its nested autosave associations are likewise changed)
261
265
  def changed_for_autosave?
262
- new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
266
+ new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
263
267
  end
264
268
 
265
269
  private
@@ -268,12 +272,12 @@ module ActiveRecord
268
272
  # or saved. If +autosave+ is +false+ only new records will be returned,
269
273
  # unless the parent is/was a new record itself.
270
274
  def associated_records_to_validate_or_save(association, new_record, autosave)
271
- if new_record
275
+ if new_record || custom_validation_context?
272
276
  association && association.target
273
277
  elsif autosave
274
- association.target.find_all { |record| record.changed_for_autosave? }
278
+ association.target.find_all(&:changed_for_autosave?)
275
279
  else
276
- association.target.find_all { |record| record.new_record? }
280
+ association.target.find_all(&:new_record?)
277
281
  end
278
282
  end
279
283
 
@@ -300,7 +304,7 @@ module ActiveRecord
300
304
  def validate_single_association(reflection)
301
305
  association = association_instance_get(reflection.name)
302
306
  record = association && association.reader
303
- association_valid?(reflection, record) if record
307
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
304
308
  end
305
309
 
306
310
  # Validate the associated records if <tt>:validate</tt> or
@@ -309,7 +313,7 @@ module ActiveRecord
309
313
  def validate_collection_association(reflection)
310
314
  if association = association_instance_get(reflection.name)
311
315
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
312
- records.each { |record| association_valid?(reflection, record) }
316
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
313
317
  end
314
318
  end
315
319
  end
@@ -317,17 +321,30 @@ module ActiveRecord
317
321
  # Returns whether or not the association is valid and applies any errors to
318
322
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
319
323
  # enabled records if they're marked_for_destruction? or destroyed.
320
- def association_valid?(reflection, record)
324
+ def association_valid?(reflection, record, index = nil)
321
325
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
322
326
 
323
- validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
324
- unless valid = record.valid?(validation_context)
327
+ context = validation_context if custom_validation_context?
328
+
329
+ unless valid = record.valid?(context)
325
330
  if reflection.options[:autosave]
331
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
332
+
326
333
  record.errors.each do |attribute, message|
327
- attribute = "#{reflection.name}.#{attribute}"
334
+ attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
328
335
  errors[attribute] << message
329
336
  errors[attribute].uniq!
330
337
  end
338
+
339
+ record.errors.details.each_key do |attribute|
340
+ reflection_attribute =
341
+ normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
342
+
343
+ record.errors.details[attribute].each do |error|
344
+ errors.details[reflection_attribute] << error
345
+ errors.details[reflection_attribute].uniq!
346
+ end
347
+ end
331
348
  else
332
349
  errors.add(reflection.name)
333
350
  end
@@ -335,18 +352,29 @@ module ActiveRecord
335
352
  valid
336
353
  end
337
354
 
355
+ def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
356
+ if indexed_attribute
357
+ "#{reflection.name}[#{index}].#{attribute}"
358
+ else
359
+ "#{reflection.name}.#{attribute}"
360
+ end
361
+ end
362
+
338
363
  # Is used as a before_save callback to check while saving a collection
339
364
  # association whether or not the parent was a new record before saving.
340
365
  def before_save_collection_association
341
366
  @new_record_before_save = new_record?
342
- true
367
+ end
368
+
369
+ def after_save_collection_association
370
+ @new_record_before_save = false
343
371
  end
344
372
 
345
373
  # Saves any new associated records, or all loaded autosave associations if
346
374
  # <tt>:autosave</tt> is enabled on the association.
347
375
  #
348
376
  # In addition, it destroys all children that were marked for destruction
349
- # with mark_for_destruction.
377
+ # with #mark_for_destruction.
350
378
  #
351
379
  # This all happens inside a transaction, _if_ the Transactions module is included into
352
380
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -354,7 +382,14 @@ module ActiveRecord
354
382
  if association = association_instance_get(reflection.name)
355
383
  autosave = reflection.options[:autosave]
356
384
 
357
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
389
+ # reconstruct the scope now that we know the owner's id
390
+ association.reset_scope
391
+
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
358
393
  if autosave
359
394
  records_to_destroy = records.select(&:marked_for_destruction?)
360
395
  records_to_destroy.each { |record| association.destroy(record) }
@@ -366,22 +401,24 @@ module ActiveRecord
366
401
 
367
402
  saved = true
368
403
 
369
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
370
405
  if autosave
371
406
  saved = association.insert_record(record, false)
372
- else
373
- association.insert_record(record) unless reflection.nested?
407
+ elsif !reflection.nested?
408
+ association_saved = association.insert_record(record)
409
+
410
+ if reflection.validate?
411
+ errors.add(reflection.name) unless association_saved
412
+ saved = association_saved
413
+ end
374
414
  end
375
415
  elsif autosave
376
- saved = record.save(:validate => false)
416
+ saved = record.save(validate: false)
377
417
  end
378
418
 
379
419
  raise ActiveRecord::Rollback unless saved
380
420
  end
381
421
  end
382
-
383
- # reconstruct the scope now that we know the owner's id
384
- association.reset_scope if association.respond_to?(:reset_scope)
385
422
  end
386
423
  end
387
424
 
@@ -389,7 +426,7 @@ module ActiveRecord
389
426
  # on the association.
390
427
  #
391
428
  # In addition, it will destroy the association if it was marked for
392
- # destruction with mark_for_destruction.
429
+ # destruction with #mark_for_destruction.
393
430
  #
394
431
  # This all happens inside a transaction, _if_ the Transactions module is included into
395
432
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -408,9 +445,12 @@ module ActiveRecord
408
445
  if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
409
446
  unless reflection.through_reflection
410
447
  record[reflection.foreign_key] = key
448
+ if inverse_reflection = reflection.inverse_of
449
+ record.association(inverse_reflection.name).loaded!
450
+ end
411
451
  end
412
452
 
413
- saved = record.save(:validate => !autosave)
453
+ saved = record.save(validate: !autosave)
414
454
  raise ActiveRecord::Rollback if !saved && autosave
415
455
  saved
416
456
  end
@@ -421,8 +461,14 @@ module ActiveRecord
421
461
  # If the record is new or it has changed, returns true.
422
462
  def record_changed?(reflection, record, key)
423
463
  record.new_record? ||
424
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
425
- record.attribute_changed?(reflection.foreign_key)
464
+ association_foreign_key_changed?(reflection, record, key) ||
465
+ record.will_save_change_to_attribute?(reflection.foreign_key)
466
+ end
467
+
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
426
472
  end
427
473
 
428
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -430,7 +476,9 @@ module ActiveRecord
430
476
  # In addition, it will destroy the association if it was marked for destruction.
431
477
  def save_belongs_to_association(reflection)
432
478
  association = association_instance_get(reflection.name)
433
- record = association && association.load_target
479
+ return unless association && association.loaded? && !association.stale_target?
480
+
481
+ record = association.load_target
434
482
  if record && !record.destroyed?
435
483
  autosave = reflection.options[:autosave]
436
484
 
@@ -438,7 +486,7 @@ module ActiveRecord
438
486
  self[reflection.foreign_key] = nil
439
487
  record.destroy
440
488
  elsif autosave != false
441
- saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
489
+ saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
442
490
 
443
491
  if association.updated?
444
492
  association_id = record.send(reflection.options[:primary_key] || :id)
@@ -450,5 +498,15 @@ module ActiveRecord
450
498
  end
451
499
  end
452
500
  end
501
+
502
+ def custom_validation_context?
503
+ validation_context && [:create, :update].exclude?(validation_context)
504
+ end
505
+
506
+ def _ensure_no_duplicate_errors
507
+ errors.messages.each_key do |attribute|
508
+ errors[attribute].uniq!
509
+ end
510
+ end
453
511
  end
454
512
  end