serialize_attributes 0.6.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/README.md +1 -6
- data/lib/serialize_attributes/store.rb +104 -92
- data/lib/serialize_attributes/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcfd75fcff7e883cb0063752bb35de287b4683c59e0029bf654d77b0e67b7ec0
|
4
|
+
data.tar.gz: adc7cc26ba5b6dca8b543a2a33960419c66cd37149a5e759bdfc820f131fbe60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f160a8efe020f2d47d385f3c2ea76149adcf2d6eff06dd6d9b49e08e0684e7fa1cbcd4abb7e6c8f8ea3d25cebed1c888a2aa7bbb42d4a7fff5f0c5ec30d08d67
|
7
|
+
data.tar.gz: a58b02f33d14bd4afa8202a7d69553377ecf2fc2b9b4c011fbc5fdf2ff8460628dc8675d8ad883bfedb98e810779597a3afbe4f01347a9014baa5189d0223afb
|
data/README.md
CHANGED
@@ -148,12 +148,7 @@ class MyModel
|
|
148
148
|
end
|
149
149
|
```
|
150
150
|
|
151
|
-
Please note that the default value for an array attribute is always `[]
|
152
|
-
specify a `default` attribute yourself explicitly:
|
153
|
-
|
154
|
-
```ruby
|
155
|
-
attribute :emails, :string, array: true, default: ["unknown@example.com"]
|
156
|
-
```
|
151
|
+
Please note that the default value for an array attribute is always `[]`.
|
157
152
|
|
158
153
|
### Enumerated ("enum") types
|
159
154
|
|
@@ -4,18 +4,19 @@ module SerializeAttributes
|
|
4
4
|
# SerializeAttributes::Store is the individual store, keyed by name. You can get a
|
5
5
|
# reference to the store by calling `Model.serialized_attributes_store(column_name)`.
|
6
6
|
class Store
|
7
|
-
def initialize(model_class, column_name, &block)
|
8
|
-
# :nodoc:
|
7
|
+
def initialize(model_class, column_name, &block) # :nodoc:
|
9
8
|
@model_class = model_class
|
10
9
|
@column_name = column_name
|
11
|
-
@
|
10
|
+
@attribute_types = {}
|
12
11
|
@defaults = {}
|
13
12
|
|
14
13
|
instance_exec(&block)
|
15
14
|
wrap_store_column
|
16
|
-
[self, @
|
15
|
+
[self, @attribute_types, @defaults].each(&:freeze)
|
17
16
|
end
|
18
17
|
|
18
|
+
attr_reader :attribute_types
|
19
|
+
|
19
20
|
# Get a list of the attributes managed by this store. Pass an optional `type` argument
|
20
21
|
# to filter attributes by their type.
|
21
22
|
#
|
@@ -33,13 +34,13 @@ module SerializeAttributes
|
|
33
34
|
#
|
34
35
|
#
|
35
36
|
def attribute_names(type: nil, array: nil)
|
36
|
-
attributes = @
|
37
|
-
attributes = @
|
37
|
+
attributes = @attribute_types
|
38
|
+
attributes = @attribute_types.select { |_, v| v.is_a?(ArrayWrapper) == array } unless array.nil?
|
38
39
|
if type
|
39
40
|
attributes_for_type(attributes, type)
|
40
41
|
else
|
41
42
|
attributes
|
42
|
-
end.keys
|
43
|
+
end.keys.map(&:to_sym)
|
43
44
|
end
|
44
45
|
|
45
46
|
# Get a list of enumerated options for the column `name` in this store.
|
@@ -47,46 +48,44 @@ module SerializeAttributes
|
|
47
48
|
# Model.serialized_attributes_store(:settings).enum_options(:enumy)
|
48
49
|
# => [nil, "placed", "confirmed"]
|
49
50
|
def enum_options(name)
|
50
|
-
type = @
|
51
|
+
type = @attribute_types.fetch(name.to_s)
|
51
52
|
raise ArgumentError, "`#{name}` attribute is not an enum type" unless type.respond_to?(:options)
|
52
53
|
|
53
54
|
type.options
|
54
55
|
end
|
55
56
|
|
56
|
-
# Cast a stored attribute against a given name
|
57
|
+
# Cast a stored attribute against a given name into an
|
58
|
+
# ActiveModel::Attribute::FromUser object (the cast value can be got using `#value`).
|
57
59
|
#
|
58
|
-
# Model.serialized_attributes_store(:settings).cast(:user_name, 42)
|
60
|
+
# Model.serialized_attributes_store(:settings).cast(:user_name, 42).value
|
59
61
|
# => "42"
|
60
62
|
def cast(name, value)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
@attributes.fetch(name.to_sym).cast(value)
|
63
|
+
type = @attributes_types.fetch(name.to_s)
|
64
|
+
|
65
|
+
ActiveModel::Attribute.from_user(name, value, type)
|
65
66
|
end
|
66
67
|
|
67
|
-
# Deserialize a stored attribute using the value from the database (or elsewhere)
|
68
|
+
# Deserialize a stored attribute using the value from the database (or elsewhere) into
|
69
|
+
# an ActiveModel::Attribute::FromDatabase object (the cast value can be got using
|
70
|
+
# `#value`).
|
68
71
|
#
|
69
|
-
# Model.serialized_attributes_store(:settings).deserialize(:subscribed, "0")
|
72
|
+
# Model.serialized_attributes_store(:settings).deserialize(:subscribed, "0").value
|
70
73
|
# => false
|
71
74
|
def deserialize(name, value)
|
72
|
-
|
73
|
-
if
|
75
|
+
type = @attribute_types[name.to_s]
|
76
|
+
if type.nil?
|
74
77
|
raise "The attribute #{name} is not defined in serialize_attribute method in the #{@model_class} class."
|
75
78
|
end
|
76
79
|
|
77
|
-
|
80
|
+
ActiveModel::Attribute.from_database(name, value, type)
|
78
81
|
end
|
79
82
|
|
80
|
-
# Retrieve the default value for a given block.
|
81
|
-
# optionally executed in the context of the model.
|
83
|
+
# Retrieve the default value for a given block.
|
82
84
|
#
|
83
85
|
# Model.serialized_attributes_store(:settings).default(:subscribed)
|
84
86
|
# #=> false
|
85
|
-
def default(name
|
86
|
-
|
87
|
-
return (context || self).instance_exec(&given) if given.is_a?(Proc)
|
88
|
-
|
89
|
-
given
|
87
|
+
def default(name)
|
88
|
+
@defaults[name.to_s]
|
90
89
|
end
|
91
90
|
|
92
91
|
private
|
@@ -102,7 +101,7 @@ module SerializeAttributes
|
|
102
101
|
NO_DEFAULT = Object.new
|
103
102
|
|
104
103
|
def attribute(name, type, default: NO_DEFAULT, array: false, **type_options)
|
105
|
-
name = name.
|
104
|
+
name = name.to_s
|
106
105
|
type = ActiveModel::Type.lookup(type, **type_options) if type.is_a?(Symbol)
|
107
106
|
|
108
107
|
if array
|
@@ -111,7 +110,7 @@ module SerializeAttributes
|
|
111
110
|
type = ArrayWrapper.new(type)
|
112
111
|
end
|
113
112
|
|
114
|
-
@
|
113
|
+
@attribute_types[name] = type
|
115
114
|
|
116
115
|
if default != NO_DEFAULT
|
117
116
|
@defaults[name] = default
|
@@ -122,68 +121,56 @@ module SerializeAttributes
|
|
122
121
|
type.attach_validations_to(@model_class, name) if type.respond_to?(:attach_validations_to)
|
123
122
|
|
124
123
|
@model_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
125
|
-
def #{name}
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
.default(:#{name}, self) # .default(:user_name, self)
|
142
|
-
end # end
|
143
|
-
end # end
|
144
|
-
#
|
145
|
-
unless #{array} # unless array
|
146
|
-
def #{name}? # def user_name?
|
147
|
-
query_attribute("#{name}") # query_attribute(:user_name)
|
148
|
-
end # end
|
149
|
-
end # end
|
150
|
-
#
|
151
|
-
def #{name}=(value) # def user_name=(value)
|
152
|
-
cast_value = self.class # cast_value = self.class
|
153
|
-
.serialized_attributes_store(:#{@column_name}) # .serialized_attributes_store(:settings)
|
154
|
-
.cast(:#{name}, value) # .cast(:user_name, value)
|
155
|
-
store = public_send(:#{@column_name}) # store = public_send(:settings)
|
156
|
-
#
|
157
|
-
if #{array} && cast_value == ArrayWrapper::EMPTY # if array && cast_value == ArrayWrapper::EMPTY
|
158
|
-
store.delete("#{name}") # store.delete("user_name")
|
159
|
-
else # else
|
160
|
-
store.merge!("#{name}" => cast_value) # store.merge!("user_name" => cast_value)
|
161
|
-
end # end
|
162
|
-
public_send(:#{@column_name}=, store) # public_send(:settings=, store)
|
163
|
-
#
|
164
|
-
values_before_typecast = store.values # values_before_typecast = store.values
|
165
|
-
values_after_typecast = # values_after_typecast =
|
166
|
-
public_send(:#{@column_name}).values # public_send(:settings).values
|
167
|
-
@_bad_typcasting = # @_bad_typcasting =
|
168
|
-
values_before_typecast != values_after_typecast # values_before_typecast != values_after_typecast
|
169
|
-
end # end
|
124
|
+
def #{name} # def user_name
|
125
|
+
store = public_send(:#{@column_name}) # store = public_send(:settings)
|
126
|
+
store.fetch_value("#{name}") # store.fetch_value("user_name")
|
127
|
+
end # end
|
128
|
+
|
129
|
+
unless #{array} # unless false
|
130
|
+
def #{name}? # def user_name?
|
131
|
+
query_attribute("#{name}") # query_attribute("user_name")
|
132
|
+
end # end
|
133
|
+
end # end
|
134
|
+
|
135
|
+
def #{name}=(value) # def user_name=(value)
|
136
|
+
store = public_send(:#{@column_name}) # store = public_send(:settings)
|
137
|
+
try(:#{@column_name}_will_change!) # try(:settings_will_change!)
|
138
|
+
store.write_from_user("#{name}", value) # store.write_from_user("user_name", value)
|
139
|
+
end # end
|
170
140
|
RUBY
|
171
141
|
end
|
172
142
|
|
173
143
|
class ArrayWrapper < SimpleDelegator # :nodoc:
|
174
|
-
EMPTY = Object.new
|
175
|
-
|
176
144
|
def cast(value)
|
177
|
-
|
178
|
-
# configured. So we return this special object here, and check it again before
|
179
|
-
# updating the underlying store.
|
180
|
-
return EMPTY unless value
|
145
|
+
return [] if value.nil?
|
181
146
|
|
182
147
|
Array(value)
|
183
148
|
end
|
184
149
|
|
185
150
|
def deserialize(value)
|
186
|
-
value.map { __getobj__.deserialize(_1) }
|
151
|
+
Array.wrap(value).map { __getobj__.deserialize(_1) }
|
152
|
+
end
|
153
|
+
|
154
|
+
# For arrays of strings (the most common array type), the underlying Type::String in
|
155
|
+
# Rails won't do this check if the raw value isn't a String (and returns `nil`):
|
156
|
+
#
|
157
|
+
# def changed_in_place?(a, b)
|
158
|
+
# if a.is_a?(String)
|
159
|
+
# ...
|
160
|
+
#
|
161
|
+
# This means we have to override this check ourselves here.
|
162
|
+
def changed_in_place?(raw_old_value, new_value)
|
163
|
+
raw_old_value != new_value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class AttributeSet < ::ActiveModel::AttributeSet # :nodoc:
|
168
|
+
def ==(other)
|
169
|
+
attributes == if other.is_a?(Hash)
|
170
|
+
other
|
171
|
+
else
|
172
|
+
other.attributes
|
173
|
+
end
|
187
174
|
end
|
188
175
|
end
|
189
176
|
|
@@ -193,29 +180,54 @@ module SerializeAttributes
|
|
193
180
|
@store = store
|
194
181
|
end
|
195
182
|
|
183
|
+
def cast(value)
|
184
|
+
case value
|
185
|
+
when Hash then deserialize(value.stringify_keys)
|
186
|
+
else value
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def changed_in_place?(raw_original_value, new_value)
|
191
|
+
(deserialize(raw_original_value) != new_value) || new_value.each_value.any?(&:changed_in_place?)
|
192
|
+
end
|
193
|
+
|
194
|
+
def serialize(value)
|
195
|
+
super(value.values_for_database)
|
196
|
+
end
|
197
|
+
|
196
198
|
def deserialize(...)
|
197
|
-
result =
|
199
|
+
result = super
|
198
200
|
return result unless @store && result.respond_to?(:each)
|
199
201
|
|
200
|
-
|
201
|
-
|
202
|
-
|
202
|
+
AttributeSet.new(
|
203
|
+
@store.attribute_types.each_with_object({}) do |(attribute, type), out|
|
204
|
+
out[attribute] = if result.key?(attribute)
|
205
|
+
@store.deserialize(attribute, result[attribute])
|
206
|
+
else
|
207
|
+
ActiveModel::Attribute.from_user(attribute, @store.default(attribute), type)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
)
|
203
211
|
end
|
204
212
|
end
|
205
213
|
|
206
|
-
# This method wraps the original store column and catches
|
214
|
+
# This method wraps the original store column and catches several read/write calls;
|
207
215
|
# this gives us a chance to convert the data in the database back into our types.
|
208
216
|
#
|
209
|
-
# We
|
210
|
-
#
|
217
|
+
# We using the block form of `.attribute` when the schema is lazily loaded (and has
|
218
|
+
# not been loaded yet).
|
211
219
|
def wrap_store_column
|
212
|
-
|
213
|
-
|
214
|
-
|
220
|
+
if respond_to?(:schema_loaded?) && !schema_loaded?
|
221
|
+
store = self
|
222
|
+
@model_class.attribute(@column_name) do
|
223
|
+
original_type = @model_class.attribute_types.fetch(@column_name.to_s)
|
224
|
+
StoreColumnWrapper.new(original_type, store)
|
225
|
+
end
|
215
226
|
|
216
|
-
|
227
|
+
else
|
217
228
|
original_type = @model_class.attribute_types.fetch(@column_name.to_s)
|
218
|
-
StoreColumnWrapper.new(original_type,
|
229
|
+
type = StoreColumnWrapper.new(original_type, self)
|
230
|
+
@model_class.attribute(@column_name, type)
|
219
231
|
end
|
220
232
|
end
|
221
233
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serialize_attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zaikio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|