dynamicschema 1.0.1 → 2.1.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.
data/README.md CHANGED
@@ -1,39 +1,34 @@
1
1
  # DynamicSchema
2
2
 
3
- The **DynamicSchema** gem provides a elegant and expressive way to define a domain-specific
4
- language (DSL) schemas, making it effortless to build and validate complex Ruby `Hash`
5
- constructs.
3
+ The **DynamicSchema** gem provides an elegant and expressive way to define domain-specific language (DSL) schemas, making it effortless to build and validate complex Ruby `Hash` constructs.
6
4
 
7
- This is particularly useful when dealing with intricate configuration or interfacing with
8
- external APIs, where data structures need to adhere to specific formats and validations.
9
- By allowing default values, type constraints, nested schemas, and transformations,
10
- DynamicSchema ensures that your data structures are both robust and flexible.
5
+ This is particularly useful when dealing with intricate configuration or interfacing with external APIs, where data structures need to adhere to specific formats and validations. By allowing default values, type constraints, nested schemas, and transformations, DynamicSchema ensures that your data structures are both robust and flexible.
11
6
 
12
7
  You can trivially define a custom schema:
13
8
 
14
9
  ```ruby
15
- openai_request_schema = DynamicSchema.define do
10
+ openai_request_schema = DynamicSchema.define do
16
11
  model String, default: 'gpt-4o'
17
12
  max_tokens Integer, default: 1024
18
13
  temperature Float, in: 0..1
19
14
 
20
- message arguments: [ :role ], as: :messages, array: true do
15
+ message arguments: [ :role ], as: :messages, array: true do
21
16
  role Symbol, in: [ :system, :user, :assistant ]
22
- content array: true do
23
- type Symbol, default: :text
17
+ content array: true do
18
+ type Symbol, default: :text
24
19
  text String
25
20
  end
26
21
  end
27
22
  end
28
23
  ```
29
24
 
