activerecord 4.2.0 → 5.2.8.1

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 +5 -5
  2. data/CHANGELOG.md +640 -928
  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 +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  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 +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  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 +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -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 -81
  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 +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  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 +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  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 +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  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 +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -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 +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 -57
  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 +5 -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 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  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 +466 -280
  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 +439 -330
  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 -324
  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 +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  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 +136 -90
  133. data/lib/active_record/errors.rb +180 -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 +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  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 +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  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 +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -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 +117 -35
  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 +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  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 +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 -339
  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 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  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 +208 -123
  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 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -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 +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  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.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,31 +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 = {}
11
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
12
  end
13
13
 
14
- module ClassMethods # :nodoc:
15
- # Defines or overrides a attribute on this model. This allows customization of
16
- # Active Record's type casting behavior, as well as adding support for user defined
17
- # 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.
18
22
  #
19
- # +name+ The name of the methods to define attribute methods for, and the column which
20
- # 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.
21
25
  #
22
- # +cast_type+ A type object that contains information about how to type cast the value.
23
- # 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.
24
29
  #
25
30
  # ==== Options
26
- # The options hash accepts the following options:
27
31
  #
28
- # +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).
29
43
  #
30
44
  # ==== Examples
31
45
  #
@@ -43,97 +57,210 @@ module ActiveRecord
43
57
  # store_listing = StoreListing.new(price_in_cents: '10.1')
44
58
  #
45
59
  # # before
46
- # store_listing.price_in_cents # => BigDecimal.new(10.1)
60
+ # store_listing.price_in_cents # => BigDecimal(10.1)
47
61
  #
48
62
  # class StoreListing < ActiveRecord::Base
49
- # attribute :price_in_cents, Type::Integer.new
63
+ # attribute :price_in_cents, :integer
50
64
  # end
51
65
  #
52
66
  # # after
53
67
  # store_listing.price_in_cents # => 10
54
68
  #
55
- # Users may also define their own custom types, as long as they respond to the methods
56
- # defined on the value type. The `type_cast` method on your type object will be called
57
- # with values both from the database, and from your controllers. See
58
- # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
59
- # 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
60
123
  #
61
124
  # class MoneyType < ActiveRecord::Type::Integer
62
- # def type_cast(value)
63
- # if value.include?('$')
125
+ # def cast(value)
126
+ # if !value.kind_of?(Numeric) && value.include?('$')
64
127
  # price_in_dollars = value.gsub(/\$/, '').to_f
65
- # price_in_dollars * 100
128
+ # super(price_in_dollars * 100)
66
129
  # else
67
- # value.to_i
130
+ # super
68
131
  # end
69
132
  # end
70
133
  # end
71
134
  #
135
+ # # config/initializers/types.rb
136
+ # ActiveRecord::Type.register(:money, MoneyType)
137
+ #
138
+ # # app/models/store_listing.rb
72
139
  # class StoreListing < ActiveRecord::Base
73
- # attribute :price_in_cents, MoneyType.new
140
+ # attribute :price_in_cents, :money
74
141
  # end
75
142
  #
76
143
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
77
144
  # store_listing.price_in_cents # => 1000
78
- 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)
79
196
  name = name.to_s
80
- clear_caches_calculated_from_columns
81
- # Assign a new hash to ensure that subclasses do not share a hash
82
- self.user_provided_columns = user_provided_columns.merge(name => cast_type)
197
+ reload_schema_from_cache
83
198
 
84
- if options.key?(:default)
85
- self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
86
- end
87
- end
88
-
89
- # Returns an array of column objects for the table associated with this class.
90
- def columns
91
- @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
199
+ self.attributes_to_define_after_schema_loads =
200
+ attributes_to_define_after_schema_loads.merge(
201
+ name => [cast_type, options]
202
+ )
92
203
  end
93
204
 
94
- # Returns a hash of column objects for the table associated with this class.
95
- def columns_hash
96
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
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)
97
231
  end
98
232
 
99
- def reset_column_information # :nodoc:
233
+ def load_schema! # :nodoc:
100
234
  super
101
- 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
102
242
  end
103
243
 
104
244
  private
105
245
 
106
- def add_user_provided_columns(schema_columns)
107
- existing_columns = schema_columns.map do |column|
108
- new_type = user_provided_columns[column.name]
109
- if new_type
110
- 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
+ )
111
259
  else
112
- column
260
+ default_attribute = ActiveModel::Attribute.from_database(name, value, type)
113
261
  end
262
+ _default_attributes[name] = default_attribute
114
263
  end
115
-
116
- existing_column_names = existing_columns.map(&:name)
117
- new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
118
- connection.new_column(name, nil, type)
119
- end
120
-
121
- existing_columns + new_columns
122
- end
123
-
124
- def clear_caches_calculated_from_columns
125
- @attributes_builder = nil
126
- @column_names = nil
127
- @column_types = nil
128
- @columns = nil
129
- @columns_hash = nil
130
- @content_columns = nil
131
- @default_attributes = nil
132
- end
133
-
134
- def raw_default_values
135
- super.merge(user_provided_defaults)
136
- end
137
264
  end
138
265
  end
139
266
  end