activerecord 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ class ColumnNotSerializableError < StandardError
9
+ def initialize(name, type)
10
+ super <<-EOS.strip_heredoc
11
+ Column `#{name}` of type #{type.class} does not support `serialize` feature.
12
+ Usually it means that you are trying to use `serialize`
13
+ on a column that already implements serialization natively.
14
+ EOS
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ # If you have an attribute that needs to be saved to the database as an
20
+ # object, and retrieved as the same object, then specify the name of that
21
+ # attribute using this method and it will be handled automatically. The
22
+ # serialization is done through YAML. If +class_name+ is specified, the
23
+ # serialized object must be of that class on assignment and retrieval.
24
+ # Otherwise SerializationTypeMismatch will be raised.
25
+ #
26
+ # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
27
+ # +Array+, will always be persisted as null.
28
+ #
29
+ # Keep in mind that database adapters handle certain serialization tasks
30
+ # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
31
+ # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
32
+ # objects transparently. There is no need to use #serialize in this
33
+ # case.
34
+ #
35
+ # For more complex cases, such as conversion to or from your application
36
+ # domain objects, consider using the ActiveRecord::Attributes API.
37
+ #
38
+ # ==== Parameters
39
+ #
40
+ # * +attr_name+ - The field name that should be serialized.
41
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
42
+ # or a class name that the object type should be equal to.
43
+ #
44
+ # ==== Example
45
+ #
46
+ # # Serialize a preferences attribute.
47
+ # class User < ActiveRecord::Base
48
+ # serialize :preferences
49
+ # end
50
+ #
51
+ # # Serialize preferences using JSON as coder.
52
+ # class User < ActiveRecord::Base
53
+ # serialize :preferences, JSON
54
+ # end
55
+ #
56
+ # # Serialize preferences as Hash using YAML coder.
57
+ # class User < ActiveRecord::Base
58
+ # serialize :preferences, Hash
59
+ # end
60
+ def serialize(attr_name, class_name_or_coder = Object)
61
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
62
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
63
+ # using the #as_json hook.
64
+ coder = if class_name_or_coder == ::JSON
65
+ Coders::JSON
66
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
67
+ class_name_or_coder
68
+ else
69
+ Coders::YAMLColumn.new(attr_name, class_name_or_coder)
70
+ end
71
+
72
+ decorate_attribute_type(attr_name, :serialize) do |type|
73
+ if type_incompatible_with_serialize?(type, class_name_or_coder)
74
+ raise ColumnNotSerializableError.new(attr_name, type)
75
+ end
76
+
77
+ Type::Serialized.new(type, coder)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def type_incompatible_with_serialize?(type, class_name)
84
+ type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
85
+ type.respond_to?(:type_cast_array, true) && class_name == ::Array
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module TimeZoneConversion
6
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
7
+ def deserialize(value)
8
+ convert_time_to_time_zone(super)
9
+ end
10
+
11
+ def cast(value)
12
+ return if value.nil?
13
+
14
+ if value.is_a?(Hash)
15
+ set_time_zone_without_conversion(super)
16
+ elsif value.respond_to?(:in_time_zone)
17
+ begin
18
+ super(user_input_in_time_zone(value)) || super
19
+ rescue ArgumentError
20
+ nil
21
+ end
22
+ else
23
+ map_avoiding_infinite_recursion(super) { |v| cast(v) }
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def convert_time_to_time_zone(value)
30
+ return if value.nil?
31
+
32
+ if value.acts_like?(:time)
33
+ value.in_time_zone
34
+ elsif value.is_a?(::Float)
35
+ value
36
+ else
37
+ map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
38
+ end
39
+ end
40
+
41
+ def set_time_zone_without_conversion(value)
42
+ ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
43
+ end
44
+
45
+ def map_avoiding_infinite_recursion(value)
46
+ map(value) do |v|
47
+ if value.equal?(v)
48
+ nil
49
+ else
50
+ yield(v)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ extend ActiveSupport::Concern
57
+
58
+ included do
59
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
60
+
61
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
62
+ class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
63
+ end
64
+
65
+ module ClassMethods # :nodoc:
66
+ private
67
+
68
+ def inherited(subclass)
69
+ super
70
+ # We need to apply this decorator here, rather than on module inclusion. The closure
71
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
72
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
73
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
74
+ subclass.class_eval do
75
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
76
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
77
+ TimeZoneConverter.new(type)
78
+ end
79
+ end
80
+ end
81
+
82
+ def create_time_zone_conversion_attribute?(name, cast_type)
83
+ enabled_for_column = time_zone_aware_attributes &&
84
+ !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
85
+
86
+ enabled_for_column && time_zone_aware_types.include?(cast_type.type)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Write
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attribute_method_suffix "="
10
+ end
11
+
12
+ module ClassMethods # :nodoc:
13
+ private
14
+
15
+ def define_method_attribute=(name)
16
+ safe_name = name.unpack("h*".freeze).first
17
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
18
+ sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
19
+
20
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
21
+ def __temp__#{safe_name}=(value)
22
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
23
+ #{sync_with_transaction_state}
24
+ _write_attribute(name, value)
25
+ end
26
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
27
+ undef_method :__temp__#{safe_name}=
28
+ STR
29
+ end
30
+ end
31
+
32
+ # Updates the attribute identified by <tt>attr_name</tt> with the
33
+ # specified +value+. Empty strings for Integer and Float columns are
34
+ # turned into +nil+.
35
+ def write_attribute(attr_name, value)
36
+ name = if self.class.attribute_alias?(attr_name)
37
+ self.class.attribute_alias(attr_name).to_s
38
+ else
39
+ attr_name.to_s
40
+ end
41
+
42
+ primary_key = self.class.primary_key
43
+ name = primary_key if name == "id".freeze && primary_key
44
+ sync_with_transaction_state if name == primary_key
45
+ _write_attribute(name, value)
46
+ end
47
+
48
+ # This method exists to avoid the expensive primary_key check internally, without
49
+ # breaking compatibility with the write_attribute API
50
+ def _write_attribute(attr_name, value) # :nodoc:
51
+ @attributes.write_from_user(attr_name.to_s, value)
52
+ value
53
+ end
54
+
55
+ private
56
+ def write_attribute_without_type_cast(attr_name, value)
57
+ name = attr_name.to_s
58
+ @attributes.write_cast_value(name, value)
59
+ value
60
+ end
61
+
62
+ # Handle *= for method_missing.
63
+ def attribute=(attribute_name, value)
64
+ _write_attribute(attribute_name, value)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute/user_provided_default"
4
+
5
+ module ActiveRecord
6
+ # See ActiveRecord::Attributes::ClassMethods for documentation
7
+ module Attributes
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
+ end
13
+
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.
22
+ #
23
+ # +name+ The name of the methods to define attribute methods for, and the
24
+ # column which this will persist to.
25
+ #
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.
29
+ #
30
+ # ==== Options
31
+ #
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).
43
+ #
44
+ # ==== Examples
45
+ #
46
+ # The type detected by Active Record can be overridden.
47
+ #
48
+ # # db/schema.rb
49
+ # create_table :store_listings, force: true do |t|
50
+ # t.decimal :price_in_cents
51
+ # end
52
+ #
53
+ # # app/models/store_listing.rb
54
+ # class StoreListing < ActiveRecord::Base
55
+ # end
56
+ #
57
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
58
+ #
59
+ # # before
60
+ # store_listing.price_in_cents # => BigDecimal(10.1)
61
+ #
62
+ # class StoreListing < ActiveRecord::Base
63
+ # attribute :price_in_cents, :integer
64
+ # end
65
+ #
66
+ # # after
67
+ # store_listing.price_in_cents # => 10
68
+ #
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
123
+ #
124
+ # class MoneyType < ActiveRecord::Type::Integer
125
+ # def cast(value)
126
+ # if !value.kind_of?(Numeric) && value.include?('$')
127
+ # price_in_dollars = value.gsub(/\$/, '').to_f
128
+ # super(price_in_dollars * 100)
129
+ # else
130
+ # super
131
+ # end
132
+ # end
133
+ # end
134
+ #
135
+ # # config/initializers/types.rb
136
+ # ActiveRecord::Type.register(:money, MoneyType)
137
+ #
138
+ # # app/models/store_listing.rb
139
+ # class StoreListing < ActiveRecord::Base
140
+ # attribute :price_in_cents, :money
141
+ # end
142
+ #
143
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
144
+ # store_listing.price_in_cents # => 1000
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)
196
+ name = name.to_s
197
+ reload_schema_from_cache
198
+
199
+ self.attributes_to_define_after_schema_loads =
200
+ attributes_to_define_after_schema_loads.merge(
201
+ name => [cast_type, options]
202
+ )
203
+ end
204
+
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)
231
+ end
232
+
233
+ def load_schema! # :nodoc:
234
+ super
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
242
+ end
243
+
244
+ private
245
+
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
+ )
259
+ else
260
+ default_attribute = ActiveModel::Attribute.from_database(name, value, type)
261
+ end
262
+ _default_attributes[name] = default_attribute
263
+ end
264
+ end
265
+ end
266
+ end