dynamicschema 2.1.0 → 2.2.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: 1142cd4153d7801dc9b6d5e363001891130cdf06b47f7b33b7d2c93e7285400f
4
- data.tar.gz: 3c5d76c15ff02f845bc35c633d52bc04daa6dae9fc5b0a14b338658cdaaa8212
3
+ metadata.gz: 02ef93530db92a0a71dbd58cb6fef978b72d11d6d92704d4ca9714bc2a329beb
4
+ data.tar.gz: 0a846b974a9c41dfe5dd3caf1034f81a75974bbf9eb1b00b743f5f16bfb1fd06
5
5
  SHA512:
6
- metadata.gz: 220295ed04510fde286848d17a9723ac3b30d9cb4cd7daa312c25e81bb6f647b1121ceacd83348e179cd3c7e7cc57d850a5505906f3584d65355610c85988845
7
- data.tar.gz: 32c4c1f21b34c469ed0103d96e4da57b7861182ed6f0fa2b6f65b7bf64f73ff087b49a2a041cbbc0d7e4427d7d2571069840a529d6684ac16b46a8e84754e703
6
+ metadata.gz: f6ef7406250f00130c77dcf5bd1c025d7185616d96a6d96193979c2c2ad57098bb3b80952225759b8d07baf537d2cd3c0746ea4b23d504e12937fcf19d7353bd
7
+ data.tar.gz: 943d6dd203630808c5838f484b4c77fa6b0511b2d23dd24e076ddb8aa22c9fbea74d05fb291b7c915b3c176db99f7eff9afb19a27e5b65c90d823744328b7237
data/README.md CHANGED
@@ -55,6 +55,7 @@ You can find a full OpenAI request example in the `/examples` folder of this rep
55
55
  - [as Option](#as-option)
56
56
  - [in Option (Values Only)](#in-option)
57
57
  - [arguments Option](#arguments-option)
58
+ - [normalize Option](#normalize-option)
58
59
  - [Class Schema](#class-schemas)
59
60
  - [Definable](#definable)
60
61
  - [Buildable](#buildable)
@@ -718,6 +719,91 @@ result = schema.build! do
718
719
  end
719
720
  ```
720
721
 
722
+ ### :normalize Option
723
+
724
+ The `:normalize` option allows you to transform values immediately upon assignment. It accepts a lambda or Proc that receives the value and returns the transformed result. When used with arrays, the normalizer is called for each individual item, not the array itself.
725
+
726
+ #### example:
727
+
728
+ ```ruby
729
+ schema = DynamicSchema.define do
730
+ name String, normalize: ->( v ) { v.strip.downcase }
731
+ tags String, array: true, normalize: ->( v ) { v.upcase }
732
+ score Integer, normalize: ->( v ) { v.clamp( 0, 100 ) }
733
+ end
734
+
735
+ result = schema.build! do
736
+ name ' Alice '
737
+ tags [ 'important', 'urgent' ]
738
+ score 150
739
+ end
740
+
741
+ puts result[:name] # => "alice"
742
+ puts result[:tags] # => ["IMPORTANT", "URGENT"]
743
+ puts result[:score] # => 100
744
+ ```
745
+
746
+ The normalizer is applied after type coercion, so you receive the properly typed value:
747
+
748
+ ```ruby
749
+ schema = DynamicSchema.define do
750
+ count Integer, normalize: ->( v ) { v * 2 }
751
+ end
752
+
753
+ result = schema.build! do
754
+ count '5' # coerced to Integer first, then normalized
755
+ end
756
+
757
+ puts result[:count] # => 10
758
+ ```
759
+
760
+ You can also use normalize with objects to transform the entire nested structure:
761
+
762
+ ```ruby
763
+ CustomUser = Struct.new( :name, :email )
764
+
765
+ schema = DynamicSchema.define do
766
+ user normalize: ->( v ) { CustomUser.new( v.to_h[:name], v.to_h[:email] ) } do
767
+ name String
768
+ email String
769
+ end
770
+ end
771
+
772
+ result = schema.build! do
773
+ user do
774
+ name 'Alice'
775
+ email 'alice@example.com'
776
+ end
777
+ end
778
+
779
+ result[:user].class # => CustomUser
780
+ result[:user].name # => "Alice"
781
+ ```
782
+
783
+ The normalize option works with Struct as well:
784
+
785
+ ```ruby
786
+ Person = DynamicSchema::Struct.define do
787
+ name String, normalize: ->( v ) { v.strip.capitalize }
788
+ tags String, array: true, normalize: ->( v ) { v.downcase }
789
+ end
790
+
791
+ person = Person.build( name: ' alice ', tags: [ 'ADMIN', 'USER' ] )
792
+ person.name # => "Alice"
793
+ person.tags # => ["admin", "user"]
794
+ ```
795
+
796
+ Note that any exceptions raised within a normalize lambda are not captured and will propagate to the caller:
797
+
798
+ ```ruby
799
+ schema = DynamicSchema.define do
800
+ value Integer, normalize: ->( v ) { raise "invalid value" if v < 0; v }
801
+ end
802
+
803
+ # This will raise RuntimeError: "invalid value"
804
+ schema.build! { value -1 }
805
+ ```
806
+
721
807
  ## Class Schemas
722
808
 
723
809
  DynamicSchema provides a number of modules you can include into your own classes to simplify their definition and construction.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do | spec |
2
2
 
3
3
  spec.name = 'dynamicschema'
4
- spec.version = '2.1.0'
4
+ spec.version = '2.2.0'
5
5
  spec.authors = [ 'Kristoph Cichocki-Romanov' ]
6
6
  spec.email = [ 'rubygems.org@kristoph.net' ]
7
7
 
@@ -167,43 +167,45 @@ module DynamicSchema
167
167
  value = arguments.first
168
168
  new_value = criteria[ :type ] ? __coerce_value( criteria[ :type ], value ) : value
169
169
  new_value = new_value.nil? ? value : new_value
170
- block ?
171
- __value_block( method, value: new_value, criteria: criteria, &block ) :
170
+ new_value = block ?
171
+ __value_block( method, value: new_value, criteria: criteria, &block ) :
172
172
  new_value
173
+ __normalize( new_value, criteria )
173
174
  end
174
175
 
175
176
  def __values_array( method, arguments, value:, criteria:, &block )
176
177
  values = [ arguments.first ].flatten
177
178
  if type = criteria[ :type ]
178
- values = values.map do | v |
179
+ values = values.map do | v |
179
180
  new_value = __coerce_value( type, v )
180
181
  new_value.nil? ? v : new_value
181
- end
182
+ end
182
183
  end
183
- if block
184
- values = values.map do | value |
184
+ if block
185
+ values = values.map do | value |
185
186
  __value_block( method, value: value, criteria: criteria, &block )
186
187
  end
187
188
  end
189
+ values = values.map { | v | __normalize( v, criteria ) }
188
190
  value.concat( values )
189
191
  end
190
192
 
191
193
  def __object( method, arguments, value:, criteria:, &block )
192
- attributes = __process_arguments(
193
- method, arguments,
194
- required_arguments: criteria[ :arguments ]
194
+ attributes = __process_arguments(
195
+ method, arguments,
196
+ required_arguments: criteria[ :arguments ]
195
197
  )
196
- if value.nil? || attributes&.any?
197
- value =
198
- Receiver::Object.new(
198
+ if value.nil? || attributes&.any?
199
+ value =
200
+ Receiver::Object.new(
199
201
  attributes,
200
- converter: @converter,
202
+ converter: @converter,
201
203
  schema: criteria[ :schema ] ||= criteria[ :compiler ].compiled,
202
204
  defaults: @defaults
203
205
  )
204
206
  end
205
207
  value.instance_eval( &block ) if block
206
- value
208
+ __normalize( value, criteria )
207
209
  end
208
210
 
209
211
  def __object_array( method, arguments, value:, criteria:, &block )
@@ -219,7 +221,7 @@ module DynamicSchema
219
221
  defaults: @defaults
220
222
  )
221
223
  receiver.instance_eval( &block ) if block
222
- receiver
224
+ __normalize( receiver, criteria )
223
225
  } )
224
226
  end
225
227
 
@@ -296,6 +298,12 @@ module DynamicSchema
296
298
  value
297
299
  end
298
300
 
301
+ def __normalize( value, criteria )
302
+ normalize = criteria[ :normalize ]
303
+ return value unless normalize && !value.nil?
304
+ normalize.call( value )
305
+ end
306
+
299
307
  end
300
308
  end
301
309
  end
@@ -47,47 +47,56 @@ module DynamicSchema
47
47
  has_multiple_types = type.is_a?( ::Array ) && criteria[ :compiler ]
48
48
 
49
49
  if has_multiple_types
50
+ _criteria = criteria
50
51
  define_method( property ) do
51
52
  @converted_attributes.fetch( key ) do
52
53
  value = @attributes[ key ]
53
54
  value = default if value.nil?
54
- schema = criteria[ :schema ] ||= ( criteria[ :compiler ]&.compiled )
55
- klass = criteria[ :class ] ||= ( schema ? ::DynamicSchema::Struct.new( schema ) : nil )
55
+ schema = _criteria[ :schema ] ||= ( _criteria[ :compiler ]&.compiled )
56
+ klass = _criteria[ :class ] ||= ( schema ? ::DynamicSchema::Struct.new( schema ) : nil )
56
57
  @converted_attributes[ key ] =
57
- if criteria[ :array ]
58
- __convert_types_array( Array( value ), klass, criteria )
58
+ if _criteria[ :array ]
59
+ __convert_types_array( Array( value ), klass, _criteria )
59
60
  else
60
- __convert_types( value, klass, criteria )
61
+ __normalize( __convert_types( value, klass, _criteria ), _criteria )
61
62
  end
62
63
  end
63
64
  end
64
65
  elsif type == ::Object
66
+ _criteria = criteria
65
67
  define_method( property ) do
66
68
  @converted_attributes.fetch( key ) do
67
69
  value = @attributes[ key ]
68
- schema = criteria[ :schema ] ||= ( criteria[ :compiler ]&.compiled )
70
+ schema = _criteria[ :schema ] ||= ( _criteria[ :compiler ]&.compiled )
69
71
  return value unless schema
70
- klass = criteria[ :class ] ||= ::DynamicSchema::Struct.new( schema )
72
+ klass = _criteria[ :class ] ||= ::DynamicSchema::Struct.new( schema )
71
73
  @converted_attributes[ key ] =
72
- if criteria[ :array ]
73
- Array( value || default ).map { | v | klass.build( v || {} ) }
74
+ if _criteria[ :array ]
75
+ Array( value || default ).map { | v | __normalize( klass.build( v || {} ), _criteria ) }
74
76
  else
75
- klass.build( value || default )
77
+ __normalize( klass.build( value || default ), _criteria )
76
78
  end
77
79
  end
78
80
  end
79
81
  elsif type
82
+ _criteria = criteria
80
83
  define_method( property ) do
81
84
  @converted_attributes.fetch( key ) do
82
85
  value = @attributes[ key ]
83
- @converted_attributes[ key ] = criteria[ :array ] ?
84
- Array( value || default ).map { | v | __convert( v, to: type ) } :
85
- __convert( value || default, to: type )
86
+ @converted_attributes[ key ] = _criteria[ :array ] ?
87
+ Array( value || default ).map { | v | __normalize( __coerce( v, to: type ), _criteria ) } :
88
+ __normalize( __coerce( value || default, to: type ), _criteria )
86
89
  end
87
90
  end
88
91
  else
92
+ _criteria = criteria
89
93
  define_method( property ) do
90
- @attributes[ key ] || default
94
+ value = @attributes[ key ] || default
95
+ if _criteria[ :array ]
96
+ Array( value ).map { | v | __normalize( v, _criteria ) }
97
+ else
98
+ __normalize( value, _criteria )
99
+ end
91
100
  end
92
101
  end
93
102
 
@@ -135,7 +144,7 @@ module DynamicSchema
135
144
  end
136
145
  end
137
146
 
138
- def __convert( value, to: )
147
+ def __coerce( value, to: )
139
148
  self.class.converter.convert( value, to: to ) { | v | to.new( v ) rescue nil }
140
149
  end
141
150
 
@@ -145,12 +154,12 @@ module DynamicSchema
145
154
  klass ? klass.build( value ) : value
146
155
  else
147
156
  scalar_types = __non_object_types( criteria[ :type ] )
148
- __convert( value, to: scalar_types )
157
+ __coerce( value, to: scalar_types )
149
158
  end
150
159
  end
151
160
 
152
161
  def __convert_types_array( values, klass, criteria )
153
- values.map { | v | __convert_types( v, klass, criteria ) }
162
+ values.map { | v | __normalize( __convert_types( v, klass, criteria ), criteria ) }
154
163
  end
155
164
 
156
165
  def __non_object_types( types )
@@ -159,6 +168,12 @@ module DynamicSchema
159
168
  result.length == 1 ? result.first : result
160
169
  end
161
170
 
171
+ def __normalize( value, criteria )
172
+ normalize = criteria[ :normalize ]
173
+ return value unless normalize && !value.nil?
174
+ normalize.call( value )
175
+ end
176
+
162
177
  end
163
178
  __klass.instance_variable_set( :@__compiled_schema, __compiled_schema.dup )
164
179
  __klass.instance_variable_set( :@__converter, __converter )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamicschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristoph Cichocki-Romanov