serialize_attributes 0.4.0 → 0.6.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 +59 -1
- data/lib/serialize_attributes/store.rb +64 -17
- data/lib/serialize_attributes/types/enum.rb +53 -0
- data/lib/serialize_attributes/version.rb +1 -1
- data/lib/serialize_attributes.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60ef1282c29e5ec4ae46242eed3dac95e756ddd5e6c57c95befc19a695bb9a6e
|
4
|
+
data.tar.gz: 9aa223ae50c2c60344844109bb4772a225796c7d70a0e7b355014bf765b402d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 867430cdb67e77c32ef2860fced5b004e80d3ac15300da02010addeff5d8100c0d20c84b0f8a80458041ad93896ccd0b86c8250dc3aad198e874eac9842f2bef
|
7
|
+
data.tar.gz: 5cebc02f04aa76e0d1dca6ecc9b63f645e595710d902d08a207eaf9d13fe11c6c5b6e51f9e52c3990340ada60d24bc1bd6bfdd189dd3137ce0652e8f88850964
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ class MyModel
|
|
7
7
|
serialize_attributes :settings do
|
8
8
|
attribute :user_name, :string
|
9
9
|
attribute :subscribed, :boolean, default: false
|
10
|
-
attribute :subscriptions, :string, array: true
|
10
|
+
attribute :subscriptions, :string, array: true
|
11
11
|
end
|
12
12
|
end
|
13
13
|
```
|
@@ -63,6 +63,16 @@ record
|
|
63
63
|
#=> #<MyModel id: 1, settings: { user_name: "Nick", subscribed: true }>
|
64
64
|
```
|
65
65
|
|
66
|
+
Additionally you can use predicated methods the same way you do with an ActiveRecord's attribute.
|
67
|
+
Indeed behind the curtain we use `ActiveRecord::AttributeMethods::Query`.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
record.subscribed?
|
71
|
+
#=> false
|
72
|
+
record.user_name?
|
73
|
+
#=> true
|
74
|
+
```
|
75
|
+
|
66
76
|
### Getting all of the stored attributes
|
67
77
|
|
68
78
|
Default values are not automatically persisted to the database, so there is a helper
|
@@ -145,6 +155,54 @@ specify a `default` attribute yourself explicitly:
|
|
145
155
|
attribute :emails, :string, array: true, default: ["unknown@example.com"]
|
146
156
|
```
|
147
157
|
|
158
|
+
### Enumerated ("enum") types
|
159
|
+
|
160
|
+
Since enum types are a common thing when managing external data, there is a special enum
|
161
|
+
type defined by the library:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class MyModel
|
165
|
+
serialize_attributes :settings do
|
166
|
+
attribute :state, :enum, of: ["open", "closed"]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
Unlike `ActiveRecord::Enum`, enums here work by attaching an inclusion validator to your
|
172
|
+
model. So for example, with the above code, I'll get a validation failure by default:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
MyModel.new(state: nil).tap(&:valid?).errors
|
176
|
+
#=> { state: "is not included in the list" }
|
177
|
+
```
|
178
|
+
|
179
|
+
If you wish to allow nil values in your enum, you should add it to the `of` collection:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
attribute :state, :enum, of: [nil, "open", "closed"]
|
183
|
+
```
|
184
|
+
|
185
|
+
The column is probably now the source of truth for correct values, so you can also
|
186
|
+
introspect the store to fetch these from elsewhere (e.g. for building documentation):
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
MyModel.serialized_attributes_store(:settings).enum_options(:state)
|
190
|
+
#=> ["open", "closed"]
|
191
|
+
```
|
192
|
+
|
193
|
+
Finally, you can also use complex types within the enum itself, by passing an additional
|
194
|
+
`type:` attribute. Values will then be cast or deserialized per that type, and the result
|
195
|
+
of the casting is what is validated, e.g:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
attribute :state, :enum, of: [nil, true, false], type: :boolean
|
199
|
+
```
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
MyModel.new(state: "f").state
|
203
|
+
#=> false
|
204
|
+
```
|
205
|
+
|
148
206
|
### Usage with ActiveModel alone
|
149
207
|
|
150
208
|
It's also possible to use this library without `ActiveRecord`:
|
@@ -19,16 +19,16 @@ module SerializeAttributes
|
|
19
19
|
# Get a list of the attributes managed by this store. Pass an optional `type` argument
|
20
20
|
# to filter attributes by their type.
|
21
21
|
#
|
22
|
-
# Model.
|
22
|
+
# Model.serialized_attributes_store(:settings).attribute_names
|
23
23
|
# => [:user_name, :subscribed, :subscriptions]
|
24
24
|
#
|
25
|
-
# Model.
|
25
|
+
# Model.serialized_attributes_store(:settings).attribute_names(type: :string)
|
26
26
|
# => [:user_name, :subscriptions]
|
27
27
|
#
|
28
|
-
# Model.
|
28
|
+
# Model.serialized_attributes_store(:settings).attribute_names(type: :string, array: true)
|
29
29
|
# => [:subscriptions]
|
30
30
|
#
|
31
|
-
# Model.
|
31
|
+
# Model.serialized_attributes_store(:settings).attribute_names(type: :string, array: false)
|
32
32
|
# => [:user_name]
|
33
33
|
#
|
34
34
|
#
|
@@ -42,12 +42,26 @@ module SerializeAttributes
|
|
42
42
|
end.keys
|
43
43
|
end
|
44
44
|
|
45
|
+
# Get a list of enumerated options for the column `name` in this store.
|
46
|
+
#
|
47
|
+
# Model.serialized_attributes_store(:settings).enum_options(:enumy)
|
48
|
+
# => [nil, "placed", "confirmed"]
|
49
|
+
def enum_options(name)
|
50
|
+
type = @attributes.fetch(name.to_sym)
|
51
|
+
raise ArgumentError, "`#{name}` attribute is not an enum type" unless type.respond_to?(:options)
|
52
|
+
|
53
|
+
type.options
|
54
|
+
end
|
55
|
+
|
45
56
|
# Cast a stored attribute against a given name
|
46
57
|
#
|
47
58
|
# Model.serialized_attributes_store(:settings).cast(:user_name, 42)
|
48
59
|
# => "42"
|
49
60
|
def cast(name, value)
|
50
|
-
@attributes
|
61
|
+
# @attributes.fetch(name.to_sym) returns the Type as defined in ActiveModel::Type or
|
62
|
+
# raise an error if the type is unknown.
|
63
|
+
# Type::Integer.new.cast("42") => 42
|
64
|
+
@attributes.fetch(name.to_sym).cast(value)
|
51
65
|
end
|
52
66
|
|
53
67
|
# Deserialize a stored attribute using the value from the database (or elsewhere)
|
@@ -55,7 +69,12 @@ module SerializeAttributes
|
|
55
69
|
# Model.serialized_attributes_store(:settings).deserialize(:subscribed, "0")
|
56
70
|
# => false
|
57
71
|
def deserialize(name, value)
|
58
|
-
@attributes[name.to_sym]
|
72
|
+
attribute = @attributes[name.to_sym]
|
73
|
+
if attribute.nil?
|
74
|
+
raise "The attribute #{name} is not defined in serialize_attribute method in the #{@model_class} class."
|
75
|
+
end
|
76
|
+
|
77
|
+
attribute.deserialize(value)
|
59
78
|
end
|
60
79
|
|
61
80
|
# Retrieve the default value for a given block. If the default is a Proc, it can be
|
@@ -80,23 +99,40 @@ module SerializeAttributes
|
|
80
99
|
end
|
81
100
|
end
|
82
101
|
|
83
|
-
|
102
|
+
NO_DEFAULT = Object.new
|
103
|
+
|
104
|
+
def attribute(name, type, default: NO_DEFAULT, array: false, **type_options)
|
84
105
|
name = name.to_sym
|
85
|
-
|
86
|
-
|
87
|
-
|
106
|
+
type = ActiveModel::Type.lookup(type, **type_options) if type.is_a?(Symbol)
|
107
|
+
|
108
|
+
if array
|
109
|
+
raise ArgumentError, "Enum-arrays not currently supported" if type.is_a?(Types::Enum)
|
110
|
+
|
111
|
+
type = ArrayWrapper.new(type)
|
112
|
+
end
|
88
113
|
|
89
114
|
@attributes[name] = type
|
90
115
|
|
91
|
-
if
|
92
|
-
@defaults[name] =
|
93
|
-
elsif
|
116
|
+
if default != NO_DEFAULT
|
117
|
+
@defaults[name] = default
|
118
|
+
elsif array
|
94
119
|
@defaults[name] = []
|
95
120
|
end
|
96
121
|
|
122
|
+
type.attach_validations_to(@model_class, name) if type.respond_to?(:attach_validations_to)
|
123
|
+
|
97
124
|
@model_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
98
125
|
def #{name} # def user_name
|
99
|
-
|
126
|
+
if @_bad_typcasting # if @_bad_typcasting
|
127
|
+
store = # store =
|
128
|
+
read_attribute_before_type_cast( # read_attribute_before_type_cast(
|
129
|
+
:#{@column_name} # :settings
|
130
|
+
) # )
|
131
|
+
@_bad_typcasting = false # @_bad_typcasting = false
|
132
|
+
else # else
|
133
|
+
store = public_send(:#{@column_name}) # store = public_send(:settings)
|
134
|
+
end # end
|
135
|
+
#
|
100
136
|
if store.key?("#{name}") # if store.key?("user_name")
|
101
137
|
store["#{name}"] # store["user_name"]
|
102
138
|
else # else
|
@@ -105,20 +141,31 @@ module SerializeAttributes
|
|
105
141
|
.default(:#{name}, self) # .default(:user_name, self)
|
106
142
|
end # end
|
107
143
|
end # end
|
108
|
-
|
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
|
+
#
|
109
151
|
def #{name}=(value) # def user_name=(value)
|
110
152
|
cast_value = self.class # cast_value = self.class
|
111
153
|
.serialized_attributes_store(:#{@column_name}) # .serialized_attributes_store(:settings)
|
112
154
|
.cast(:#{name}, value) # .cast(:user_name, value)
|
113
155
|
store = public_send(:#{@column_name}) # store = public_send(:settings)
|
114
156
|
#
|
115
|
-
if #{
|
157
|
+
if #{array} && cast_value == ArrayWrapper::EMPTY # if array && cast_value == ArrayWrapper::EMPTY
|
116
158
|
store.delete("#{name}") # store.delete("user_name")
|
117
159
|
else # else
|
118
160
|
store.merge!("#{name}" => cast_value) # store.merge!("user_name" => cast_value)
|
119
161
|
end # end
|
162
|
+
public_send(:#{@column_name}=, store) # public_send(:settings=, store)
|
120
163
|
#
|
121
|
-
|
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
|
122
169
|
end # end
|
123
170
|
RUBY
|
124
171
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
5
|
+
module SerializeAttributes
|
6
|
+
module Types
|
7
|
+
# A custom type which can only hold one of a set of predetermined values.
|
8
|
+
class Enum
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
# Construct a type instance.
|
12
|
+
#
|
13
|
+
# @param of [Array] One or more possible values that this type can take
|
14
|
+
# @param type [Symbol] An optional ActiveModel::Type instance, or symbol, for
|
15
|
+
# casting/uncasting (only required if enum has non-primitive types)
|
16
|
+
#
|
17
|
+
# @example Required to be one of two values
|
18
|
+
# attribute :state, :enum, of: ["placed", "confirmed"]
|
19
|
+
#
|
20
|
+
# @example Optionally allowing nil
|
21
|
+
# attribute :folding, :enum, of: [nil, "top-fold", "bottom-fold"]
|
22
|
+
#
|
23
|
+
# @example Casting input/output using another type
|
24
|
+
# attribute :loves_pizza, :enum, of: [true], type: :boolean
|
25
|
+
# # object.loves_pizza = "t"
|
26
|
+
# #=> true
|
27
|
+
def initialize(of: [], type: nil)
|
28
|
+
@options = of.freeze
|
29
|
+
@type = resolve_type(type)
|
30
|
+
end
|
31
|
+
|
32
|
+
def attach_validations_to(object, field_name)
|
33
|
+
object.validates_inclusion_of(field_name, in: @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
delegate_missing_to :@type
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
UNTYPED_TYPE = ActiveModel::Type::Value.new
|
41
|
+
|
42
|
+
def resolve_type(given)
|
43
|
+
case given
|
44
|
+
in Symbol then ActiveModel::Type.lookup(given)
|
45
|
+
in nil then UNTYPED_TYPE
|
46
|
+
in _ then given
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
ActiveModel::Type.register(:enum, SerializeAttributes::Types::Enum)
|
data/lib/serialize_attributes.rb
CHANGED
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: 0.6.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-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- Rakefile
|
37
37
|
- lib/serialize_attributes.rb
|
38
38
|
- lib/serialize_attributes/store.rb
|
39
|
+
- lib/serialize_attributes/types/enum.rb
|
39
40
|
- lib/serialize_attributes/version.rb
|
40
41
|
homepage: https://github.com/zaikio/serialize_attributes
|
41
42
|
licenses:
|