30
- And then repetedly use that schema to elegantly build a schema conformant `Hash`:
25
+ And then repeatedly use that schema to elegantly build a schema-conformant `Hash`:
31
26
  ```ruby
32
27
  request = openai_request_schema.build {
33
- message :system do
28
+ message :system do
34
29
  content text: "You are a helpful assistant that talks like a pirate."
35
30
  end
36
- message :user do
31
+ message :user do
37
32
  content text: ARGV[0] || "say hello!"
38
33
  end
39
34
  }
@@ -50,6 +45,9 @@ You can find a full OpenAI request example in the `/examples` folder of this rep
50
45
  - [Values](#values)
51
46
  - [Objects](#objects)
52
47
  - [Types](#types)
48
+ - [Custom Types](#custom-types)
49
+ - [Multiple Types](#multiple-types)
50
+ - [Multiple Types with Nested Schema](#multiple-types-with-nested-schema)
53
51
  - [Options](#options)
54
52
  - [default Option](#default-option)
55
53
  - [required Option](#required-option)
@@ -58,10 +56,11 @@ You can find a full OpenAI request example in the `/examples` folder of this rep
58
56
  - [in Option (Values Only)](#in-option)
59
57
  - [arguments Option](#arguments-option)
60
58
  - [Class Schema](#class-schemas)
61
- - [Definable](#definable)
62
- - [Buildable](#buildable)
59
+ - [Definable](#definable)
60
+ - [Buildable](#buildable)
61
+ - [Struct](#struct)
63
62
  - [Validation Methods](#validation-methods)
64
- - [Validation Rules](#validation-rules)
63
+ - [Validation Rules](#validation-rules)
65
64
  - [validate!](#validate)
66
65
  - [validate](#validate-1)
67
66
  - [valid?](#valid)
@@ -103,35 +102,181 @@ require 'dynamic_schema'
103
102
 
104
103
  ### Defining Schemas with **DynamicSchema**
105
104
 
106
- DynamicSchema permits the caller to define a domain specific language (DSL) schema with *values*,
107
- *objects* and related *options*. You can use the `DynamicSchema.define` convenience method, or
108
- instantiate `DynamicSchema::Builder`, then call it's `define` method, to prepare a builder.
105
+ DynamicSchema lets you define a DSL made of values, objects, and options, then reuse that DSL to build and validate Ruby Hashes.
106
+
107
+ You start by constructing a `DynamicSchema::Builder`. You can do this by calling:
108
+ - `DynamicSchema.define { … }`
109
+ - `DynamicSchema::Builder.new.define { … }`
110
+
111
+ In both cases, you pass a block that declares the schema values and objects with their options.
112
+
113
+ ```ruby
114
+ schema = DynamicSchema.define do
115
+ # values with an optional default
116
+ api_key String
117
+ model String, default: 'gpt-4o'
118
+
119
+ # object with its own values
120
+ chat_options do
121
+ max_tokens Integer, default: 1024
122
+ temperature Float, in: 0..1
123
+ end
124
+ end
125
+ ```
109
126
 
110
- In all cases the `define` methods require a block where the names of schema components as well as
111
- their options are specified.
127
+ You can then:
128
+
129
+ ```ruby
130
+ # build without validation
131
+ built = schema.build do
132
+ api_key 'secret'
133
+ chat_options do
134
+ temperature 0.7
135
+ end
136
+ end
137
+
138
+ # build with validation (raises on first error)
139
+ built_validated = schema.build! do
140
+ api_key 'secret'
141
+ chat_options do
142
+ temperature 0.7
143
+ end
144
+ end
145
+
146
+ # validate an existing Hash (no building)
147
+ errors = schema.validate( { api_key: 'secret', chat_options: { temperature: 0.7 } } )
148
+ valid = schema.valid?( { api_key: 'secret', chat_options: { temperature: 0.7 } } )
149
+ ```
112
150
 
113
- Once a schema is defined you may repeatedly use the `Builder` instance to 'build' a Hash of values
114
- using the DSL you've defined. The builder has a 'build' method which will construct a Hash without
115
- validating the values. If you've specified that a value should be of a specific type and an
116
- incompatible type was given that type will be in the Hash with no indication of that violation.
117
- Alterativelly, you can call the `build!` method which will validate the Hash, raising an exception
118
- if any of the schema criteria is violated.
151
+ #### Inheritance
119
152
 
120
- Finally, you can use a builder to validate a given Hash against the schema you've defined using
121
- the `validate`, `validate!` and `valid?` `Builder` instance methods.
153
+ You can extend an existing schema using the `inherit:` option. Pass a Proc that describes the parent schema—typically from a class that includes `DynamicSchema::Definable` via its `schema` method.
154
+
155
+ ```ruby
156
+ class BaseSettings
157
+ include DynamicSchema::Definable
158
+ schema do
159
+ api_key String, required: true
160
+ end
161
+ end
162
+
163
+ # extend the base schema with additional fields
164
+ builder = DynamicSchema.define( inherit: BaseSettings.schema ) do
165
+ region Symbol, in: %i[us eu apac]
166
+ end
167
+
168
+ settings = builder.build! do
169
+ api_key 'secret'
170
+ region :us
171
+ end
172
+ ```
173
+
174
+ You can call `build`, `build!`, `validate`, `validate!`, and `valid?` on the builder as needed.
122
175
 
123
176
  ---
124
177
 
125
- ## Values
178
+ ## Struct
179
+
180
+ In addition to building plain Ruby `Hash` values, DynamicSchema can generate lightweight Ruby classes from a schema. A `DynamicSchema::Struct` exposes readers and writers for the fields you define, and transparently wraps nested objects so that you can access them with dot-style accessors rather than deep hash indexing.
126
181
 
127
- A *value* is the basic building blocks of your schema. Values represent individual settings,
128
- options or API paramters that you can define with specific types, defaults, and other options.
182
+ You create a struct class by passing the same schema shape you would give to a Builder. The schema can be provided as:
129
183
 
130
- When defining a value, you provide the name as though you were calling a Ruby method, with
131
- arguments that include an optional type (which can be a `Class`, `Module` or an `Array` of these )
132
- as well as a `Hash` of options, all of which are optional:
184
+ - a `Proc` that defines the schema
185
+ - a `DynamicSchema::Builder`
186
+ - a compiled `Hash` (advanced)
133
187
 
134
- `name {type} default: {true|false}, required: {true|false}, array: {true|false}, as: {name}, in: {Array|Range}`
188
+ ```ruby
189
+ require 'dynamic_schema'
190
+
191
+ # simple struct with typed fields
192
+ Person = DynamicSchema::Struct.define do
193
+ full_name String
194
+ age Integer
195
+ end
196
+
197
+ person = Person.build( full_name: 'Sam Lee', age: '42' )
198
+ person.age # => 42 (coerced using the same converters as Builder)
199
+ person.full_name = 'Samira Lee'
200
+ person.to_h # => { full_name: 'Samira Lee', age: 42 }
201
+
202
+ # nested object with its own accessors
203
+ Company = DynamicSchema::Struct.define do
204
+ employee do
205
+ full_name String
206
+ years_of_service Integer
207
+ end
208
+ end
209
+
210
+ acme = Company.build( employee: { full_name: 'Alex', years_of_service: 5 } )
211
+ acme.employee.full_name # => 'Alex'
212
+ acme.employee.years_of_service # => 5
213
+
214
+ # array of nested objects
215
+ Order = DynamicSchema::Struct.define do
216
+ items array: true do
217
+ name String
218
+ price Integer
219
+ end
220
+ end
221
+
222
+ order = Order.build( items: [ { name: 'Desk', price: 100 }, { name: 'Chair', price: 50 } ] )
223
+ order.items.map { | i | i.name } # => [ 'Desk', 'Chair' ]
224
+
225
+ # referencing another struct class
226
+ OrderItem = DynamicSchema::Struct.define do
227
+ name String
228
+ quantity Integer
229
+ end
230
+
231
+ OrderCollection = DynamicSchema::Struct.define do
232
+ order_number String
233
+ line_items OrderItem, array: true
234
+ end
235
+
236
+ collection = OrderCollection.new( {
237
+ order_number: 'A-100',
238
+ line_items: [ { name: 'Desk', quantity: 1 }, { name: 'Chair', quantity: 2 } ]
239
+ } )
240
+ collection.line_items[ 0 ].name # => 'Desk'
241
+ collection.line_items[ 1 ].quantity # => 2
242
+ ```
243
+
244
+ - defining
245
+ - `DynamicSchema::Struct.define` takes a block that looks exactly like a Builder schema.
246
+ - Use `array: true` to expose arrays of nested structs.
247
+ - You may reference another struct class as a value type; arrays of that type expose nested accessors for each element.
248
+ - building
249
+ - `StructClass.build( attributes )` constructs an instance and (optionally) coerces typed scalar fields using the same converters as the Builder.
250
+ - `StructClass.build!` additionally validates the instance just like `builder.build!`.
251
+ - accessing
252
+ - Use standard Ruby readers/writers: `instance.attribute`, `instance.attribute = value`.
253
+ - `#to_h` returns a deep Hash of the current values (nested structs become hashes).
254
+
255
+ You can also create a struct class from a builder or a compiled hash if you already have a schema elsewhere:
256
+
257
+ ```ruby
258
+ builder = DynamicSchema.define do
259
+ name String
260
+ end
261
+
262
+ NameStruct = DynamicSchema::Struct.new( builder )
263
+ NameStruct.build( name: 'Taylor' ).name # => 'Taylor'
264
+ ```
265
+
266
+ - validation
267
+ - struct instances include the same validation helpers as hashes built via a builder.
268
+ - `StructClass.build!` validates immediately and raises on the first error.
269
+ - instances respond to `#validate!`, `#validate`, and `#valid?` using the compiled schema.
270
+
271
+ ---
272
+
273
+ ## Values
274
+
275
+ A *value* is a basic building block of your schema. Values represent individual settings, options or API parameters that you can define with specific types, defaults, and other options.
276
+
277
+ When defining a value, you provide the name as though you were calling a Ruby method, with arguments that include an optional type (which can be a `Class`, `Module` or an `Array` of these) as well as a `Hash` of options, all of which are optional:
278
+
279
+ `name {type}, default: {value}, required: {true|false}, array: {true|false}, as: {name}, in: {Array|Range}`
135
280
 
