serialize_attributes 0.4.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 809e8a18bc0152b6f9961215ee10b10769b90c01e7973e094ea238e09e4bacc5
4
- data.tar.gz: fa5e51462f9e9f7a57048dec29c6d09ec041dd1a748242674f6560956c4c91a7
3
+ metadata.gz: f4fa19f41c2a597b312db2432d1df9a2efad508c91feebd885caab60f68304ac
4
+ data.tar.gz: a84b9c79d3bc59c8a416d8c2c3f2f08f46bdf3a25d8de465c8fb61593ce181aa
5
5
  SHA512:
6
- metadata.gz: 5ab61e0943e013c453cec02210764f1dab818e640185c416564b19d4f3e62c85993068e7cb68b1cb268205f42086f8fc65de318aa07c3bdf620dfdf4ea4e6c56
7
- data.tar.gz: 60ac9d6688461a364c81bef28a79b4e03707c00ae20e05b3d5dbbc40a63d93a64e646f73c44a8315ff3aa87b151c710754ed7fede08162b58438e2981257027c
6
+ metadata.gz: c5f6f45154a0b8f28b8712f81714c4d4eccccb9a58b7a54a0d2fe24499c101656d6416d80cf34eac0632de6444b8fe0b57503cbec6a5a2f8ffdea21badd7428c
7
+ data.tar.gz: 336dfe149b07fb43a171d417a79d33f1fc67eec5d6399cb73a803f38f0fc2afa72cafd1da859c489ad1c92551291337599dfb55827678f46c6717845288fc273
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
  ```
@@ -145,6 +145,54 @@ specify a `default` attribute yourself explicitly:
145
145
  attribute :emails, :string, array: true, default: ["unknown@example.com"]
146
146
  ```
147
147
 
148
+ ### Enumerated ("enum") types
149
+
150
+ Since enum types are a common thing when managing external data, there is a special enum
151
+ type defined by the library:
152
+
153
+ ```ruby
154
+ class MyModel
155
+ serialize_attributes :settings do
156
+ attribute :state, :enum, of: ["open", "closed"]
157
+ end
158
+ end
159
+ ```
160
+
161
+ Unlike `ActiveRecord::Enum`, enums here work by attaching an inclusion validator to your
162
+ model. So for example, with the above code, I'll get a validation failure by default:
163
+
164
+ ```ruby
165
+ MyModel.new(state: nil).tap(&:valid?).errors
166
+ #=> { state: "is not included in the list" }
167
+ ```
168
+
169
+ If you wish to allow nil values in your enum, you should add it to the `of` collection:
170
+
171
+ ```ruby
172
+ attribute :state, :enum, of: [nil, "open", "closed"]
173
+ ```
174
+
175
+ The column is probably now the source of truth for correct values, so you can also
176
+ introspect the store to fetch these from elsewhere (e.g. for building documentation):
177
+
178
+ ```ruby
179
+ MyModel.serialized_attributes_store(:settings).enum_options(:state)
180
+ #=> ["open", "closed"]
181
+ ```
182
+
183
+ Finally, you can also use complex types within the enum itself, by passing an additional
184
+ `type:` attribute. Values will then be cast or deserialized per that type, and the result
185
+ of the casting is what is validated, e.g:
186
+
187
+ ```ruby
188
+ attribute :state, :enum, of: [nil, true, false], type: :boolean
189
+ ```
190
+
191
+ ```ruby
192
+ MyModel.new(state: "f").state
193
+ #=> false
194
+ ```
195
+
148
196
  ### Usage with ActiveModel alone
149
197
 
150
198
  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.serialize_attributes_store(:settings).attribute_names
22
+ # Model.serialized_attributes_store(:settings).attribute_names
23
23
  # => [:user_name, :subscribed, :subscriptions]
24
24
  #
25
- # Model.serialize_attributes_store(:settings).attribute_names(type: :string)
25
+ # Model.serialized_attributes_store(:settings).attribute_names(type: :string)
26
26
  # => [:user_name, :subscriptions]
27
27
  #
28
- # Model.serialize_attributes_store(:settings).attribute_names(type: :string, array: true)
28
+ # Model.serialized_attributes_store(:settings).attribute_names(type: :string, array: true)
29
29
  # => [:subscriptions]
30
30
  #
31
- # Model.serialize_attributes_store(:settings).attribute_names(type: :string, array: false)
31
+ # Model.serialized_attributes_store(:settings).attribute_names(type: :string, array: false)
32
32
  # => [:user_name]
33
33
  #
34
34
  #
@@ -42,6 +42,17 @@ 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)
@@ -88,20 +99,28 @@ module SerializeAttributes
88
99
  end
89
100
  end
90
101
 
91
- def attribute(name, type, **options)
102
+ NO_DEFAULT = Object.new
103
+
104
+ def attribute(name, type, default: NO_DEFAULT, array: false, **type_options)
92
105
  name = name.to_sym
93
- arr = options.delete(:array) { false }
94
- type = ActiveModel::Type.lookup(type, **options.except(:default)) if type.is_a?(Symbol)
95
- type = ArrayWrapper.new(type) if arr
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
96
113
 
97
114
  @attributes[name] = type
98
115
 
99
- if options.key?(:default)
100
- @defaults[name] = options[:default]
101
- elsif arr
116
+ if default != NO_DEFAULT
117
+ @defaults[name] = default
118
+ elsif array
102
119
  @defaults[name] = []
103
120
  end
104
121
 
122
+ type.attach_validations_to(@model_class, name) if type.respond_to?(:attach_validations_to)
123
+
105
124
  @model_class.module_eval <<~RUBY, __FILE__, __LINE__ + 1
106
125
  def #{name} # def user_name
107
126
  if @_bad_typcasting # if @_bad_typcasting
@@ -129,7 +148,7 @@ module SerializeAttributes
129
148
  .cast(:#{name}, value) # .cast(:user_name, value)
130
149
  store = public_send(:#{@column_name}) # store = public_send(:settings)
131
150
  #
132
- if #{arr} && cast_value == ArrayWrapper::EMPTY # if arr && cast_value == ArrayWrapper::EMPTY
151
+ if #{array} && cast_value == ArrayWrapper::EMPTY # if array && cast_value == ArrayWrapper::EMPTY
133
152
  store.delete("#{name}") # store.delete("user_name")
134
153
  else # else
135
154
  store.merge!("#{name}" => cast_value) # store.merge!("user_name" => cast_value)
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SerializeAttributes
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "serialize_attributes/version"
4
4
  require "serialize_attributes/store"
5
+ require "serialize_attributes/types/enum"
5
6
 
6
7
  # Serialize ActiveModel attributes in JSON using type casting
7
8
  module SerializeAttributes
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.1
4
+ version: 0.5.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-07-13 00:00:00.000000000 Z
11
+ date: 2022-07-25 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: