easy_talk 1.0.2 → 1.0.3

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,41 +1,85 @@
1
1
  # EasyTalk
2
2
 
3
- EasyTalk is a Ruby library that simplifies defining and generating JSON Schema documents, and validates that JSON data conforms to these schemas.
3
+ ## Introduction
4
+
5
+ ### What is EasyTalk?
6
+ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema. It provides an intuitive interface for Ruby developers to define structured data models that can be used for validation and documentation.
7
+
8
+ ### Key Features
9
+ * **Intuitive Schema Definition**: Use Ruby classes and methods to define JSON Schema documents easily.
10
+ * **Works for plain Ruby classes and ActiveRecord models**: Integrate with existing code or build from scratch.
11
+ * **LLM Function Support**: Ideal for integrating with Large Language Models (LLMs) such as OpenAI's GPT series. EasyTalk enables you to effortlessly create JSON Schema documents describing the inputs and outputs of LLM function calls.
12
+ * **Schema Composition**: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
13
+ * **Validation**: Write validations using ActiveModel's validations.
14
+
15
+ ### Use Cases
16
+ - API request/response validation
17
+ - LLM function definitions
18
+ - Object structure documentation
19
+ - Data validation and transformation
20
+ - Configuration schema definitions
21
+
22
+ ### Inspiration
23
+ Inspired by Python's Pydantic library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.
4
24
 
5
- Key Features
6
- * Intuitive Schema Definition: Use Ruby classes and methods to define JSON Schema documents easily.
7
- * LLM Function Support: Ideal for integrating with Large Language Models (LLMs) such as OpenAI’s GPT series. EasyTalk enables you to effortlessly create JSON Schema documents describing the inputs and outputs of LLM function calls.
8
- * Schema Composition: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
9
- * Validation: Write validations using ActiveModel’s validations.
25
+ ## Installation
10
26
 
11
- Inspiration
12
- Inspired by Python's Pydantic library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.
27
+ ### Requirements
28
+ - Ruby 3.2 or higher
29
+ - ActiveModel 7.0 or higher
30
+ - ActiveSupport 7.0 or higher
13
31
 
14
- Example Use:
32
+ ### Installation Steps
33
+ Add EasyTalk to your application's Gemfile:
15
34
 
16
35
  ```ruby
17
- class User
36
+ gem 'easy_talk'
37
+ ```
38
+
39
+ Or install it directly:
40
+
41
+ ```bash
42
+ $ gem install easy_talk
43
+ ```
44
+
45
+ ### Verification
46
+ After installation, you can verify it's working by creating a simple model:
47
+
48
+ ```ruby
49
+ require 'easy_talk'
50
+
51
+ class Test
18
52
  include EasyTalk::Model
53
+
54
+ define_schema do
55
+ property :name, String
56
+ end
57
+ end
58
+
59
+ puts Test.json_schema
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ### Minimal Example
65
+ Here's a basic example to get you started with EasyTalk:
19
66
 
20
- validates :name, :email, :group, presence: true
21
- validates :age, numericality: { greater_than_or_equal_to: 18, less_than_or_equal_to: 100 }
67
+ ```ruby
68
+ class User
69
+ include EasyTalk::Model
22
70
 
23
71
  define_schema do
24
72
  title "User"
25
73
  description "A user of the system"
26
- property :name, String, description: "The user's name", title: "Full Name"
27
- property :email, Hash do
28
- property :address, String, format: "email", description: "The user's email", title: "Email Address"
29
- property :verified, T::Boolean, description: "Whether the email is verified"
30
- end
31
- property :group, Integer, enum: [1, 2, 3], default: 1, description: "The user's group"
32
- property :age, Integer, minimum: 18, maximum: 100, description: "The user's age"
33
- property :tags, T::Array[String], min_items: 1, unique_items: true, description: "The user's tags"
74
+ property :name, String, description: "The user's name"
75
+ property :email, String, format: "email"
76
+ property :age, Integer, minimum: 18
34
77
  end
35
78
  end
36
79
  ```
37
80
 
38
- Calling `User.json_schema` will return the Ruby representation of the JSON Schema for the `User` class:
81
+ ### Generated JSON Schema
82
+ Calling `User.json_schema` will generate:
39
83
 
40
84
  ```ruby
41
85
  {
@@ -44,89 +88,591 @@ Calling `User.json_schema` will return the Ruby representation of the JSON Schem
44
88
  "description" => "A user of the system",
45
89
  "properties" => {
46
90
  "name" => {
47
- "type" => "string", "title" => "Full Name", "description" => "The user's name"
91
+ "type" => "string",
92
+ "description" => "The user's name"
48
93
  },
49
94
  "email" => {
50
- "type" => "object",
51
- "properties" => {
52
- "address" => {
53
- "type" => "string", "title" => "Email Address", "description" => "The user's email", "format" => "email"
54
- },
55
- "verified" => {
56
- "type" => "boolean", "description" => "Whether the email is verified"
57
- }
58
- },
59
- "required" => ["address", "verified"]
60
- },
61
- "group" => {
62
- "type" => "integer", "description" => "The user's group", "enum" => [1, 2, 3], "default" => 1
95
+ "type" => "string",
96
+ "format" => "email"
63
97
  },
64
98
  "age" => {
65
- "type" => "integer", "description" => "The user's age", "minimum" => 18, "maximum" => 100
66
- },
67
- "tags" => {
68
- "type" => "array",
69
- "items" => { "type" => "string" },
70
- "description" => "The user's tags",
71
- "minItems" => 1,
72
- "uniqueItems" => true
99
+ "type" => "integer",
100
+ "minimum" => 18
73
101
  }
74
102
  },
75
- "required" => ["name", "email", "group", "age", "tags"]
103
+ "required" => ["name", "email", "age"]
76
104
  }
77
105
  ```
78
106
 
79
- Instantiate a User object and validate it with ActiveModel validations:
107
+ ### Basic Usage
108
+ Creating and validating an instance of your model:
80
109
 
81
110
  ```ruby
82
- user = User.new(name: "John Doe", email: { address: "john@test.com", verified: true }, group: 1, age: 25, tags: ["tag1", "tag2"])
111
+ user = User.new(name: "John Doe", email: "john@example.com", age: 25)
83
112
  user.valid? # => true
84
113
 
85
- user.name = nil
114
+ user.age = 17
86
115
  user.valid? # => false
116
+ ```
117
+
118
+ ## Core Concepts
87
119
 
88
- user.errors.full_messages # => ["Name can't be blank"]
89
- user.errors["name"] # => ["can't be blank"]
120
+ ### Schema Definition
121
+ In EasyTalk, you define your schema by including the `EasyTalk::Model` module and using the `define_schema` method. This method takes a block where you can define the properties and constraints of your schema.
122
+
123
+ ```ruby
124
+ class MyModel
125
+ include EasyTalk::Model
126
+
127
+ define_schema do
128
+ title "My Model"
129
+ description "Description of my model"
130
+ property :some_property, String
131
+ property :another_property, Integer
132
+ end
133
+ end
90
134
  ```
91
135
 
92
- ## Installation
136
+ ### Property Types
137
+
138
+ #### Ruby Types
139
+ EasyTalk supports standard Ruby types directly:
140
+
141
+ - `String`: String values
142
+ - `Integer`: Integer values
143
+ - `Float`: Floating-point numbers
144
+ - `Date`: Date values
145
+ - `DateTime`: Date and time values
146
+ - `Hash`: Object/dictionary values
147
+
148
+ #### Sorbet-Style Types
149
+ For complex types, EasyTalk uses Sorbet-style type notation:
150
+
151
+ - `T::Boolean`: Boolean values (true/false)
152
+ - `T::Array[Type]`: Arrays with items of a specific type
153
+ - `T.nilable(Type)`: Type that can also be nil
93
154
 
94
- install the gem by running the following command in your terminal:
155
+ #### Custom Types
156
+ EasyTalk supports special composition types:
157
+
158
+ - `T::AnyOf[Type1, Type2, ...]`: Value can match any of the specified schemas
159
+ - `T::OneOf[Type1, Type2, ...]`: Value must match exactly one of the specified schemas
160
+ - `T::AllOf[Type1, Type2, ...]`: Value must match all of the specified schemas
161
+
162
+ ### Property Constraints
163
+ Property constraints depend on the type of property. Some common constraints include:
164
+
165
+ - `description`: A description of the property
166
+ - `title`: A title for the property
167
+ - `format`: A format hint for the property (e.g., "email", "date")
168
+ - `enum`: A list of allowed values
169
+ - `minimum`/`maximum`: Minimum/maximum values for numbers
170
+ - `min_length`/`max_length`: Minimum/maximum length for strings
171
+ - `pattern`: A regular expression pattern for strings
172
+ - `min_items`/`max_items`: Minimum/maximum number of items for arrays
173
+ - `unique_items`: Whether array items must be unique
174
+
175
+ ### Required vs Optional Properties
176
+ By default, all properties defined in an EasyTalk model are required. You can make a property optional by specifying `optional: true`:
177
+
178
+ ```ruby
179
+ define_schema do
180
+ property :name, String
181
+ property :middle_name, String, optional: true
182
+ end
183
+ ```
184
+
185
+ In this example, `name` is required but `middle_name` is optional.
186
+
187
+ ### Schema Validation
188
+ EasyTalk models include ActiveModel validations. You can validate your models using the standard ActiveModel validation methods:
189
+
190
+ ```ruby
191
+ class User
192
+ include EasyTalk::Model
193
+
194
+ validates :name, presence: true
195
+ validates :age, numericality: { greater_than_or_equal_to: 18 }
196
+
197
+ define_schema do
198
+ property :name, String
199
+ property :age, Integer, minimum: 18
200
+ end
201
+ end
202
+
203
+ user = User.new(name: "John", age: 17)
204
+ user.valid? # => false
205
+ user.errors.full_messages # => ["Age must be greater than or equal to 18"]
206
+ ```
207
+
208
+ ## Defining Schemas
209
+
210
+ ### Basic Schema Structure
211
+ A schema definition consists of a class that includes `EasyTalk::Model` and a `define_schema` block:
212
+
213
+ ```ruby
214
+ class Person
215
+ include EasyTalk::Model
216
+
217
+ define_schema do
218
+ title "Person"
219
+ property :name, String
220
+ property :age, Integer
221
+ end
222
+ end
223
+ ```
224
+
225
+ ### Property Definitions
226
+ Properties are defined using the `property` method, which takes a name, a type, and optional constraints:
227
+
228
+ ```ruby
229
+ property :name, String, description: "The person's name", title: "Full Name"
230
+ property :age, Integer, minimum: 0, maximum: 120, description: "The person's age"
231
+ ```
232
+
233
+ ### Nested Objects
234
+ You can define nested objects using a block:
235
+
236
+ ```ruby
237
+ property :email, Hash do
238
+ property :address, String, format: "email"
239
+ property :verified, T::Boolean
240
+ end
241
+ ```
95
242
 
96
- $ gem install easy_talk
243
+ ### Arrays and Collections
244
+ Arrays can be defined using the `T::Array` type:
97
245
 
98
- ## Usage
246
+ ```ruby
247
+ property :tags, T::Array[String], min_items: 1, unique_items: true
248
+ property :scores, T::Array[Integer], description: "List of scores"
249
+ ```
99
250
 
100
- Simply include the `EasyTalk::Model` module in your Ruby class, define the schema using the `define_schema` block, and call the `json_schema` class method to generate the JSON Schema document.
251
+ You can also define arrays of complex types:
101
252
 
253
+ ```ruby
254
+ property :addresses, T::Array[Address], description: "List of addresses"
255
+ ```
102
256
 
103
- ## Schema Definition
257
+ ### Constraints and Validations
258
+ Constraints can be added to properties and are used for schema generation:
104
259
 
105
- In the example above, the define_schema method adds a title and description to the schema. The property method defines properties of the schema document. property accepts:
260
+ ```ruby
261
+ property :name, String, min_length: 2, max_length: 50
262
+ property :email, String, format: "email"
263
+ property :category, String, enum: ["A", "B", "C"], default: "A"
264
+ ```
106
265
 
107
- * A name (symbol)
108
- * A type (generic Ruby type like String/Integer, a Sorbet type like T::Boolean, or one of the custom types like T::AnyOf[...])
109
- * A hash of constraints (e.g., minimum: 18, enum: [1, 2, 3], etc.)
266
+ For validation, you can use ActiveModel validations:
110
267
 
111
- ## Why Sortbet-style types?
268
+ ```ruby
269
+ validates :name, presence: true, length: { minimum: 2, maximum: 50 }
270
+ validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
271
+ validates :category, inclusion: { in: ["A", "B", "C"] }
272
+ ```
112
273
 
113
- Ruby doesn’t natively allow complex types like Array[String] or Array[Integer]. Sorbet-style types let you define these compound types clearly. EasyTalk uses this style to handle property types such as T::Array[String] or T::AnyOf[ClassA, ClassB].
274
+ ### Additional Properties
275
+ By default, EasyTalk models do not allow additional properties beyond those defined in the schema. You can change this behavior using the `additional_properties` keyword:
114
276
 
115
- ## Property Constraints
277
+ ```ruby
278
+ define_schema do
279
+ property :name, String
280
+ additional_properties true
281
+ end
282
+ ```
116
283
 
117
- Property constraints are type-dependent. Refer to the [CONSTRAINTS.md](CONSTRAINTS.md) file for a list of constraints supported by the JSON Schema generator.
284
+ With `additional_properties true`, you can add arbitrary properties to your model instances:
118
285
 
286
+ ```ruby
287
+ company = Company.new
288
+ company.name = "Acme Corp" # Defined property
289
+ company.location = "New York" # Additional property
290
+ company.employee_count = 100 # Additional property
291
+ ```
119
292
 
120
293
  ## Schema Composition
121
294
 
122
- EasyTalk supports schema composition. You can define a schema for a nested object by defining a new class that includes `EasyTalk::Model`. You can then reference the nested schema in the parent using special types:
295
+ ### Using T::AnyOf
296
+ The `T::AnyOf` type allows a property to match any of the specified schemas:
297
+
298
+ ```ruby
299
+ class Payment
300
+ include EasyTalk::Model
301
+
302
+ define_schema do
303
+ property :details, T::AnyOf[CreditCard, Paypal, BankTransfer]
304
+ end
305
+ end
306
+ ```
307
+
308
+ ### Using T::OneOf
309
+ The `T::OneOf` type requires a property to match exactly one of the specified schemas:
310
+
311
+ ```ruby
312
+ class Contact
313
+ include EasyTalk::Model
314
+
315
+ define_schema do
316
+ property :contact, T::OneOf[PhoneContact, EmailContact]
317
+ end
318
+ end
319
+ ```
320
+
321
+ ### Using T::AllOf
322
+ The `T::AllOf` type requires a property to match all of the specified schemas:
323
+
324
+ ```ruby
325
+ class VehicleRegistration
326
+ include EasyTalk::Model
327
+
328
+ define_schema do
329
+ compose T::AllOf[VehicleIdentification, OwnerInfo, RegistrationDetails]
330
+ end
331
+ end
332
+ ```
333
+
334
+ ### Complex Compositions
335
+ You can combine composition types to create complex schemas:
336
+
337
+ ```ruby
338
+ class ComplexObject
339
+ include EasyTalk::Model
340
+
341
+ define_schema do
342
+ property :basic_info, BaseInfo
343
+ property :specific_details, T::OneOf[DetailTypeA, DetailTypeB]
344
+ property :metadata, T::AnyOf[AdminMetadata, UserMetadata, nil]
345
+ end
346
+ end
347
+ ```
348
+
349
+ ### Reusing Models
350
+ Models can reference other models to create hierarchical schemas:
351
+
352
+ ```ruby
353
+ class Address
354
+ include EasyTalk::Model
355
+
356
+ define_schema do
357
+ property :street, String
358
+ property :city, String
359
+ property :state, String
360
+ property :zip, String
361
+ end
362
+ end
363
+
364
+ class User
365
+ include EasyTalk::Model
366
+
367
+ define_schema do
368
+ property :name, String
369
+ property :address, Address
370
+ end
371
+ end
372
+ ```
373
+
374
+ ## ActiveModel Integration
375
+
376
+ ### Validations
377
+ EasyTalk models include ActiveModel validations:
378
+
379
+ ```ruby
380
+ class User
381
+ include EasyTalk::Model
382
+
383
+ validates :age, comparison: { greater_than: 21 }
384
+ validates :height, presence: true, numericality: { greater_than: 0 }
385
+
386
+ define_schema do
387
+ property :name, String
388
+ property :age, Integer
389
+ property :height, Float
390
+ end
391
+ end
392
+ ```
393
+
394
+ ### Error Handling
395
+ You can access validation errors using the standard ActiveModel methods:
396
+
397
+ ```ruby
398
+ user = User.new(name: "Jim", age: 18, height: -5.9)
399
+ user.valid? # => false
400
+ user.errors[:age] # => ["must be greater than 21"]
401
+ user.errors[:height] # => ["must be greater than 0"]
402
+ ```
403
+
404
+ ### Model Attributes
405
+ EasyTalk models provide getters and setters for all defined properties:
123
406
 
124
- T::OneOf[Model1, Model2, ...] — The property must match at least one of the specified schemas
125
- T::AnyOf[Model1, Model2, ...] — The property can match any of the specified schemas
126
- T::AllOf[Model1, Model2, ...] — The property must match all of the specified schemas
407
+ ```ruby
408
+ user = User.new
409
+ user.name = "John"
410
+ user.age = 30
411
+ puts user.name # => "John"
412
+ ```
413
+
414
+ You can also initialize a model with a hash of attributes:
415
+
416
+ ```ruby
417
+ user = User.new(name: "John", age: 30, height: 5.9)
418
+ ```
419
+
420
+ ## ActiveRecord Integration
421
+
422
+ ### Automatic Schema Generation
423
+ For ActiveRecord models, EasyTalk automatically generates a schema based on the database columns:
424
+
425
+ ```ruby
426
+ class Product < ActiveRecord::Base
427
+ include EasyTalk::Model
428
+ end
429
+ ```
430
+
431
+ This will create a schema with properties for each column in the `products` table.
432
+
433
+ ### Enhancing Generated Schemas
434
+ You can enhance the auto-generated schema with the `enhance_schema` method:
435
+
436
+ ```ruby
437
+ class Product < ActiveRecord::Base
438
+ include EasyTalk::Model
439
+
440
+ enhance_schema({
441
+ title: "Retail Product",
442
+ description: "A product available for purchase",
443
+ properties: {
444
+ name: {
445
+ description: "Product display name",
446
+ title: "Product Name"
447
+ },
448
+ price: {
449
+ description: "Retail price in USD"
450
+ }
451
+ }
452
+ })
453
+ end
454
+ ```
455
+
456
+ ### Column Exclusion Options
457
+ EasyTalk provides several ways to exclude columns from your JSON schema:
458
+
459
+ #### 1. Global Configuration
460
+
461
+ ```ruby
462
+ EasyTalk.configure do |config|
463
+ # Exclude specific columns by name from all models
464
+ config.excluded_columns = [:created_at, :updated_at, :deleted_at]
465
+
466
+ # Exclude all foreign key columns (columns ending with '_id')
467
+ config.exclude_foreign_keys = true # Default: false
468
+
469
+ # Exclude all primary key columns ('id')
470
+ config.exclude_primary_key = true # Default: true
471
+
472
+ # Exclude timestamp columns ('created_at', 'updated_at')
473
+ config.exclude_timestamps = true # Default: true
474
+
475
+ # Exclude all association properties
476
+ config.exclude_associations = true # Default: false
477
+ end
478
+ ```
479
+
480
+ #### 2. Model-Specific Column Ignoring
481
+
482
+ ```ruby
483
+ class Product < ActiveRecord::Base
484
+ include EasyTalk::Model
485
+
486
+ enhance_schema({
487
+ ignore: [:internal_ref_id, :legacy_code] # Model-specific exclusions
488
+ })
489
+ end
490
+ ```
491
+
492
+ ### Virtual Properties
493
+ You can add properties that don't exist as database columns:
494
+
495
+ ```ruby
496
+ class Product < ActiveRecord::Base
497
+ include EasyTalk::Model
498
+
499
+ enhance_schema({
500
+ properties: {
501
+ full_details: {
502
+ virtual: true,
503
+ type: :string,
504
+ description: "Complete product information"
505
+ }
506
+ }
507
+ })
508
+ end
509
+ ```
510
+
511
+ ### Associations and Foreign Keys
512
+ By default, EasyTalk includes your model's associations in the schema:
513
+
514
+ ```ruby
515
+ class Product < ActiveRecord::Base
516
+ include EasyTalk::Model
517
+ belongs_to :category
518
+ has_many :reviews
519
+ end
520
+ ```
521
+
522
+ This will include `category` (as an object) and `reviews` (as an array) in the schema.
523
+
524
+ You can control this behavior with configuration:
525
+
526
+ ```ruby
527
+ EasyTalk.configure do |config|
528
+ config.exclude_associations = true # Don't include associations
529
+ config.exclude_foreign_keys = true # Don't include foreign key columns
530
+ end
531
+ ```
532
+
533
+ ## Advanced Features
534
+
535
+ ### LLM Function Generation
536
+ EasyTalk provides a helper method for generating OpenAI function specifications:
537
+
538
+ ```ruby
539
+ class Weather
540
+ include EasyTalk::Model
541
+
542
+ define_schema do
543
+ title "GetWeather"
544
+ description "Get the current weather in a given location"
545
+ property :location, String, description: "The city and state, e.g. San Francisco, CA"
546
+ property :unit, String, enum: ["celsius", "fahrenheit"], default: "fahrenheit"
547
+ end
548
+ end
549
+
550
+ function_spec = EasyTalk::Tools::FunctionBuilder.new(Weather)
551
+ ```
552
+
553
+ This generates a function specification compatible with OpenAI's function calling API.
554
+
555
+ ### Schema Transformation
556
+ You can transform EasyTalk schemas into various formats:
557
+
558
+ ```ruby
559
+ # Get Ruby hash representation
560
+ schema_hash = User.schema
561
+
562
+ # Get JSON Schema representation
563
+ json_schema = User.json_schema
564
+
565
+ # Convert to JSON string
566
+ json_string = User.json_schema.to_json
567
+ ```
568
+
569
+ ### Type Checking and Validation
570
+ EasyTalk performs basic type checking during schema definition:
571
+
572
+ ```ruby
573
+ # This will raise an error because "minimum" should be used with numeric types
574
+ property :name, String, minimum: 1 # Error!
575
+
576
+ # This will raise an error because enum values must match the property type
577
+ property :age, Integer, enum: ["young", "old"] # Error!
578
+ ```
579
+
580
+ ### Custom Type Builders
581
+ For advanced use cases, you can create custom type builders:
582
+
583
+ ```ruby
584
+ module EasyTalk
585
+ module Builders
586
+ class MyCustomTypeBuilder < BaseBuilder
587
+ # Custom implementation
588
+ end
589
+ end
590
+ end
591
+ ```
592
+
593
+ ## Configuration
127
594
 
128
- Example: A Payment object that can be a credit card, PayPal, or bank transfer:
595
+ ### Global Settings
596
+ You can configure EasyTalk globally:
129
597
 
598
+ ```ruby
599
+ EasyTalk.configure do |config|
600
+ config.excluded_columns = [:created_at, :updated_at, :deleted_at]
601
+ config.exclude_foreign_keys = true
602
+ config.exclude_primary_key = true
603
+ config.exclude_timestamps = true
604
+ config.exclude_associations = false
605
+ config.default_additional_properties = false
606
+ end
607
+ ```
608
+
609
+ ### Per-Model Configuration
610
+ Some settings can be configured per model:
611
+
612
+ ```ruby
613
+ class Product < ActiveRecord::Base
614
+ include EasyTalk::Model
615
+
616
+ enhance_schema({
617
+ additionalProperties: true,
618
+ ignore: [:internal_ref_id, :legacy_code]
619
+ })
620
+ end
621
+ ```
622
+
623
+ ### Exclusion Rules
624
+ Columns are excluded based on the following rules (in order of precedence):
625
+
626
+ 1. Explicitly listed in `excluded_columns` global setting
627
+ 2. Listed in the model's `schema_enhancements[:ignore]` array
628
+ 3. Is a primary key when `exclude_primary_key` is true (default)
629
+ 4. Is a timestamp column when `exclude_timestamps` is true (default)
630
+ 5. Matches a foreign key pattern when `exclude_foreign_keys` is true
631
+
632
+ ### Customizing Output
633
+ You can customize the JSON Schema output by enhancing the schema:
634
+
635
+ ```ruby
636
+ class User < ActiveRecord::Base
637
+ include EasyTalk::Model
638
+
639
+ enhance_schema({
640
+ title: "User Account",
641
+ description: "User account information",
642
+ properties: {
643
+ name: {
644
+ title: "Full Name",
645
+ description: "User's full name"
646
+ }
647
+ }
648
+ })
649
+ end
650
+ ```
651
+
652
+ ## Examples
653
+
654
+ ### User Registration
655
+
656
+ ```ruby
657
+ class User
658
+ include EasyTalk::Model
659
+
660
+ validates :name, :email, :password, presence: true
661
+ validates :password, length: { minimum: 8 }
662
+ validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
663
+
664
+ define_schema do
665
+ title "User Registration"
666
+ description "User registration information"
667
+ property :name, String, description: "User's full name"
668
+ property :email, String, format: "email", description: "User's email address"
669
+ property :password, String, min_length: 8, description: "User's password"
670
+ property :notify, T::Boolean, default: true, description: "Whether to send notifications"
671
+ end
672
+ end
673
+ ```
674
+
675
+ ### Payment Processing
130
676
 
131
677
  ```ruby
132
678
  class CreditCard
@@ -176,131 +722,147 @@ class Payment
176
722
  end
177
723
  ```
178
724
 
179
- ## Additional Properties
180
-
181
- EasyTalk supports the JSON Schema `additionalProperties` keyword, allowing you to control whether instances of your model can accept properties beyond those explicitly defined in the schema.
182
-
183
- ### Usage
184
-
185
- Use the `additional_properties` keyword in your schema definition to specify whether additional properties are allowed:
725
+ ### Complex Object Hierarchies
186
726
 
187
727
  ```ruby
188
- class Company
728
+ class Address
189
729
  include EasyTalk::Model
190
730
 
191
731
  define_schema do
192
- property :name, String
193
- additional_properties true # Allow additional properties
732
+ property :street, String
733
+ property :city, String
734
+ property :state, String
735
+ property :zip, String, pattern: '^[0-9]{5}(?:-[0-9]{4})?$'
194
736
  end
195
737
  end
196
738
 
197
- # Additional properties are allowed
198
- company = Company.new
199
- company.name = "Acme Corp" # Defined property
200
- company.location = "New York" # Additional property
201
- company.employee_count = 100 # Additional property
202
-
203
- company.as_json
204
- # => {
205
- # "name" => "Acme Corp",
206
- # "location" => "New York",
207
- # "employee_count" => 100
208
- # }
209
- ```
210
-
211
- ### Behavior
212
-
213
- When `additional_properties true`:
214
- - Instances can accept properties beyond those defined in the schema
215
- - Additional properties can be set both via the constructor and direct assignment
216
- - Additional properties are included in JSON serialization
217
- - Attempting to access an undefined additional property raises NoMethodError
218
-
219
- ```ruby
220
- # Setting via constructor
221
- company = Company.new(
222
- name: "Acme Corp",
223
- location: "New York" # Additional property
224
- )
225
-
226
- # Setting via assignment
227
- company.rank = 1 # Additional property
228
-
229
- # Accessing undefined properties
230
- company.undefined_prop # Raises NoMethodError
231
- ```
739
+ class Employee
740
+ include EasyTalk::Model
232
741
 
233
- When `additional_properties false` or not specified:
234
- - Only properties defined in the schema are allowed
235
- - Attempting to set or get undefined properties raises NoMethodError
742
+ define_schema do
743
+ title 'Employee'
744
+ description 'Company employee'
745
+ property :name, String, title: 'Full Name'
746
+ property :gender, String, enum: %w[male female other]
747
+ property :department, T.nilable(String)
748
+ property :hire_date, Date
749
+ property :active, T::Boolean, default: true
750
+ property :addresses, T.nilable(T::Array[Address])
751
+ end
752
+ end
236
753
 
237
- ```ruby
238
- class RestrictedCompany
754
+ class Company
239
755
  include EasyTalk::Model
