serialize_attributes 0.6.0 → 1.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.
- checksums.yaml +4 -4
- data/README.md +1 -6
- data/lib/serialize_attributes/store.rb +92 -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: 9264e3f62465ae9a4a27bf3afa14fa9cd516d14eab7b93e316c56d6cfb88ab4b
|
4
|
+
data.tar.gz: 69bf1f4d1ccf209aabee4bb0ee5f128cc6ff93c3bb59dbbe295b39517c2cd5d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 045a9d1d63e862b7eadb8372d68c002ae2c1c6cf32b00157c5c97d9970012f3c82c5d944a4cc55a632a693c7241875e9355953586d23d262f9355f61f1afc3bc
|
7
|
+
data.tar.gz: d9992e49647e63d28b44d5c29e2c74936e7ee1fc305243c92bed744d579142c05c0475c26ce976cbce079f4ba29a67a48cd2cbd43729872daac337ecc45aefd7
|
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,44 @@ 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
|
+
end
|
154
|
+
|
155
|
+
class AttributeSet < ::ActiveModel::AttributeSet # :nodoc:
|
156
|
+
def ==(other)
|
157
|
+
attributes == if other.is_a?(Hash)
|
158
|
+
other
|
159
|
+
else
|
160
|
+
other.attributes
|
161
|
+
end
|
187
162
|
end
|
188
163
|
end
|
189
164
|
|
@@ -193,29 +168,54 @@ module SerializeAttributes
|
|
193
168
|
@store = store
|
194
169
|
end
|
195
170
|
|
171
|
+
def cast(value)
|
172
|
+
case value
|
173
|
+
when Hash then deserialize(value.stringify_keys)
|
174
|
+
else value
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def changed_in_place?(raw_original_value, new_value)
|
179
|
+
AttributeSet.new(raw_original_value) != new_value
|
180
|
+
end
|
181
|
+
|
182
|
+
def serialize(value)
|
183
|
+
super(value.values_for_database)
|
184
|
+
end
|
185
|
+
|
196
186
|
def deserialize(...)
|
197
|
-
result =
|
187
|
+
result = super
|
198
188
|
return result unless @store && result.respond_to?(:each)
|
199
189
|
|
200
|
-
|
201
|
-
|
202
|
-
|
190
|
+
AttributeSet.new(
|
191
|
+
@store.attribute_types.each_with_object({}) do |(attribute, type), out|
|
192
|
+
out[attribute] = if result.key?(attribute)
|
193
|
+
@store.deserialize(attribute, result[attribute])
|
194
|
+
else
|
195
|
+
ActiveModel::Attribute.from_user(attribute, @store.default(attribute), type)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
)
|
203
199
|
end
|
204
200
|
end
|
205
201
|
|
206
|
-
# This method wraps the original store column and catches
|
202
|
+
# This method wraps the original store column and catches several read/write calls;
|
207
203
|
# this gives us a chance to convert the data in the database back into our types.
|
208
204
|
#
|
209
|
-
# We
|
210
|
-
#
|
205
|
+
# We using the block form of `.attribute` when the schema is lazily loaded (and has
|
206
|
+
# not been loaded yet).
|
211
207
|
def wrap_store_column
|
212
|
-
|
213
|
-
|
214
|
-
|
208
|
+
if respond_to?(:schema_loaded?) && !schema_loaded?
|
209
|
+
store = self
|
210
|
+
@model_class.attribute(@column_name) do
|
211
|
+
original_type = @model_class.attribute_types.fetch(@column_name.to_s)
|
212
|
+
StoreColumnWrapper.new(original_type, store)
|
213
|
+
end
|
215
214
|
|
216
|
-
|
215
|
+
else
|
217
216
|
original_type = @model_class.attribute_types.fetch(@column_name.to_s)
|
218
|
-
StoreColumnWrapper.new(original_type,
|
217
|
+
type = StoreColumnWrapper.new(original_type, self)
|
218
|
+
@model_class.attribute(@column_name, type)
|
219
219
|
end
|
220
220
|
end
|
221
221
|
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.0
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|