serialize_attributes 0.4.1 → 0.5.0

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