240
756
 
241
757
  define_schema do
758
+ title 'Company'
242
759
  property :name, String
243
- additional_properties false # Restrict to defined properties only
760
+ property :employees, T::Array[Employee], title: 'Company Employees', description: 'A list of company employees'
244
761
  end
245
762
  end
246
-
247
- company = RestrictedCompany.new
248
- company.name = "Acme Corp" # OK - defined property
249
- company.location = "New York" # Raises NoMethodError
250
763
  ```
251
764
 
252
- ### JSON Schema
253
-
254
- The `additional_properties` setting is reflected in the generated JSON Schema:
765
+ ### API Integration
255
766
 
256
767
  ```ruby
257
- Company.json_schema
258
- # => {
259
- # "type" => "object",
260
- # "properties" => {
261
- # "name" => { "type" => "string" }
262
- # },
263
- # "required" => ["name"],
264
- # "additionalProperties" => true
265
- # }
768
+ # app/controllers/api/users_controller.rb
769
+ class Api::UsersController < ApplicationController
770
+ def create
771
+ schema = User.json_schema
772
+
773
+ # Validate incoming request against the schema
774
+ validation_result = JSONSchemer.schema(schema).valid?(params.to_json)
775
+
776
+ if validation_result
777
+ user = User.new(user_params)
778
+ if user.save
779
+ render json: user, status: :created
780
+ else
781
+ render json: { errors: user.errors }, status: :unprocessable_entity
782
+ end
783
+ else
784
+ render json: { errors: "Invalid request" }, status: :bad_request
785
+ end
786
+ end
787
+
788
+ private
789
+
790
+ def user_params
791
+ params.require(:user).permit(:name, :email, :password)
792
+ end
793
+ end
266
794
  ```
267
795
 
268
- ### Best Practices
796
+ ## Troubleshooting
269
797
 
270
- 1. **Default to Restrictive**: Unless you specifically need additional properties, it's recommended to leave `additional_properties` as false (the default) to maintain schema integrity.
798
+ ### Common Errors
271
799
 
272
- 2. **Documentation**: If you enable additional properties, document the expected additional property types and their purpose.
800
+ #### "Invalid property name"
801
+ Property names must start with a letter or underscore and can only contain letters, numbers, and underscores:
273
802
 
274
- 3. **Validation**: Consider implementing custom validation for additional properties if they need to conform to specific patterns or types.
803
+ ```ruby
804
+ # Invalid
805
+ property "1name", String # Starts with a number
806
+ property "name!", String # Contains a special character
275
807
 
276
- 4. **Error Handling**: When working with instances that allow additional properties, use `respond_to?` or `try` to handle potentially undefined properties safely:
808
+ # Valid
809
+ property :name, String
810
+ property :user_name, String
811
+ ```
812
+
813
+ #### "Property type is missing"
814
+ You must specify a type for each property:
277
815
 
278
816
  ```ruby
279
- # Safe property access
280
- value = company.try(:optional_property)
281
- # or
282
- value = company.optional_property if company.respond_to?(:optional_property)
817
+ # Invalid
818
+ property :name
819
+
820
+ # Valid
821
+ property :name, String
283
822
  ```
284
823
 
285
- ## Type Checking and Schema Constraints
824
+ #### "Unknown option"
825
+ You specified an option that is not valid for the property type:
826
+
827
+ ```ruby
828
+ # Invalid (min_length is for strings, not integers)
829
+ property :age, Integer, min_length: 2
286
830
 
287
- EasyTalk uses a combination of standard Ruby types (`String`, `Integer`), Sorbet types (`T::Boolean`, `T::Array[String]`, etc.), and custom Sorbet-style types (`T::AnyOf[]`, `T::OneOf[]`) to perform basic type checking. For example:
831
+ # Valid
832
+ property :age, Integer, minimum: 18
833
+ ```
288
834
 
289
- If you specify `enum: [1,2,3]` but the property type is `String`, EasyTalk raises a type error.
290
- If you define `minimum: 1` on a `String` property, it raises an error because minimum applies only to numeric types.
835
+ ### Schema Validation Issues
836
+ If you're having issues with validation:
291
837
 
292
- ## Schema Validation
838
+ 1. Make sure you've defined ActiveModel validations for your model
839
+ 2. Check for mismatches between schema constraints and validations
840
+ 3. Verify that required properties are present
293
841
 
294
- You can instantiate an EasyTalk model with a hash of attributes and validate it using standard ActiveModel validations. EasyTalk does not automatically validate instances; you must explicitly define ActiveModel validations in your EasyTalk model. See [spec/easy_talk/activemodel_integration_spec.rb](ActiveModel Integration Spec) for examples.
842
+ ### Type Errors
843
+ Type errors usually occur when there's a mismatch between a property type and its constraints:
295
844
 
296
- ## JSON Schema Specifications
845
+ ```ruby
846
+ # Error: enum values must be strings for a string property
847
+ property :status, String, enum: [1, 2, 3]
297
848
 
298
- EasyTalk is currently loose about JSON Schema versions. It doesn’t strictly enforce or adhere to any particular version of the specification. The goal is to add more robust support for the latest JSON Schema specs in the future.
849
+ # Correct
850
+ property :status, String, enum: ["active", "inactive", "pending"]
851
+ ```
299
852
 
300
- To learn about current capabilities, see the [spec/easy_talk/examples](https://github.com/sergiobayona/easy_talk/tree/main/spec/easy_talk/examples) folder. The examples illustrate how EasyTalk generates JSON Schema in different scenarios.
853
+ ### Best Practices
301
854
 
302
- ## Development
855
+ 1. Define clear property names and descriptions
856
+ 2. Use appropriate types for each property
857
+ 3. Add validations for important business rules
858
+ 4. Keep schemas focused and modular
859
+ 5. Reuse models when appropriate
860
+ 6. Use explicit types instead of relying on inference
861
+ 7. Test your schemas with sample data
303
862
 
863
+ ## Development and Contributing
864
+
865
+ ### Setting Up the Development Environment
304
866
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that lets you experiment.
305
867
 
306
868
  To install this gem onto your local machine, run:
@@ -309,11 +871,29 @@ To install this gem onto your local machine, run:
309
871
  bundle exec rake install
310
872
  ```
311
873
 
312
- ## Contributing
874
+ ### Running Tests
875
+ Run the test suite with:
876
+
877
+ ```bash
878
+ bundle exec rake spec
879
+ ```
880
+
881
+ ### Contributing Guidelines
882
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
313
883
 
314
- Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
884
+ ## JSON Schema Compatibility
885
+
886
+ ### Supported Versions
887
+ EasyTalk is currently loose about JSON Schema versions. It doesn't strictly enforce or adhere to any particular version of the specification. The goal is to add more robust support for the latest JSON Schema specs in the future.
888
+
889
+ ### Specification Compliance
890
+ To learn about current capabilities, see the [spec/easy_talk/examples](https://github.com/sergiobayona/easy_talk/tree/main/spec/easy_talk/examples) folder. The examples illustrate how EasyTalk generates JSON Schema in different scenarios.
891
+
892
+ ### Known Limitations
893
+ - Limited support for custom formats
894
+ - No direct support for JSON Schema draft 2020-12 features
895
+ - Complex composition scenarios may require manual adjustment
315
896
 
316
897
  ## License
317
898
 
318
899
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
319
-