serialize_attributes 0.6.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|