all_futures 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +272 -61
  4. data/LICENSE.txt +1 -1
  5. data/README.md +59 -79
  6. data/Rakefile +13 -1
  7. data/SUMMARY.md +22 -2
  8. data/bin/console +8 -0
  9. data/bin/loc +0 -0
  10. data/bin/setup +0 -0
  11. data/bin/standardize +0 -0
  12. data/lib/all_futures/association.rb +42 -0
  13. data/lib/all_futures/association_index.rb +69 -0
  14. data/lib/all_futures/attributes.rb +28 -0
  15. data/lib/all_futures/base.rb +66 -0
  16. data/lib/all_futures/callbacks.rb +71 -0
  17. data/lib/all_futures/configuration.rb +9 -0
  18. data/lib/all_futures/dirty.rb +116 -0
  19. data/lib/all_futures/embeds.rb +40 -0
  20. data/lib/all_futures/errors.rb +37 -0
  21. data/lib/all_futures/finder.rb +214 -0
  22. data/lib/all_futures/locale/en.yml +49 -0
  23. data/lib/all_futures/persistence.rb +595 -0
  24. data/lib/all_futures/presenter.rb +35 -0
  25. data/lib/all_futures/railtie.rb +26 -0
  26. data/lib/all_futures/translation.rb +22 -0
  27. data/lib/all_futures/validations.rb +10 -0
  28. data/lib/all_futures/version.rb +3 -1
  29. data/lib/all_futures/versions.rb +80 -0
  30. data/lib/all_futures.rb +24 -1
  31. data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
  32. data/lib/vendor/README.md +8 -0
  33. data/lib/vendor/active_entity/aggregations.rb +277 -0
  34. data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
  35. data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
  36. data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
  37. data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
  38. data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
  39. data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
  40. data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
  41. data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
  42. data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
  43. data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
  44. data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
  45. data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
  46. data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
  47. data/lib/vendor/active_entity/associations.rb +168 -0
  48. data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
  49. data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
  50. data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
  51. data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
  52. data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
  53. data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
  54. data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
  55. data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
  56. data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
  57. data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
  58. data/lib/vendor/active_entity/attribute_methods.rb +833 -0
  59. data/lib/vendor/active_entity/attributes.rb +240 -0
  60. data/lib/vendor/active_entity/base.rb +293 -0
  61. data/lib/vendor/active_entity/callbacks.rb +21 -0
  62. data/lib/vendor/active_entity/coders/json.rb +15 -0
  63. data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
  64. data/lib/vendor/active_entity/core.rb +348 -0
  65. data/lib/vendor/active_entity/enum.rb +260 -0
  66. data/lib/vendor/active_entity/errors.rb +69 -0
  67. data/lib/vendor/active_entity/gem_version.rb +17 -0
  68. data/lib/vendor/active_entity/inheritance.rb +176 -0
  69. data/lib/vendor/active_entity/integration.rb +78 -0
  70. data/lib/vendor/active_entity/locale/en.yml +45 -0
  71. data/lib/vendor/active_entity/model_schema.rb +118 -0
  72. data/lib/vendor/active_entity/nested_attributes.rb +554 -0
  73. data/lib/vendor/active_entity/persistence.rb +20 -0
  74. data/lib/vendor/active_entity/railtie.rb +76 -0
  75. data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
  76. data/lib/vendor/active_entity/reflection.rb +447 -0
  77. data/lib/vendor/active_entity/serialization.rb +28 -0
  78. data/lib/vendor/active_entity/store.rb +291 -0
  79. data/lib/vendor/active_entity/translation.rb +24 -0
  80. data/lib/vendor/active_entity/type/date.rb +9 -0
  81. data/lib/vendor/active_entity/type/date_time.rb +9 -0
  82. data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
  83. data/lib/vendor/active_entity/type/json.rb +30 -0
  84. data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
  85. data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
  86. data/lib/vendor/active_entity/type/registry.rb +112 -0
  87. data/lib/vendor/active_entity/type/serialized.rb +71 -0
  88. data/lib/vendor/active_entity/type/text.rb +11 -0
  89. data/lib/vendor/active_entity/type/time.rb +21 -0
  90. data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
  91. data/lib/vendor/active_entity/type.rb +75 -0
  92. data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
  93. data/lib/vendor/active_entity/validations/absence.rb +25 -0
  94. data/lib/vendor/active_entity/validations/associated.rb +60 -0
  95. data/lib/vendor/active_entity/validations/length.rb +26 -0
  96. data/lib/vendor/active_entity/validations/presence.rb +68 -0
  97. data/lib/vendor/active_entity/validations/subset.rb +76 -0
  98. data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
  99. data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
  100. data/lib/vendor/active_entity/validations.rb +47 -0
  101. data/lib/vendor/active_entity/version.rb +10 -0
  102. data/lib/vendor/active_entity.rb +99 -0
  103. data/lib/vendor/core_ext/array_without_blank.rb +48 -0
  104. metadata +175 -21
  105. data/lib/all_futures/all_futures.rb +0 -28
  106. data/lib/all_futures/config.rb +0 -14
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+
5
+ module ActiveEntity
6
+ module AttributeMethods
7
+ module Dirty
8
+ extend ActiveSupport::Concern
9
+ include ActiveEntity::AMAttributeMethods
10
+
11
+ included do
12
+ attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
13
+ attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
14
+ attribute_method_affix prefix: "restore_", suffix: "!"
15
+ attribute_method_affix prefix: "clear_", suffix: "_change"
16
+ end
17
+
18
+ def initialize_dup(other) # :nodoc:
19
+ super
20
+ if self.class.respond_to?(:_default_attributes)
21
+ @attributes = self.class._default_attributes.map do |attr|
22
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
23
+ end
24
+ end
25
+ @mutations_from_database = nil
26
+ end
27
+
28
+ def as_json(options = {}) # :nodoc:
29
+ options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
30
+ super(options)
31
+ end
32
+
33
+ # Clears dirty data and moves +changes+ to +previous_changes+ and
34
+ # +mutations_from_database+ to +mutations_before_last_save+ respectively.
35
+ def changes_applied
36
+ unless defined?(@attributes)
37
+ mutations_from_database.finalize_changes
38
+ end
39
+ @mutations_before_last_save = mutations_from_database
40
+ forget_attribute_assignments
41
+ @mutations_from_database = nil
42
+ end
43
+
44
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
45
+ #
46
+ # person.changed? # => false
47
+ # person.name = 'bob'
48
+ # person.changed? # => true
49
+ def changed?
50
+ mutations_from_database.any_changes?
51
+ end
52
+
53
+ # Returns an array with the name of the attributes with unsaved changes.
54
+ #
55
+ # person.changed # => []
56
+ # person.name = 'bob'
57
+ # person.changed # => ["name"]
58
+ def changed
59
+ mutations_from_database.changed_attribute_names
60
+ end
61
+
62
+ # Dispatch target for <tt>*_changed?</tt> attribute methods.
63
+ def attribute_changed?(attr_name, **options) # :nodoc:
64
+ mutations_from_database.changed?(attr_name.to_s, **options)
65
+ end
66
+
67
+ # Dispatch target for <tt>*_was</tt> attribute methods.
68
+ def attribute_was(attr_name) # :nodoc:
69
+ mutations_from_database.original_value(attr_name.to_s)
70
+ end
71
+
72
+ # Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
73
+ def attribute_previously_changed?(attr_name, **options) # :nodoc:
74
+ mutations_before_last_save.changed?(attr_name.to_s, **options)
75
+ end
76
+
77
+ # Dispatch target for <tt>*_previously_was</tt> attribute methods.
78
+ def attribute_previously_was(attr_name) # :nodoc:
79
+ mutations_before_last_save.original_value(attr_name.to_s)
80
+ end
81
+
82
+ # Restore all previous data of the provided attributes.
83
+ def restore_attributes(attr_names = changed)
84
+ attr_names.each { |attr_name| restore_attribute!(attr_name) }
85
+ end
86
+
87
+ # Clears all dirty data: current changes and previous changes.
88
+ def clear_changes_information
89
+ @mutations_before_last_save = nil
90
+ forget_attribute_assignments
91
+ @mutations_from_database = nil
92
+ end
93
+
94
+ def clear_attribute_changes(attr_names)
95
+ attr_names.each do |attr_name|
96
+ clear_attribute_change(attr_name)
97
+ end
98
+ end
99
+
100
+ # Returns a hash of the attributes with unsaved changes indicating their original
101
+ # values like <tt>attr => original value</tt>.
102
+ #
103
+ # person.name # => "bob"
104
+ # person.name = 'robert'
105
+ # person.changed_attributes # => {"name" => "bob"}
106
+ def changed_attributes
107
+ mutations_from_database.changed_values
108
+ end
109
+
110
+ # Returns a hash of changed attributes indicating their original
111
+ # and new values like <tt>attr => [original value, new value]</tt>.
112
+ #
113
+ # person.changes # => {}
114
+ # person.name = 'bob'
115
+ # person.changes # => { "name" => ["bill", "bob"] }
116
+ def changes
117
+ mutations_from_database.changes
118
+ end
119
+
120
+ # Returns a hash of attributes that were changed before the model was saved.
121
+ #
122
+ # person.name # => "bob"
123
+ # person.name = 'robert'
124
+ # person.save
125
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
126
+ def previous_changes
127
+ mutations_before_last_save.changes
128
+ end
129
+
130
+ def attribute_changed_in_place?(attr_name) # :nodoc:
131
+ mutations_from_database.changed_in_place?(attr_name.to_s)
132
+ end
133
+
134
+ private
135
+ def clear_attribute_change(attr_name)
136
+ mutations_from_database.forget_change(attr_name.to_s)
137
+ end
138
+
139
+ def mutations_from_database
140
+ @mutations_from_database ||= if defined?(@attributes)
141
+ ActiveModel::AttributeMutationTracker.new(@attributes)
142
+ else
143
+ ActiveModel::ForcedMutationTracker.new(self)
144
+ end
145
+ end
146
+
147
+ def forget_attribute_assignments
148
+ @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
149
+ end
150
+
151
+ def mutations_before_last_save
152
+ @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
153
+ end
154
+
155
+ # Dispatch target for <tt>*_change</tt> attribute methods.
156
+ def attribute_change(attr_name)
157
+ mutations_from_database.change_to_attribute(attr_name.to_s)
158
+ end
159
+
160
+ # Dispatch target for <tt>*_previous_change</tt> attribute methods.
161
+ def attribute_previous_change(attr_name)
162
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
163
+ end
164
+
165
+ # Dispatch target for <tt>*_will_change!</tt> attribute methods.
166
+ def attribute_will_change!(attr_name)
167
+ mutations_from_database.force_change(attr_name.to_s)
168
+ end
169
+
170
+ # Dispatch target for <tt>restore_*!</tt> attribute methods.
171
+ def restore_attribute!(attr_name)
172
+ attr_name = attr_name.to_s
173
+ if attribute_changed?(attr_name)
174
+ __send__("#{attr_name}=", attribute_was(attr_name))
175
+ clear_attribute_change(attr_name)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActiveEntity
6
+ module AttributeMethods
7
+ module PrimaryKey
8
+ extend ActiveSupport::Concern
9
+
10
+ # Returns this record's primary key value wrapped in an array if one is
11
+ # available.
12
+ def to_key
13
+ key = id
14
+ [key] if key
15
+ end
16
+
17
+ # Returns the primary key column's value.
18
+ def id
19
+ _read_attribute(@primary_key)
20
+ end
21
+
22
+ # Sets the primary key column's value.
23
+ def id=(value)
24
+ _write_attribute(@primary_key, value)
25
+ end
26
+
27
+ # Queries the primary key column's value.
28
+ def id?
29
+ query_attribute(@primary_key)
30
+ end
31
+
32
+ # Returns the primary key column's value before type cast.
33
+ def id_before_type_cast
34
+ read_attribute_before_type_cast(@primary_key)
35
+ end
36
+
37
+ # Returns the primary key column's previous value.
38
+ def id_was
39
+ attribute_was(@primary_key)
40
+ end
41
+
42
+ private
43
+
44
+ def attribute_method?(attr_name)
45
+ attr_name == "id" || super
46
+ end
47
+
48
+ module ClassMethods
49
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
50
+
51
+ def instance_method_already_implemented?(method_name)
52
+ super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
53
+ end
54
+
55
+ def dangerous_attribute_method?(method_name)
56
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
57
+ end
58
+
59
+ # Defines the primary key field -- can be overridden in subclasses.
60
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
61
+ # setting, though.
62
+ def primary_key
63
+ unless defined? @primary_key
64
+ @primary_key =
65
+ if has_attribute?("id")
66
+ "id"
67
+ else
68
+ nil
69
+ end
70
+ end
71
+
72
+ @primary_key
73
+ end
74
+
75
+ # Sets the name of the primary key column.
76
+ #
77
+ # class Project < ActiveEntity::Base
78
+ # self.primary_key = 'sysid'
79
+ # end
80
+ #
81
+ # You can also define the #primary_key method yourself:
82
+ #
83
+ # class Project < ActiveEntity::Base
84
+ # def self.primary_key
85
+ # 'foo_' + super
86
+ # end
87
+ # end
88
+ #
89
+ # Project.primary_key # => "foo_id"
90
+ def primary_key=(value)
91
+ @primary_key = value && -value.to_s
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module AttributeMethods
5
+ module Query
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attribute_method_suffix "?"
10
+ end
11
+
12
+ def query_attribute(attr_name)
13
+ value = self[attr_name]
14
+
15
+ case value
16
+ when true then true
17
+ when false, nil then false
18
+ else
19
+ if !type_for_attribute(attr_name) { false }
20
+ if Numeric === value || !value.match?(/[^0-9]/)
21
+ !value.to_i.zero?
22
+ else
23
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
24
+ !value.blank?
25
+ end
26
+ elsif value.respond_to?(:zero?)
27
+ !value.zero?
28
+ else
29
+ !value.blank?
30
+ end
31
+ end
32
+ end
33
+
34
+ alias :attribute? :query_attribute
35
+ private :attribute?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module AttributeMethods
5
+ module Read
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods # :nodoc:
9
+ private
10
+
11
+ def define_method_attribute(name, owner:)
12
+ ActiveEntity::AMAttributeMethods::AttrNames.define_attribute_accessor_method(
13
+ owner, name
14
+ ) do |temp_method_name, attr_name_expr|
15
+ owner <<
16
+ "def #{temp_method_name}" <<
17
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
18
+ "end"
19
+ end
20
+ end
21
+ end
22
+
23
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
24
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
25
+ # to a date object, like Date.new(2004, 12, 12)).
26
+ def read_attribute(attr_name, &block)
27
+ name = attr_name.to_s
28
+ name = self.class.attribute_aliases[name] || name
29
+
30
+ name = @primary_key if name == "id" && @primary_key
31
+ @attributes.fetch_value(name, &block)
32
+ end
33
+
34
+ # This method exists to avoid the expensive primary_key check internally, without
35
+ # breaking compatibility with the read_attribute API
36
+ def _read_attribute(attr_name, &block) # :nodoc
37
+ @attributes.fetch_value(attr_name, &block)
38
+ end
39
+
40
+ alias :attribute :_read_attribute
41
+ private :attribute
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module AttributeMethods
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ class ColumnNotSerializableError < StandardError
9
+ def initialize(name, type)
10
+ super <<~EOS
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 ActiveEntity::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 < ActiveEntity::Base
48
+ # serialize :preferences
49
+ # end
50
+ #
51
+ # # Serialize preferences using JSON as coder.
52
+ # class User < ActiveEntity::Base
53
+ # serialize :preferences, JSON
54
+ # end
55
+ #
56
+ # # Serialize preferences as Hash using YAML coder.
57
+ # class User < ActiveEntity::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 Entity 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?(ActiveEntity::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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+
5
+ module ActiveEntity
6
+ module AttributeMethods
7
+ module TimeZoneConversion
8
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
9
+ def deserialize(value)
10
+ convert_time_to_time_zone(super)
11
+ end
12
+
13
+ def cast(value)
14
+ return if value.nil?
15
+
16
+ if value.is_a?(Hash)
17
+ set_time_zone_without_conversion(super)
18
+ elsif value.respond_to?(:in_time_zone)
19
+ begin
20
+ super(user_input_in_time_zone(value)) || super
21
+ rescue ArgumentError
22
+ nil
23
+ end
24
+ else
25
+ map_avoiding_infinite_recursion(super) { |v| cast(v) }
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def convert_time_to_time_zone(value)
32
+ return if value.nil?
33
+
34
+ if value.acts_like?(:time)
35
+ value.in_time_zone
36
+ elsif value.is_a?(::Float)
37
+ value
38
+ else
39
+ map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
40
+ end
41
+ end
42
+
43
+ def set_time_zone_without_conversion(value)
44
+ ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
45
+ end
46
+
47
+ def map_avoiding_infinite_recursion(value)
48
+ map(value) do |v|
49
+ if value.equal?(v)
50
+ nil
51
+ else
52
+ yield(v)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ extend ActiveSupport::Concern
59
+
60
+ included do
61
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
62
+
63
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
64
+ class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
65
+ end
66
+
67
+ module ClassMethods # :nodoc:
68
+ private
69
+
70
+ def inherited(subclass)
71
+ super
72
+ # We need to apply this decorator here, rather than on module inclusion. The closure
73
+ # created by the matcher would otherwise evaluate for `ActiveEntity::Base`, not the
74
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
75
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
76
+ subclass.class_eval do
77
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
78
+ decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
79
+ TimeZoneConverter.new(type)
80
+ end
81
+ end
82
+ end
83
+
84
+ def create_time_zone_conversion_attribute?(name, cast_type)
85
+ enabled_for_column = time_zone_aware_attributes &&
86
+ !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
87
+
88
+ enabled_for_column && time_zone_aware_types.include?(cast_type.type)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
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, owner:)
16
+ ActiveEntity::AMAttributeMethods::AttrNames.define_attribute_accessor_method(
17
+ owner, name, writer: true,
18
+ ) do |temp_method_name, attr_name_expr|
19
+ owner <<
20
+ "def #{temp_method_name}(value)" <<
21
+ " _write_attribute(#{attr_name_expr}, value)" <<
22
+ "end"
23
+ end
24
+ end
25
+ end
26
+
27
+ # Updates the attribute identified by <tt>attr_name</tt> with the
28
+ # specified +value+. Empty strings for Integer and Float columns are
29
+ # turned into +nil+.
30
+ def write_attribute(attr_name, value)
31
+ name = attr_name.to_s
32
+ name = self.class.attribute_aliases[name] || name
33
+
34
+ name = @primary_key if name == "id" && @primary_key
35
+ @attributes.write_from_user(name, value)
36
+ end
37
+
38
+ # This method exists to avoid the expensive primary_key check internally, without
39
+ # breaking compatibility with the write_attribute API
40
+ def _write_attribute(attr_name, value) # :nodoc:
41
+ @attributes.write_from_user(attr_name, value)
42
+ end
43
+
44
+ alias :attribute= :_write_attribute
45
+ private :attribute=
46
+
47
+ private
48
+
49
+ def write_attribute_without_type_cast(attr_name, value)
50
+ @attributes.write_cast_value(attr_name, value)
51
+ end
52
+ end
53
+ end
54
+ end