dynamicschema 2.0.0 → 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,26 +1,21 @@
1
1
  # DynamicSchema
2
2
 
3
- The **DynamicSchema** gem provides an elegant and expressive way to define 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
@@ -30,10 +25,10 @@ end
30
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,11 +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)
63
61
  - [Struct](#struct)
64
62
  - [Validation Methods](#validation-methods)
65
- - [Validation Rules](#validation-rules)
63
+ - [Validation Rules](#validation-rules)
66
64
  - [validate!](#validate)
67
65
  - [validate](#validate-1)
68
66
  - [valid?](#valid)
@@ -104,12 +102,11 @@ require 'dynamic_schema'
104
102
 
105
103
  ### Defining Schemas with **DynamicSchema**
106
104
 
107
- DynamicSchema lets you define a DSL made of values, objects, and options, then reuse that DSL to
108
- build and validate Ruby Hashes.
105
+ DynamicSchema lets you define a DSL made of values, objects, and options, then reuse that DSL to build and validate Ruby Hashes.
109
106
 
110
- You start by constructing a `DynamicSchema::Builder`. You can do this by calling:
111
- - `DynamicSchema.define { … }`
112
- - `DynamicSchema::Builder.new.define { … }`
107
+ You start by constructing a `DynamicSchema::Builder`. You can do this by calling:
108
+ - `DynamicSchema.define { … }`
109
+ - `DynamicSchema::Builder.new.define { … }`
113
110
 
114
111
  In both cases, you pass a block that declares the schema values and objects with their options.
115
112
 
@@ -153,9 +150,7 @@ valid = schema.valid?( { api_key: 'secret', chat_options: { temperature: 0.7 }
153
150
 
154
151
  #### Inheritance
155
152
 
156
- You can extend an existing schema using the `inherit:` option. Pass a Proc that describes
157
- the parent schema—typically from a class that includes `DynamicSchema::Definable` via
158
- its `schema` method.
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.
159
154
 
160
155
  ```ruby
161
156
  class BaseSettings
@@ -182,13 +177,9 @@ You can call `build`, `build!`, `validate`, `validate!`, and `valid?` on the bui
182
177
 
183
178
  ## Struct
184
179
 
185
- In addition to building plain Ruby `Hash` values, DynamicSchema can generate lightweight
186
- Ruby classes from a schema. A `DynamicSchema::Struct` exposes readers and writers for the
187
- fields you define, and transparently wraps nested objects so that you can access them with
188
- dot-style accessors rather than deep hash indexing.
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.
189
181
 
190
- You create a struct class by passing the same schema shape you would give to a Builder. The
191
- schema can be provided as:
182
+ You create a struct class by passing the same schema shape you would give to a Builder. The schema can be provided as:
192
183
 
193
184
  - a `Proc` that defines the schema
194
185
  - a `DynamicSchema::Builder`
@@ -253,18 +244,15 @@ collection.line_items[ 1 ].quantity # => 2
253
244
  - defining
254
245
  - `DynamicSchema::Struct.define` takes a block that looks exactly like a Builder schema.
255
246
  - Use `array: true` to expose arrays of nested structs.
256
- - You may reference another struct class as a value type; arrays of that type expose
257
- nested accessors for each element.
247
+ - You may reference another struct class as a value type; arrays of that type expose nested accessors for each element.
258
248
  - building
259
- - `StructClass.build( attributes )` constructs an instance and (optionally) coerces typed
260
- scalar fields using the same converters as the Builder.
249
+ - `StructClass.build( attributes )` constructs an instance and (optionally) coerces typed scalar fields using the same converters as the Builder.
261
250
  - `StructClass.build!` additionally validates the instance just like `builder.build!`.
262
251
  - accessing
263
252
  - Use standard Ruby readers/writers: `instance.attribute`, `instance.attribute = value`.
264
253
  - `#to_h` returns a deep Hash of the current values (nested structs become hashes).
265
254
 
266
- You can also create a struct class from a builder or a compiled hash if you already have a
267
- schema elsewhere:
255
+ You can also create a struct class from a builder or a compiled hash if you already have a schema elsewhere:
268
256
 
269
257
  ```ruby
270
258
  builder = DynamicSchema.define do
@@ -282,16 +270,13 @@ NameStruct.build( name: 'Taylor' ).name # => 'Taylor'
282
270
 
283
271
  ---
284
272
 
285
- ## Values
273
+ ## Values
286
274
 
287
- A *value* is a basic building block of your schema. Values represent individual settings,
288
- options or API parameters that you can define with specific types, defaults, and other options.
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.
289
276
 
290
- When defining a value, you provide the name as though you were calling a Ruby method, with
291
- arguments that include an optional type (which can be a `Class`, `Module` or an `Array` of these)
292
- as well as a `Hash` of options, all of which are optional:
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:
293
278
 
294
- `name {type} default: {true|false}, required: {true|false}, array: {true|false}, as: {name}, in: {Array|Range}`
279
+ `name {type}, default: {value}, required: {true|false}, array: {true|false}, as: {name}, in: {Array|Range}`
295
280
 
296
281
  #### example:
297
282
 
@@ -301,7 +286,7 @@ require 'dynamic_schema'
301
286
  # define a schema structure with values
302
287
  schema = DynamicSchema.define do
303
288
  api_key
304
- version, String, default: '1.0'
289
+ version String, default: '1.0'
305
290
  end
306
291
 
307
292
  # build the schema and set values
@@ -316,30 +301,26 @@ puts result[:version] # => "1.0"
316
301
 
317
302
  - defining
318
303
  - `api_key` defines a value named `api_key`. Any type can be used to assign the value.
319
- - `version, String, default: '1.0'` defines a value with a default.
320
- - building
304
+ - `version String, default: '1.0'` defines a value with a default.
305
+ - building
321
306
  - `schema.build!` accepts both a Hash and a block where you can set the values.
322
307
  - Inside the block, `api_key 'your-api-key'` sets the value of `api_key`.
323
- - accessing
308
+ - accessing
324
309
  - `result[:api_key]` retrieves the value of `api_key`.
325
- - If a value has a default and you don't set it, the default value will be included in
326
- resulting hash.
310
+ - If a value has a default and you don't set it, the default value will be included in resulting hash.
327
311
 
328
312
  ---
329
313
 
330
314
  ## Objects
331
315
 
332
- A schema may be organized hierarchically, by creating collections of related values and
333
- 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.
334
317
 
335
- An *object* is defined in a similar manner to a value. Simply provide the name as though
336
- calling a Ruby method, with a Hash of options and a block which encloses the child values
337
- 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:
338
319
 
339
320
  ```
340
- 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
341
322
  # child values and objects can be defined here
342
- end
323
+ end
343
324
  ```
344
325
 
345
326
  Notice an *object* does not accept a type as it is always of type `Object`.
@@ -350,7 +331,7 @@ Notice an *object* does not accept a type as it is always of type `Object`.
350
331
  require 'dynamic_schema'
351
332
 
352
333
  schema = DynamicSchema.define do
353
- api_key, String
334
+ api_key String
354
335
  chat_options do
355
336
  model String, default: 'claude-3'
356
337
  max_tokens Integer, default: 1024
@@ -377,7 +358,7 @@ puts result[:chat_options][:stream] # => true
377
358
  - defining
378
359
  - `chat_options do ... end` defines an object named `chat_options`.
379
360
  - Inside the object you can define values that belong to that object.
380
- - building
361
+ - building
381
362
  - In the build block, you can set values for values within objects by nesting blocks.
382
363
  - `chat_options do ... end` allows you to set values inside the `chat_options` object.
383
364
  - accessing
@@ -385,11 +366,9 @@ puts result[:chat_options][:stream] # => true
385
366
 
386
367
  ---
387
368
 
388
- ## Types
369
+ ## Types
389
370
 
390
- An *object* is always of type `Object`. A *value* can have no type or it can be of one or
391
- more types. You specify the value type by providing an instance of a `Class` when defining
392
- 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.
393
372
 
394
373
  #### example:
395
374
 
@@ -398,37 +377,209 @@ require 'dynamic_schema'
398
377
 
399
378
  schema = DynamicSchema.define do
400
379
  typeless_value
401
- symbol_value Symbol
380
+ symbol_value Symbol
402
381
  boolean_value [ TrueClass, FalseClass ]
403
382
  end
404
383
 
405
384
  result = schema.build! do
406
385
  typeless_value Struct.new(:name).new(name: 'Kristoph')
407
386
  symbol_value "something"
408
- boolean_value true
409
- end
387
+ boolean_value true
388
+ end
410
389
 
411
390
  puts result[:typeless_value].name # => "Kristoph"
412
391
  puts result[:symbol_value] # => :something
413
- puts result[:boolean_value] # => true
392
+ puts result[:boolean_value] # => true
414
393
  ```
415
394
 
416
395
  - defining
417
- - `typeless_value` defines a value that has no type and will accept an assignment of any type
418
- - `symbol_value` defines a value that accepts symbols or types that can be coerced into
419
- 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**)
420
398
  - `boolean_value` defines a value that can be either `true` or `false`
421
399
 
422
- ## 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
410
+
411
+ schema = DynamicSchema.define do
412
+ customer Customer
413
+ end
414
+
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
422
+
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.
423
446
 
424
- Both *values* and *objects* can be customized through *options*. The options for both values and
425
- objects include `default`, `required`, `as` and `array`. In addition values support the `in`
426
- criteria option while objects support the `arguments` option.
447
+ ### Multiple Types
427
448
 
428
- ### :default Option
449
+ You can specify multiple types for a value by providing an array of types. The value must match one of the listed types.
429
450
 
430
- The `:default` option allows you to specify a default value that will be used if no value is
431
- provided during build.
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.
432
583
 
433
584
  #### example:
434
585
 
@@ -445,9 +596,7 @@ puts result[:timeout] # => 30
445
596
 
446
597
  ### :required Option
447
598
 
448
- The `:required` option ensures that a value must be provided when building the schema. If a
449
- required value is missing when using `build!`, `validate`, or `validate!`,
450
- 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.
451
600
 
452
601
  #### example:
453
602
 
@@ -468,9 +617,7 @@ end
468
617
 
469
618
  ### :array Option
470
619
 
471
- The `:array` option wraps the value or object in an array in the resulting Hash, even if only
472
- one value is provided. This is particularly useful when dealing with APIs that expect array
473
- 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.
474
621
 
475
622
  #### example:
476
623
 
@@ -496,9 +643,7 @@ puts result[:message] # => [{ text: "Hello world", type: "plain" }]
496
643
 
497
644
  ### :as Option
498
645
 
499
- The `:as` option allows you to use a different name in the DSL than what appears in the final
500
- Hash. This is particularly useful when interfacing with APIs that have specific key
501
- 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.
502
647
 
503
648
  #### example:
504
649
 
@@ -518,8 +663,7 @@ puts result["Authorization"] # => "Bearer abc123"
518
663
 
519
664
  ### :in Option
520
665
 
521
- The `:in` option provides validation for values, ensuring they fall within a specified Range or
522
- 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.
523
667
 
524
668
  #### example:
525
669
 
@@ -550,12 +694,9 @@ end
550
694
 
551
695
  ### :arguments Option
552
696
 
553
- The `:arguments` option allows objects to accept arguments when building. Any arguments provided
554
- 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').
555
698
 
556
- If the an argument is provided, the same argument appears in the attributes hash, or in the object
557
- block, the assignemnt in the block will take priority, followed by the attributes assigned and
558
- 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.
559
700
 
560
701
  #### example:
561
702
 
@@ -579,129 +720,113 @@ end
579
720
 
580
721
  ## Class Schemas
581
722
 
582
- DynamicSchema provides a number of modules you can include into your own classes to simplify
583
- their definition and construction.
723
+ DynamicSchema provides a number of modules you can include into your own classes to simplify their definition and construction.
584
724
 
585
- ### Definable
725
+ ### Definable
586
726
 
587
- The `Definable` module, when included in a class, will add the `schema` and the `builder` class
588
- methods.
727
+ The `Definable` module, when included in a class, will add the `schema` and the `builder` class methods.
589
728
 
590
- By calling `schema` with a block you can define a schema for that specific class. You may also
591
- retrieve the defined schema by calling 'schema' ( with or without a block ). The 'schema' method
592
- may be called repeatedly to build up a schema with each call adding to the existing schema
593
- ( 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).
594
730
 
595
- The `schema` method will integrate with a class hierarchy. By including Definable in a base class
596
- you can call `schema` to define a schema for that base class and then in subsequent derived classes
597
- 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.
598
732
 
599
- The `builder` method will return a memoized builder of the schema defined by calls to the `schema`
600
- 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.
601
734
 
602
- ```ruby
603
- class Setting
604
- include DynamicSchema::Definable
605
- schema do
606
- name String
607
- end
608
- end
735
+ ```ruby
736
+ class Setting
737
+ include DynamicSchema::Definable
738
+ schema do
739
+ name String
740
+ end
741
+ end
609
742
 
610
- class DatabaSetting < Setting
611
- schema do
612
- database do
743
+ class DatabaseSetting < Setting
744
+ schema do
745
+ database do
613
746
  host String
614
- port String
615
- name String
616
- end
617
- end
747
+ port String
748
+ name String
749
+ end
750
+ end
618
751
 
619
752
  def initialize( attributes = {} )
620
- # validate the attributes
753
+ # validate the attributes
621
754
  self.class.builder.validate!( attributes )
622
- # retain them for future access
623
- @attributes = attributes&.dup
755
+ # retain them for future access
756
+ @attributes = attributes&.dup
624
757
  end
625
758
 
626
- end
759
+ end
627
760
  ```
628
761
 
629
- ### Buildable
762
+ ### Buildable
630
763
 
631
- The `Buildable` module can be included in a class, in addition to `Definable`, to facilitate
632
- building that class using a schema assisted builder pattern. The `Buildable` module adds
633
- `build!` and `build` methods to the class which can be used to build that class, with and
634
- without validation respectively.
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.
635
765
 
636
- These methods accept both a Hash with attributes that follow the schema, as well as a block
637
- that can be used to build the class instance. The attributes and block can be used simultaneously.
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.
638
767
 
639
- **Important** Note that `Buildable` requires a class method `builder` ( which `Definable`
640
- 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.
641
769
 
642
770
  ```ruby
643
- class Setting
644
- include DynamicSchema::Definable
771
+ class Setting
772
+ include DynamicSchema::Definable
645
773
  include DynamicSchema::Buildable
646
- schema do
647
- name String
648
- end
649
- end
650
-
651
- class DatabaseSetting < Setting
652
- schema do
653
- database do
774
+ schema do
775
+ name String
776
+ end
777
+ end
778
+
779
+ class DatabaseSetting < Setting
780
+ schema do
781
+ database do
654
782
  adapter Symbol
655
783
  host String
656
- port String
657
- name String
658
- end
659
- end
784
+ port String
785
+ name String
786
+ end
787
+ end
660
788
 
661
789
  def initialize( attributes = {} )
662
- # validate the attributes
790
+ # validate the attributes
663
791
  self.class.builder.validate!( attributes )
664
- # retain them for the future
665
- @attributes = attributes&.dup
792
+ # retain them for the future
793
+ @attributes = attributes&.dup
666
794
  end
667
- end
795
+ end
668
796
 
669
- database_settings = DatabaseSetting.build! name: 'settings.database' do
670
- database adapter: :pg do
797
+ database_settings = DatabaseSetting.build! name: 'settings.database' do
798
+ database adapter: :pg do
671
799
  host "localhost"
672
800
  port "127.0.0.1"
673
801
  name "mydb"
674
- end
802
+ end
675
803
  end
676
804
  ```
677
805
 
678
806
  ## Validation
679
807
 
680
- DynamicSchema provides three different methods for validating Hash structures against your
681
- defined schema: `validate!`, `validate`, and `valid?`.
808
+ DynamicSchema provides three different methods for validating Hash structures against your defined schema: `validate!`, `validate`, and `valid?`.
682
809
 
683
- These methods allow you to verify that your data conforms to your schema requirements,
684
- 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.
685
811
 
686
812
  ### Validation Rules
687
813
 
688
814
  When validating, DynamicSchema checks:
689
815
 
690
- 1. **Required Fields**:
816
+ 1. **Required Fields**:
691
817
  Any value or object marked as `required: true` is present.
692
- 2. **Type Constraints**:
818
+ 2. **Type Constraints**:
693
819
  Any values match their specified types or can be coerced to the specified type.
694
820
  3. **Value Ranges**:
695
821
  Any values fall within their specified `:in` constraints.
696
- 4. **Objects**:
822
+ 4. **Objects**:
697
823
  Any objects are recursively validated.
698
- 5. **Arrays**:
824
+ 5. **Arrays**:
699
825
  Any validation rules are applied to each element when `array: true`
700
826
 
701
827
  ### validate!
702
828
 
703
- The `validate!` method performs strict validation and raises an exception when it encounters
704
- the first validation error.
829
+ The `validate!` method performs strict validation and raises an exception when it encounters the first validation error.
705
830
 
706
831
  #### example:
707
832
 
@@ -716,27 +841,26 @@ schema.validate!( { temperature: 0.5 } )
716
841
 
717
842
  # this will raise DynamicSchema::IncompatibleTypeError
718
843
  schema.validate!( {
719
- api_key: ["not-a-string"],
844
+ api_key: ["not-a-string"],
720
845
  temperature: 0.5
721
846
  } )
722
847
 
723
848
  # this will raise DynamicSchema::InOptionError
724
849
  schema.validate!( {
725
850
  api_key: "abc123",
726
- temperature: 1.5
851
+ temperature: 1.5
727
852
  } )
728
853
 
729
854
  # this is valid and will not raise any errors
730
855
  schema.validate!( {
731
- api_key: 123,
856
+ api_key: 123,
732
857
  temperature: 0.5
733
858
  } )
734
859
  ```
735
860
 
736
861
  ### validate
737
862
 
738
- The `validate` method performs validation but instead of raising exceptions, it collects and
739
- 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.
740
864
 
741
865
  #### example:
742
866
 
@@ -791,8 +915,7 @@ schema.valid?({
791
915
  DynamicSchema provides specific error types for different validation failures:
792
916
 
793
917
  - `DynamicSchema::RequiredOptionError`: Raised when a required field is missing
794
- - `DynamicSchema::IncompatibleTypeError`: Raised when a value's type doesn't match the schema
795
- and cannot be coerced
918
+ - `DynamicSchema::IncompatibleTypeError`: Raised when a value's type doesn't match the schema and cannot be coerced
796
919
  - `DynamicSchema::InOptionError`: Raised when a value falls outside its specified range/set
797
920
  - `ArgumentError`: Raised when the provided values structure isn't a Hash
798
921