136
281
  #### example:
137
282
 
@@ -141,7 +286,7 @@ require 'dynamic_schema'
141
286
  # define a schema structure with values
142
287
  schema = DynamicSchema.define do
143
288
  api_key
144
- version, String, default: '1.0'
289
+ version String, default: '1.0'
145
290
  end
146
291
 
147
292
  # build the schema and set values
@@ -156,30 +301,26 @@ puts result[:version] # => "1.0"
156
301
 
157
302
  - defining
158
303
  - `api_key` defines a value named `api_key`. Any type can be used to assign the value.
159
- - `version, String, default: '1.0'` defines a value with a default.
160
- - building
161
- - `schema.build!` build accepts both a Hash and a block where you can set the values.
304
+ - `version String, default: '1.0'` defines a value with a default.
305
+ - building
306
+ - `schema.build!` accepts both a Hash and a block where you can set the values.
162
307
  - Inside the block, `api_key 'your-api-key'` sets the value of `api_key`.
163
- - accessing
308
+ - accessing
164
309
  - `result[:api_key]` retrieves the value of `api_key`.
165
- - If a value has a default and you don't set it, the default value will be included in
166
- resulting hash.
310
+ - If a value has a default and you don't set it, the default value will be included in resulting hash.
167
311
 
168
312
  ---
169
313
 
170
314
  ## Objects
171
315
 
