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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '039b75227869c70e8767ab8e48b5f355d9e048f7b6a18cacec6ca679259f0097'
4
- data.tar.gz: 7b16daca0ce8d9cb81af01ce6d6a984cadbc7ef36ee2b7a72b1ac67aed854415
3
+ metadata.gz: 8938191d6ee1e84b60f0c659674952d9fbb56c7ea01bc2672194c8aee3120bf4
4
+ data.tar.gz: 2dd18069152f37b629030d10e0d46408bdec7369c36ed4407cbaa36baf2ff297
5
5
  SHA512:
6
- metadata.gz: b7766bafdcb4e6d23890584bffac5c1ce7744d5166d29061a3247b2b377f52195f80531028c041db8a98f80a2b675411528804058e7fc7b008327de96c14e2e6
7
- data.tar.gz: 007c20d017c7ceaf708d69f05f8c1fc19c1c262313550670f8ce1469df2641e4c78799cac90301e6cea94acfa9d7ad8489d6fba034a440b9e08ece2a0e37abb3
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
- My [Schema.org structured data gem](https://github.com/public-law/schema-dot-org) uses ValidatedObjects to recursively create well formed HTML / JSON-LD.
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
- if type_val.is_a?(Array) && type_val.length == 1 && type_val[0].is_a?(Class)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ValidatedObject
4
- VERSION = '2.3.3'
4
+ VERSION = '2.3.4'
5
5
  end
@@ -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:)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validated_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.3
4
+ version: 2.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robb Shecter