validated_object 2.3.3 → 2.3.4
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 -1
- data/lib/validated_object/simplified_api.rb +16 -8
- data/lib/validated_object/version.rb +1 -1
- data/lib/validated_object.rb +77 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8938191d6ee1e84b60f0c659674952d9fbb56c7ea01bc2672194c8aee3120bf4
|
4
|
+
data.tar.gz: 2dd18069152f37b629030d10e0d46408bdec7369c36ed4407cbaa36baf2ff297
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: beb309f24b778cf42ea63d503669a68ff71d0bc2432fb961ce32bfb1f84b32bf9a7fe8acf1a62dc00f84aaf5cbaa95954720d4a22faf5e9b4dda439d6ef425a3
|
7
|
+
data.tar.gz: 5e8b97e5ff1e46279b0e81da5b7e7a110732c7c68507638e822f39fff3059f7da02d619a33ba1fb403bdfe21d8c9b5f4b4ce77f7fac9adf4048f6d2199ba7561
|
data/README.md
CHANGED
@@ -154,7 +154,7 @@ the data.
|
|
154
154
|
|
155
155
|
### Use in code generation
|
156
156
|
|
157
|
-
|
157
|
+
The [Schema.org structured data gem](https://github.com/public-law/schema-dot-org) uses ValidatedObjects to recursively create well formed HTML / JSON-LD.
|
158
158
|
|
159
159
|
## Installation
|
160
160
|
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
2
4
|
|
3
|
-
# Enable a simplified API for the common case of
|
4
|
-
# read-only ValidatedObjects.
|
5
5
|
module ValidatedObject
|
6
|
+
# Enable a simplified API for the common case of
|
7
|
+
# read-only ValidatedObjects.
|
6
8
|
module SimplifiedApi
|
7
9
|
extend ActiveSupport::Concern
|
8
10
|
|
@@ -19,7 +21,6 @@ module ValidatedObject
|
|
19
21
|
validates(*args, **kwargs, &block)
|
20
22
|
end
|
21
23
|
|
22
|
-
# Alias for validated_attr for compatibility with test usage.
|
23
24
|
def validates_attr(attribute, *options, **kwargs)
|
24
25
|
attr_reader attribute
|
25
26
|
|
@@ -27,16 +28,23 @@ module ValidatedObject
|
|
27
28
|
type_val = kwargs.delete(:type)
|
28
29
|
element_type = kwargs.delete(:element_type)
|
29
30
|
|
31
|
+
# Handle Union types - pass them through directly
|
32
|
+
if type_val.is_a?(ValidatedObject::Base::Union)
|
33
|
+
opts = { type: { with: type_val } }
|
34
|
+
validates attribute, opts.merge(kwargs)
|
30
35
|
# Parse Array[ElementType] syntax
|
31
|
-
|
36
|
+
elsif type_val.is_a?(Array) && type_val.length == 1 && type_val[0].is_a?(Class)
|
32
37
|
# This handles Array[Comment] syntax
|
33
38
|
element_type = type_val[0]
|
34
39
|
type_val = Array
|
40
|
+
opts = { type: { with: type_val } }
|
41
|
+
opts[:type][:element_type] = element_type if element_type
|
42
|
+
validates attribute, opts.merge(kwargs)
|
43
|
+
else
|
44
|
+
opts = { type: { with: type_val } }
|
45
|
+
opts[:type][:element_type] = element_type if element_type
|
46
|
+
validates attribute, opts.merge(kwargs)
|
35
47
|
end
|
36
|
-
|
37
|
-
opts = { type: { with: type_val } }
|
38
|
-
opts[:type][:element_type] = element_type if element_type
|
39
|
-
validates attribute, opts.merge(kwargs)
|
40
48
|
else
|
41
49
|
validates attribute, *options, **kwargs
|
42
50
|
end
|
data/lib/validated_object.rb
CHANGED
@@ -54,6 +54,21 @@ module ValidatedObject
|
|
54
54
|
class Boolean
|
55
55
|
end
|
56
56
|
|
57
|
+
# A private class definition for union types.
|
58
|
+
# Stores multiple allowed types for validation.
|
59
|
+
# Created via ValidatedObject::Base.union(*types)
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# validates :id, type: union(String, Integer)
|
63
|
+
# validates :data, type: union(Hash, [Hash])
|
64
|
+
class Union
|
65
|
+
attr_reader :types
|
66
|
+
|
67
|
+
def initialize(*types)
|
68
|
+
@types = types
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
57
72
|
# Instantiate and validate a new object.
|
58
73
|
# @example
|
59
74
|
# maru = Dog.new(birthday: Date.today, name: 'Maru')
|
@@ -99,6 +114,14 @@ module ValidatedObject
|
|
99
114
|
validation_options = options
|
100
115
|
expected_class = validation_options[:with]
|
101
116
|
|
117
|
+
# Support union types
|
118
|
+
if expected_class.is_a?(Union)
|
119
|
+
return if validate_union_type(record, attribute, value, expected_class, validation_options)
|
120
|
+
|
121
|
+
save_union_error(record, attribute, value, expected_class, validation_options)
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
102
125
|
# Support type: Array, element_type: ElementType
|
103
126
|
if expected_class == Array && validation_options[:element_type]
|
104
127
|
return save_error(record, attribute, value, validation_options) unless value.is_a?(Array)
|
@@ -134,6 +157,50 @@ module ValidatedObject
|
|
134
157
|
record.errors.add attribute,
|
135
158
|
validation_options[:message] || "is a #{value.class}, not a #{validation_options[:with]}"
|
136
159
|
end
|
160
|
+
|
161
|
+
def validate_union_type(_record, _attribute, value, union, _validation_options)
|
162
|
+
union.types.any? do |type_spec|
|
163
|
+
if type_spec.is_a?(Array) && type_spec.length == 1
|
164
|
+
# Handle [ElementType] syntax within union
|
165
|
+
validate_array_element_type(value, type_spec[0])
|
166
|
+
elsif type_spec.is_a?(Class) || type_spec == Boolean
|
167
|
+
# Handle class types (String, Integer, etc.) and pseudo-boolean
|
168
|
+
pseudo_boolean?(type_spec, value) || expected_class?(type_spec, value)
|
169
|
+
else
|
170
|
+
# Handle literal values (symbols, strings, numbers, etc.)
|
171
|
+
value == type_spec
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_array_element_type(value, element_type)
|
177
|
+
return false unless value.is_a?(Array)
|
178
|
+
|
179
|
+
value.all? { |el| el.is_a?(element_type) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def save_union_error(record, attribute, value, union, validation_options)
|
183
|
+
return if validation_options[:message]
|
184
|
+
|
185
|
+
type_names = union.types.map do |type_spec|
|
186
|
+
if type_spec.is_a?(Array) && type_spec.length == 1
|
187
|
+
"Array of #{type_spec[0]}"
|
188
|
+
elsif type_spec.is_a?(Class) || type_spec == Boolean
|
189
|
+
type_spec.to_s
|
190
|
+
else
|
191
|
+
# For literal values like :active, show them as-is
|
192
|
+
type_spec.inspect
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
message = if type_names.length == 1
|
197
|
+
"is a #{value.class}, not one of #{type_names.first}"
|
198
|
+
else
|
199
|
+
"is a #{value.class}, not one of #{type_names.join(', ')}"
|
200
|
+
end
|
201
|
+
|
202
|
+
record.errors.add attribute, message
|
203
|
+
end
|
137
204
|
end
|
138
205
|
|
139
206
|
# Register the TypeValidator with ActiveModel so `type:` validation option works
|
@@ -146,6 +213,16 @@ module ValidatedObject
|
|
146
213
|
validates(*args, **kwargs, &block)
|
147
214
|
end
|
148
215
|
|
216
|
+
# Create a union type specification for validation
|
217
|
+
# @param types [Array] The types that are allowed
|
218
|
+
# @return [Union] A union type specification
|
219
|
+
# @example
|
220
|
+
# validates :id, type: union(String, Integer)
|
221
|
+
# validates :data, type: union(Hash, [Hash])
|
222
|
+
def self.union(*types)
|
223
|
+
Union.new(*types)
|
224
|
+
end
|
225
|
+
|
149
226
|
private
|
150
227
|
|
151
228
|
def set_instance_variables(from_hash:)
|