172
- A schema may be organized hierarchically, by creating collections of related values and
173
- even other collections. These collections are called objects.
316
+ A schema may be organized hierarchically, by creating collections of related values and even other collections. These collections are called objects.
174
317
 
175
- An *object* is defined in a similar manner to a value. Simply provide the name as though
176
- calling a Ruby method, with a Hash of options and a block which encloses the child values
177
- and objects:
318
+ An *object* is defined in a similar manner to a value. Simply provide the name as though calling a Ruby method, with a Hash of options and a block which encloses the child values and objects:
178
319
 
179
320
  ```
180
- name arguments: [ {argument} ], default: {true|false}, required: {true|false}, array: {true|false}, as: {name} do
321
+ name arguments: [ {argument} ], default: {value}, required: {true|false}, array: {true|false}, as: {name} do
181
322
  # child values and objects can be defined here
182
- end
323
+ end
183
324
  ```
184
325
 
185
326
  Notice an *object* does not accept a type as it is always of type `Object`.
@@ -190,7 +331,7 @@ Notice an *object* does not accept a type as it is always of type `Object`.
190
331
  require 'dynamic_schema'
191
332
 
192
333
  schema = DynamicSchema.define do
193
- api_key, String
334
+ api_key String
194
335
  chat_options do
195
336
  model String, default: 'claude-3'
196
337
  max_tokens Integer, default: 1024
@@ -217,7 +358,7 @@ puts result[:chat_options][:stream] # => true
217
358
  - defining
218
359
  - `chat_options do ... end` defines an object named `chat_options`.
219
360
  - Inside the object you can define values that belong to that object.
220
- - building
361
+ - building
221
362
  - In the build block, you can set values for values within objects by nesting blocks.
222
363
  - `chat_options do ... end` allows you to set values inside the `chat_options` object.
223
364
  - accessing
@@ -225,11 +366,9 @@ puts result[:chat_options][:stream] # => true
225
366
 
226
367
  ---
227
368
 
228
- ## Types
369
+ ## Types
229
370
 
230
- An *object* is always of type `Object`. A *value* can have no type or it can be of one or
231
- more types. You specify the value type by providing an instance of a `Class` when defining
232
- the value. If you want to specify multiple types simply provide an array of types.
371
+ An *object* is always of type `Object`. A *value* can have no type or it can be of one or more types. You specify the value type by providing an instance of a `Class` when defining the value. If you want to specify multiple types simply provide an array of types.
233
372
 
234
373
  #### example:
235
374
 
@@ -238,37 +377,209 @@ require 'dynamic_schema'
238
377
 
239
378
  schema = DynamicSchema.define do
240
379
  typeless_value
241
- symbol_value Symbol
380
+ symbol_value Symbol
242
381
  boolean_value [ TrueClass, FalseClass ]
243
382
  end
244
383
 
245
384
  result = schema.build! do
246
385
  typeless_value Struct.new(:name).new(name: 'Kristoph')
247
386
  symbol_value "something"
248
- boolean_value true
249
- end
387
+ boolean_value true
388
+ end
250
389
 
251
390
  puts result[:typeless_value].name # => "Kristoph"
252
391
  puts result[:symbol_value] # => :something
253
- puts result[:boolean_value] # => true
392
+ puts result[:boolean_value] # => true
254
393
  ```
255
394
 
256
395
  - defining
257
- - `typeless_value` defines a value that has no type and will accept an assignment of any type
258
- - `symbol_value` defines a value that accepts symbols or types that can be coerced into
259
- symbols, such as strings (see **Type Coercion**)
396
+ - `typeless_value` defines a value that has no type and will accept an assignment of any type
397
+ - `symbol_value` defines a value that accepts symbols or types that can be coerced into symbols, such as strings (see **Type Coercion**)
260
398
  - `boolean_value` defines a value that can be either `true` or `false`
261
399
 
262
- ## Options
400
+ ### Custom Types
401
+
402
+ You can use any Ruby class as a value type, not just the built-in types. When a custom class is specified as the type, DynamicSchema will validate that values are instances of that class. You can also configure custom class instances using blocks:
403
+
404
+ ```ruby
405
+ require 'dynamic_schema'
406
+
407
+ class Customer
408
+ attr_accessor :name, :email
409
+ end
263
410
 
264
- Both *values* and *objects* can be customized through *options*. The options for both values and
265
- objects include `default`, `required`, `as` and `array`. In addition values support the `in`
266
- criteria option while objects support the `arguments` option.
411
+ schema = DynamicSchema.define do
412
+ customer Customer
413
+ end
267
414
 
268
- ### :default Option
415
+ # auto-instantiate and configure with a block
416
+ result = schema.build! do
417
+ customer do
418
+ name 'Alice'
419
+ email 'alice@example.com'
420
+ end
421
+ end
269
422
 
270
- The `:default` option allows you to specify a default value that will be used if no value is
271
- provided during build.
423
+ result[:customer].name # => 'Alice'
424
+ result[:customer].email # => 'alice@example.com'
425
+
426
+ # or provide an existing instance
427
+ existing = Customer.new
428
+ existing.name = 'Bob'
429
+
430
+ result = schema.build! do
431
+ customer existing do
432
+ email 'bob@example.com'
433
+ end
434
+ end
435
+
436
+ result[:customer].name # => 'Bob'
437
+ result[:customer].email # => 'bob@example.com'
438
+ ```
439
+
440
+ When using a block with a custom type:
441
+ - If no instance is provided, DynamicSchema will call `YourClass.new` to create one
442
+ - Inside the block, method calls are translated to setter calls on the instance
443
+ - You can provide an existing instance and still use a block to configure it further
444
+
445
+ This is particularly useful when integrating with `DynamicSchema::Struct` or other custom classes that need to be configured within a schema.
446
+
447
+ ### Multiple Types
448
+
449
+ You can specify multiple types for a value by providing an array of types. The value must match one of the listed types.
450
+
451
+ ```ruby
452
+ schema = DynamicSchema.define do
453
+ enabled [ TrueClass, FalseClass ]
454
+ identifier [ String, Integer ]
455
+ end
456
+
457
+ result = schema.build! do
458
+ enabled true
459
+ identifier 12345
460
+ end
461
+
462
+ result[ :enabled ] # => true
463
+ result[ :identifier ] # => 12345
464
+ ```
465
+
466
+ Validation ensures the value matches one of the specified types:
467
+
468
+ ```ruby
469
+ schema.valid?( { enabled: true } ) # => true
470
+ schema.valid?( { enabled: 'yes' } ) # => false (string not in types)
471
+ schema.valid?( { identifier: 'abc' } ) # => true
472
+ schema.valid?( { identifier: 123 } ) # => true
473
+ ```
474
+
475
+ ### Multiple Types with Nested Schema
476
+
477
+ When you combine multiple types with a block, you create a field that can be either a nested object (defined by the block) or one of the scalar types. The decision is made at runtime:
478
+
479
+ - **Hash values** (or blocks in Builder) are processed using the nested schema
480
+ - **Non-hash values** are validated against the scalar types in the array
481
+
482
+ This is useful for APIs where a field might be either a structured object or a simple value like a boolean.
483
+
484
+ #### Builder Example
485
+
486
+ ```ruby
487
+ schema = DynamicSchema.define do
488
+ # 'data' can be either a nested object OR true/false
489
+ data [ Object, TrueClass, FalseClass ] do
490
+ name String
491
+ value Integer
492
+ end
493
+ end
494
+
495
+ # Using as a nested object (with block)
496
+ result = schema.build! do
497
+ data do
498
+ name 'example'
499
+ value 42
500
+ end
501
+ end
502
+ result[ :data ][ :name ] # => 'example'
503
+
504
+ # Using as a nested object (with hash)
505
+ result = schema.build! do
506
+ data( { name: 'from hash', value: 100 } )
507
+ end
508
+ result[ :data ][ :name ] # => 'from hash'
509
+
510
+ # Using as a boolean
511
+ result = schema.build! do
512
+ data true
513
+ end
514
+ result[ :data ] # => true
515
+ ```
516
+
517
+ #### Struct Example
518
+
519
+ ```ruby
520
+ Settings = DynamicSchema::Struct.define do
521
+ config [ Object, TrueClass, FalseClass ] do
522
+ host String
523
+ port Integer
524
+ end
525
+ end
526
+
527
+ # With nested object - accessed via dot notation
528
+ settings = Settings.build( config: { host: 'localhost', port: 8080 } )
529
+ settings.config.host # => 'localhost'
530
+ settings.config.port # => 8080
531
+
532
+ # With boolean - returned as-is
533
+ settings = Settings.build( config: false )
534
+ settings.config # => false
535
+ ```
536
+
537
+ #### Arrays with Multiple Types
538
+
539
+ Combine with `array: true` to create arrays where each element can be either a nested object or a scalar:
540
+
541
+ ```ruby
542
+ schema = DynamicSchema.define do
543
+ items [ Object, TrueClass, FalseClass ], array: true do
544
+ name String
545
+ end
546
+ end
547
+
548
+ result = schema.build! do
549
+ items( { name: 'first' } )
550
+ items true
551
+ items( { name: 'second' } )
552
+ items false
553
+ end
554
+
555
+ result[ :items ] # => [ { name: 'first' }, true, { name: 'second' }, false ]
556
+ ```
557
+
558
+ #### Validation
559
+
560
+ Validation checks that hash values conform to the nested schema and non-hash values match one of the scalar types:
561
+
562
+ ```ruby
563
+ schema = DynamicSchema.define do
564
+ data [ Object, TrueClass, FalseClass ] do
565
+ value Integer
566
+ end
567
+ end
568
+
569
+ schema.valid?( { data: { value: 42 } } ) # => true (valid nested object)
570
+ schema.valid?( { data: true } ) # => true (valid boolean)
571
+ schema.valid?( { data: 'invalid' } ) # => false (string not in types)
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Options
577
+
578
+ Both *values* and *objects* can be customized through *options*. The options for both values and objects include `default`, `required`, `as` and `array`. In addition values support the `in` criteria option while objects support the `arguments` option.
579
+
580
+ ### :default Option
581
+
582
+ The `:default` option allows you to specify a default value that will be used if no value is provided during build.
272
583
 
273
584
  #### example:
274
585
 
@@ -285,9 +596,7 @@ puts result[:timeout] # => 30
285
596
 
286
597
  ### :required Option
287
598
 
288
- The `:required` option ensures that a value must be provided when building the schema. If a
289
- required value is missing when using `build!`, `validate`, or `validate!`,
290
- a `DynamicSchema::RequiredOptionError` will be raised.
599
+ The `:required` option ensures that a value must be provided when building the schema. If a required value is missing when using `build!`, `validate`, or `validate!`, a `DynamicSchema::RequiredOptionError` will be raised.
291
600
 
292
601
  #### example:
293
602
 
@@ -308,9 +617,7 @@ end
308
617
 
309
618
  ### :array Option
310
619
 
311
- The `:array` option wraps the value or object in an array in the resulting Hash, even if only
312
- one value is provided. This is particularly useful when dealing with APIs that expect array
313
- inputs.
620
+ The `:array` option wraps the value or object in an array in the resulting Hash, even if only one value is provided. This is particularly useful when dealing with APIs that expect array inputs.
314
621
 
315
622
  #### example:
316
623
 
@@ -336,9 +643,7 @@ puts result[:message] # => [{ text: "Hello world", type: "plain" }]
336
643
 
337
644
  ### :as Option
338
645
 
339
- The `:as` option allows you to use a different name in the DSL than what appears in the final
340
- Hash. This is particularly useful when interfacing with APIs that have specific key
341
- requirements.
646
+ The `:as` option allows you to use a different name in the DSL than what appears in the final Hash. This is particularly useful when interfacing with APIs that have specific key requirements.
342
647
 
343
648
  #### example:
344
649
 
@@ -358,8 +663,7 @@ puts result["Authorization"] # => "Bearer abc123"
358
663
 
359
664
  ### :in Option
360
665
 
361
- The `:in` option provides validation for values, ensuring they fall within a specified Range or
362
- are included in an Array of allowed values. This option is only available for values.
666
+ The `:in` option provides validation for values, ensuring they fall within a specified Range or are included in an Array of allowed values. This option is only available for values.
363
667
 
364
668
  #### example:
365
669
 
@@ -388,14 +692,11 @@ result = schema.build! do
388
692
  end
389
693
  ```
390
694
 
391
- ### :arguments Option
695
+ ### :arguments Option
392
696
 
393
- The `:arguments` option allows objects to accept arguments when building. Any arguments provided
394
- must appear when the object is built ( and so are implicitly 'required' ).
697
+ The `:arguments` option allows objects to accept arguments when building. Any arguments provided must appear when the object is built (and so are implicitly 'required').
395
698
 
396
- If the an argument is provided, the same argument appears in the attributes hash, or in the object
397
- block, the assignemnt in the block will take priority, followed by the attributes assigned and
398
- finally the argument.
699
+ If an argument is provided, the same argument appears in the attributes hash, or in the object block, the assignment in the block will take priority, followed by the attributes assigned and finally the argument.
399
700
 
400
701
  #### example:
401
702
 
@@ -419,129 +720,113 @@ end
419
720
 
420
721
  ## Class Schemas
421
722
 
422
- DynamicSchema provides a number of modules you can include into your own classes to simplify
423
- their definition and construction.
723
+ DynamicSchema provides a number of modules you can include into your own classes to simplify their definition and construction.
424
724
 
425
- ### Definable
725
+ ### Definable
426
726
 
427
- The `Definable` module, when inclued in a class, will add the `schema` and the `builder` class
428
- methods.
727
+ The `Definable` module, when included in a class, will add the `schema` and the `builder` class methods.
429
728
 
430
- By calling `schema` with a block you can define a schema for that specific class. You may also
431
- retrieve the defined schema by calling 'schema' ( with or without a block ). The 'schema' method
432
- may be called repeatedly to build up a schema with each call adding to the existing schema
433
- ( replacing values and objects of the same name if they appear in subsequent calls ).
729
+ By calling `schema` with a block you can define a schema for that specific class. You may also retrieve the defined schema by calling 'schema' (with or without a block). The 'schema' method may be called repeatedly to build up a schema with each call adding to the existing schema (replacing values and objects of the same name if they appear in subsequent calls).
434
730
 
435
- The `schema` method will integrate with a class hierarchy. By including Definable in a base class
436
- you can call `schema` to define a schema for that base class and then in subsequent dervied classes
437
- to augment it for those classes.
731
+ The `schema` method will integrate with a class hierarchy. By including Definable in a base class you can call `schema` to define a schema for that base class and then in subsequent derived classes to augment it for those classes.
438
732
 
439
- The `builder` method will return a memoized builder of the schema defined by calls to the `schema`
440
- method which can be used to build and validate schema conformant hashes.
733
+ The `builder` method will return a memoized builder of the schema defined by calls to the `schema` method which can be used to build and validate schema conformant hashes.
441
734
 
442
- ```ruby
443
- class Setting
444
- include DynamicSchema::Definable
445
- schema do
446
- name String
447
- end
448
- end
735
+ ```ruby
736
+ class Setting
737
+ include DynamicSchema::Definable
738
+ schema do
739
+ name String
740
+ end
741
+ end
449
742
 
450
- class DatabaSetting < Setting
451
- schema do
452
- database do
743
+ class DatabaseSetting < Setting
744
+ schema do
745
+ database do
453
746
  host String
454
- port String
455
- name String
456
- end
457
- end
747
+ port String
748
+ name String
749
+ end
750
+ end
458
751
 
459
- def initalize( attributes = {} )
460
- # validate the attributes
752
+ def initialize( attributes = {} )
753
+ # validate the attributes
461
754
  self.class.builder.validate!( attributes )
462
- # retain them for future access
463
- @attributes = attributes&.dup
755
+ # retain them for future access
756
+ @attributes = attributes&.dup
464
757
  end
465
758
 
466
- end
759
+ end
467
760
  ```
468
761
 
469
- ### Buildable
762
+ ### Buildable
470
763
 
471
- The `Buildable` module can be included in a class, in addition to `Definable` to faciliate
472
- building that class using a schema assisted builder pattern. The `Buildable` module adds
473
- `build!` and `build` methods to the class which can be used to build that class, with and
474
- without validation respectivelly.
764
+ The `Buildable` module can be included in a class, in addition to `Definable`, to facilitate building that class using a schema assisted builder pattern. The `Buildable` module adds `build!` and `build` methods to the class which can be used to build that class, with and without validation respectively.
475
765
 
476
- These methods accept both a hash with attributes that follow the schema, as well as a block
477
- that can be used to build the class instance. The attributes and block can be used simultanously.
766
+ These methods accept both a Hash with attributes that follow the schema, as well as a block that can be used to build the class instance. The attributes and block can be used simultaneously.
478
767
 
479
- **Important** Note that `Buildable` requires a class method `builder` ( which `Definable`
480
- provides ) and an initializer that accepts a `Hash` of attributes.
768
+ **Important** Note that `Buildable` requires a class method `builder` (which `Definable` provides) and an initializer that accepts a `Hash` of attributes.
481
769
 
482
770
  ```ruby
483
- class Setting
484
- include DynamicSchema::Definable
771
+ class Setting
772
+ include DynamicSchema::Definable
485
773
  include DynamicSchema::Buildable
486
- schema do
487
- name String
488
- end
489
- end
490
-
491
- class DatabaSetting < Setting
492
- schema do
493
- database do
494
- adapter Symbol,
774
+ schema do
775
+ name String
776
+ end
777
+ end
778
+
779
+ class DatabaseSetting < Setting
780
+ schema do
781
+ database do
782
+ adapter Symbol
495
783
  host String
496
- port String
497
- name String
498
- end
499
- end
784
+ port String
785
+ name String
786
+ end
787
+ end
500
788
 
501
- def initalize( attributes = {} )
502
- # validate the attributes
789
+ def initialize( attributes = {} )
790
+ # validate the attributes
503
791
  self.class.builder.validate!( attributes )
504
- # retain them for the future
505
- @attributes = attributes&.dup
792
+ # retain them for the future
793
+ @attributes = attributes&.dup
506
794
  end
507
- end
795
+ end
508
796
 
509
- database_settings = DatabaSettings.build! name: 'settings.database' do
510
- database adapter: :pg do
797
+ database_settings = DatabaseSetting.build! name: 'settings.database' do
798
+ database adapter: :pg do
511
799
  host "localhost"
512
800
  port "127.0.0.1"
513
801
  name "mydb"
514
- end
802
+ end
515
803
  end
516
804
  ```
517
805
 
518
806
  ## Validation
519
807
 
520
- DynamicSchema provides three different methods for validating Hash structures against your
521
- defined schema: `validate!`, `validate`, and `valid?`.
808
+ DynamicSchema provides three different methods for validating Hash structures against your defined schema: `validate!`, `validate`, and `valid?`.
522
809
 
523
- These methods allow you to verify that your data conforms to your schema's requirements,
524
- including type constraints, required fields, and value ranges.
810
+ These methods allow you to verify that your data conforms to your schema requirements, including type constraints, required fields, and value ranges.
525
811
 
526
812
  ### Validation Rules
527
813
 
528
814
  When validating, DynamicSchema checks:
529
815
 
530
- 1. **Required Fields**:
531
- Any value or object marked as `required: true` are present.
532
- 2. **Type Constraints**:
816
+ 1. **Required Fields**:
817
+ Any value or object marked as `required: true` is present.
818
+ 2. **Type Constraints**:
533
819
  Any values match their specified types or can be coerced to the specified type.
534
820
  3. **Value Ranges**:
535
821
  Any values fall within their specified `:in` constraints.
536
- 4. **Objects**:
537
- Any objects are recursively validates.
538
- 5. **Arrays**:
822
+ 4. **Objects**:
823
+ Any objects are recursively validated.
824
+ 5. **Arrays**:
539
825
  Any validation rules are applied to each element when `array: true`
540
826
 
541
827
  ### validate!
542
828
 
543
- The `validate!` method performs strict validation and raises an exception when it encounters
544
- the first validation error.
829
+ The `validate!` method performs strict validation and raises an exception when it encounters the first validation error.
545
830
 
546
831
  #### example:
547
832
 
@@ -556,27 +841,26 @@ schema.validate!( { temperature: 0.5 } )
556
841
 
557
842
  # this will raise DynamicSchema::IncompatibleTypeError
558
843
  schema.validate!( {
559
- api_key: ["not-a-string"],
844
+ api_key: ["not-a-string"],
560
845
  temperature: 0.5
561
846
  } )
562
847
 
563
848
  # this will raise DynamicSchema::InOptionError
564
849
  schema.validate!( {
565
850
  api_key: "abc123",
566
- temperature: 1.5
851
+ temperature: 1.5
567
852
  } )
568
853
 
569
854
  # this is valid and will not raise any errors
570
855
  schema.validate!( {
571
- api_key: 123,
856
+ api_key: 123,
572
857
  temperature: 0.5
573
858
  } )
574
859
  ```
575
860
 
576
861
  ### validate
577
862
 
578
- The `validate` method performs validation but instead of raising exceptions, it collects and
579
- returns an array of all validation errors encountered.
863
+ The `validate` method performs validation but instead of raising exceptions, it collects and returns an array of all validation errors encountered.
580
864
 
581
865
  #### example:
582
866
 
@@ -631,8 +915,7 @@ schema.valid?({
631
915
  DynamicSchema provides specific error types for different validation failures:
632
916
 
633
917
  - `DynamicSchema::RequiredOptionError`: Raised when a required field is missing
634
- - `DynamicSchema::IncompatibleTypeError`: Raised when a value's type doesn't match the schema
635
- and cannot be coerced
918
+ - `DynamicSchema::IncompatibleTypeError`: Raised when a value's type doesn't match the schema and cannot be coerced
636
919
  - `DynamicSchema::InOptionError`: Raised when a value falls outside its specified range/set
637
920
  - `ArgumentError`: Raised when the provided values structure isn't a Hash
638
921
 
@@ -642,7 +925,7 @@ Each error includes helpful context about the validation failure, including the
642
925
 
643
926
  ## Contributing
644
927
 
645
- Bug reports and pull requests are welcome on GitHub at [https://github.com/EndlessInternational/adaptive-schema](https://github.com/EndlessInternational/dynamic-schema).
928
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/EndlessInternational/dynamic_schema](https://github.com/EndlessInternational/dynamic_schema).
646
929
 
647
930
  ## License
648
931