easy_talk 1.0.3 → 2.0.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,5 +1,8 @@
1
1
  # EasyTalk
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/easy_talk.svg)](https://badge.fury.io/rb/easy_talk)
4
+ [![Ruby](https://github.com/sergiobayona/easy_talk/actions/workflows/dev-build.yml/badge.svg)](https://github.com/sergiobayona/easy_talk/actions/workflows/dev-build.yml)
5
+
3
6
  ## Introduction
4
7
 
5
8
  ### What is EasyTalk?
@@ -7,10 +10,12 @@ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema.
7
10
 
8
11
  ### Key Features
9
12
  * **Intuitive Schema Definition**: Use Ruby classes and methods to define JSON Schema documents easily.
13
+ * **Automatic ActiveModel Validations**: Schema constraints automatically generate corresponding ActiveModel validations (configurable).
10
14
  * **Works for plain Ruby classes and ActiveRecord models**: Integrate with existing code or build from scratch.
11
15
  * **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
16
  * **Schema Composition**: Define EasyTalk models and reference them in other EasyTalk models to create complex schemas.
13
- * **Validation**: Write validations using ActiveModel's validations.
17
+ * **Enhanced Model Integration**: Automatic instantiation of nested EasyTalk models from hash attributes.
18
+ * **Flexible Configuration**: Global and per-model configuration options for fine-tuned control.
14
19
 
15
20
  ### Use Cases
16
21
  - API request/response validation
@@ -26,8 +31,39 @@ Inspired by Python's Pydantic library, EasyTalk brings similar functionality to
26
31
 
27
32
  ### Requirements
28
33
  - Ruby 3.2 or higher
29
- - ActiveModel 7.0 or higher
30
- - ActiveSupport 7.0 or higher
34
+
35
+ ### Version 2.0.0 Breaking Changes
36
+
37
+ ⚠️ **IMPORTANT**: Version 2.0.0 includes breaking changes. Please review before upgrading:
38
+
39
+ - **Removed**: Block-style nested object definitions (using `Hash do ... end`)
40
+ - **Migration**: Use class references instead of inline Hash definitions
41
+
42
+ ```ruby
43
+ # ❌ No longer supported (v1.x style)
44
+ define_schema do
45
+ property :address, Hash do
46
+ property :street, String
47
+ property :city, String
48
+ end
49
+ end
50
+
51
+ # ✅ New approach (v2.x style)
52
+ class Address
53
+ include EasyTalk::Model
54
+ define_schema do
55
+ property :street, String
56
+ property :city, String
57
+ end
58
+ end
59
+
60
+ class User
61
+ include EasyTalk::Model
62
+ define_schema do
63
+ property :address, Address # Reference the class directly
64
+ end
65
+ end
66
+ ```
31
67
 
32
68
  ### Installation Steps
33
69
  Add EasyTalk to your application's Gemfile:
@@ -109,10 +145,11 @@ Creating and validating an instance of your model:
109
145
 
110
146
  ```ruby
111
147
  user = User.new(name: "John Doe", email: "john@example.com", age: 25)
112
- user.valid? # => true
148
+ user.valid? # => true (automatically validates based on schema constraints)
113
149
 
114
150
  user.age = 17
115
- user.valid? # => false
151
+ user.valid? # => false (violates minimum: 18 constraint)
152
+ user.errors[:age] # => ["must be greater than or equal to 18"]
116
153
  ```
117
154
 
118
155
  ## Core Concepts
@@ -143,7 +180,6 @@ EasyTalk supports standard Ruby types directly:
143
180
  - `Float`: Floating-point numbers
144
181
  - `Date`: Date values
145
182
  - `DateTime`: Date and time values
146
- - `Hash`: Object/dictionary values
147
183
 
148
184
  #### Sorbet-Style Types
149
185
  For complex types, EasyTalk uses Sorbet-style type notation:
@@ -184,25 +220,57 @@ end
184
220
 
185
221
  In this example, `name` is required but `middle_name` is optional.
186
222
 
187
- ### Schema Validation
188
- EasyTalk models include ActiveModel validations. You can validate your models using the standard ActiveModel validation methods:
223
+ ### Automatic Validation Generation
224
+ EasyTalk automatically generates ActiveModel validations from your schema constraints. This feature is enabled by default but can be configured:
189
225
 
190
226
  ```ruby
191
227
  class User
192
228
  include EasyTalk::Model
193
229
 
194
- validates :name, presence: true
195
- validates :age, numericality: { greater_than_or_equal_to: 18 }
230
+ define_schema do
231
+ property :name, String, min_length: 2, max_length: 50
232
+ property :email, String, format: "email"
233
+ property :age, Integer, minimum: 18, maximum: 120
234
+ property :status, String, enum: ["active", "inactive", "pending"]
235
+ end
236
+ # Validations are automatically generated:
237
+ # validates :name, presence: true, length: { minimum: 2, maximum: 50 }
238
+ # validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
239
+ # validates :age, presence: true, numericality: { greater_than_or_equal_to: 18, less_than_or_equal_to: 120 }
240
+ # validates :status, presence: true, inclusion: { in: ["active", "inactive", "pending"] }
241
+ end
242
+
243
+ user = User.new(name: "Jo", email: "invalid-email", age: 17)
244
+ user.valid? # => false
245
+ user.errors.full_messages
246
+ # => ["Name is too short (minimum is 2 characters)",
247
+ # "Email is invalid",
248
+ # "Age must be greater than or equal to 18"]
249
+ ```
250
+
251
+ ### Manual Validation Overrides
252
+ You can still add manual validations alongside automatic ones:
253
+
254
+ ```ruby
255
+ class User
256
+ include EasyTalk::Model
257
+
258
+ # Custom validation in addition to automatic ones
259
+ validates :email, uniqueness: true
260
+ validate :complex_business_rule
196
261
 
197
262
  define_schema do
198
263
  property :name, String
264
+ property :email, String, format: "email"
199
265
  property :age, Integer, minimum: 18
200
266
  end
267
+
268
+ private
269
+
270
+ def complex_business_rule
271
+ # Custom validation logic
272
+ end
201
273
  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
274
  ```
207
275
 
208
276
  ## Defining Schemas
@@ -230,16 +298,6 @@ property :name, String, description: "The person's name", title: "Full Name"
230
298
  property :age, Integer, minimum: 0, maximum: 120, description: "The person's age"
231
299
  ```
232
300
 
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
- ```
242
-
243
301
  ### Arrays and Collections
244
302
  Arrays can be defined using the `T::Array` type:
245
303
 
@@ -254,22 +312,38 @@ You can also define arrays of complex types:
254
312
  property :addresses, T::Array[Address], description: "List of addresses"
255
313
  ```
256
314
 
257
- ### Constraints and Validations
258
- Constraints can be added to properties and are used for schema generation:
315
+ ### Constraints and Automatic Validations
316
+ Constraints are added to properties and are used for both schema generation and automatic validation generation:
259
317
 
260
318
  ```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"
319
+ define_schema do
320
+ property :name, String, min_length: 2, max_length: 50
321
+ property :email, String, format: "email"
322
+ property :category, String, enum: ["A", "B", "C"]
323
+ property :score, Float, minimum: 0.0, maximum: 100.0
324
+ property :tags, T::Array[String], min_items: 1, max_items: 10
325
+ end
326
+ # Automatically generates equivalent ActiveModel validations:
327
+ # validates :name, presence: true, length: { minimum: 2, maximum: 50 }
328
+ # validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
329
+ # validates :category, presence: true, inclusion: { in: ["A", "B", "C"] }
330
+ # validates :score, presence: true, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }
331
+ # validates :tags, presence: true, length: { minimum: 1, maximum: 10 }
264
332
  ```
265
333
 
266
- For validation, you can use ActiveModel validations:
334
+ ### Supported Constraint-to-Validation Mappings
267
335
 
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
- ```
336
+ | Constraint | Validation Generated |
337
+ |------------|---------------------|
338
+ | `min_length`, `max_length` | `length: { minimum: X, maximum: Y }` |
339
+ | `minimum`, `maximum` | `numericality: { greater_than_or_equal_to: X, less_than_or_equal_to: Y }` |
340
+ | `format: "email"` | `format: { with: URI::MailTo::EMAIL_REGEXP }` |
341
+ | `format: "url"` or `format: "uri"` | `format: { with: URI::regexp }` |
342
+ | `pattern: /regex/` | `format: { with: /regex/ }` |
343
+ | `enum: [...]` | `inclusion: { in: [...] }` |
344
+ | `min_items`, `max_items` (arrays) | `length: { minimum: X, maximum: Y }` |
345
+ | `optional: true` | Skips presence validation |
346
+ | `T.nilable(Type)` | Allows nil values, skips presence validation |
273
347
 
274
348
  ### Additional Properties
275
349
  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:
@@ -373,20 +447,21 @@ end
373
447
 
374
448
  ## ActiveModel Integration
375
449
 
376
- ### Validations
377
- EasyTalk models include ActiveModel validations:
450
+ ### Enhanced Validation System
451
+ EasyTalk models include comprehensive ActiveModel validation support with automatic generation:
378
452
 
379
453
  ```ruby
380
454
  class User
381
455
  include EasyTalk::Model
382
456
 
383
- validates :age, comparison: { greater_than: 21 }
384
- validates :height, presence: true, numericality: { greater_than: 0 }
457
+ # Manual validations work alongside automatic ones
458
+ validates :age, comparison: { greater_than: 21 } # Additional business rule
459
+ validates :height, numericality: { greater_than: 0 } # Overrides auto-validation
385
460
 
386
461
  define_schema do
387
- property :name, String
388
- property :age, Integer
389
- property :height, Float
462
+ property :name, String, min_length: 2 # Auto-generates presence + length validations
463
+ property :age, Integer, minimum: 18 # Auto-generates presence + numericality validations
464
+ property :height, Float # Auto-generates presence validation (overridden above)
390
465
  end
391
466
  end
392
467
  ```
@@ -395,10 +470,11 @@ end
395
470
  You can access validation errors using the standard ActiveModel methods:
396
471
 
397
472
  ```ruby
398
- user = User.new(name: "Jim", age: 18, height: -5.9)
473
+ user = User.new(name: "J", age: 18, height: -5.9)
399
474
  user.valid? # => false
400
- user.errors[:age] # => ["must be greater than 21"]
401
- user.errors[:height] # => ["must be greater than 0"]
475
+ user.errors[:name] # => ["is too short (minimum is 2 characters)"]
476
+ user.errors[:age] # => ["must be greater than 21"] # Custom validation
477
+ user.errors[:height] # => ["must be greater than 0"] # Overridden validation
402
478
  ```
403
479
 
404
480
  ### Model Attributes
@@ -411,10 +487,35 @@ user.age = 30
411
487
  puts user.name # => "John"
412
488
  ```
413
489
 
414
- You can also initialize a model with a hash of attributes:
490
+ You can also initialize a model with a hash of attributes, including nested EasyTalk models:
415
491
 
416
492
  ```ruby
417
493
  user = User.new(name: "John", age: 30, height: 5.9)
494
+
495
+ # NEW in v2.0.0: Automatic nested model instantiation
496
+ class Address
497
+ include EasyTalk::Model
498
+ define_schema do
499
+ property :street, String
500
+ property :city, String
501
+ end
502
+ end
503
+
504
+ class User
505
+ include EasyTalk::Model
506
+ define_schema do
507
+ property :name, String
508
+ property :address, Address
509
+ end
510
+ end
511
+
512
+ # Hash attributes automatically instantiate nested models
513
+ user = User.new(
514
+ name: "John",
515
+ address: { street: "123 Main St", city: "Boston" }
516
+ )
517
+ user.address.class # => Address (automatically instantiated)
518
+ user.address.street # => "123 Main St"
418
519
  ```
419
520
 
420
521
  ## ActiveRecord Integration
@@ -597,12 +698,42 @@ You can configure EasyTalk globally:
597
698
 
598
699
  ```ruby
599
700
  EasyTalk.configure do |config|
701
+ # ActiveRecord integration options
600
702
  config.excluded_columns = [:created_at, :updated_at, :deleted_at]
601
703
  config.exclude_foreign_keys = true
602
704
  config.exclude_primary_key = true
603
705
  config.exclude_timestamps = true
604
706
  config.exclude_associations = false
707
+
708
+ # Schema behavior options
605
709
  config.default_additional_properties = false
710
+ config.nilable_is_optional = false # Makes T.nilable properties also optional
711
+
712
+ # NEW in v2.0.0: Automatic validation generation
713
+ config.auto_validations = true # Automatically generate ActiveModel validations
714
+ end
715
+ ```
716
+
717
+ ### Automatic Validation Configuration
718
+ The new `auto_validations` option (enabled by default) automatically generates ActiveModel validations from your schema constraints:
719
+
720
+ ```ruby
721
+ # Disable automatic validations globally
722
+ EasyTalk.configure do |config|
723
+ config.auto_validations = false
724
+ end
725
+
726
+ # Now you must manually define validations
727
+ class User
728
+ include EasyTalk::Model
729
+
730
+ validates :name, presence: true, length: { minimum: 2 }
731
+ validates :age, presence: true, numericality: { greater_than_or_equal_to: 18 }
732
+
733
+ define_schema do
734
+ property :name, String, min_length: 2
735
+ property :age, Integer, minimum: 18
736
+ end
606
737
  end
607
738
  ```
608
739
 
@@ -651,25 +782,51 @@ end
651
782
 
652
783
  ## Examples
653
784
 
654
- ### User Registration
785
+ ### User Registration (with Auto-Validations)
655
786
 
656
787
  ```ruby
657
788
  class User
658
789
  include EasyTalk::Model
659
790
 
660
- validates :name, :email, :password, presence: true
661
- validates :password, length: { minimum: 8 }
662
- validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
791
+ # Additional custom validations beyond automatic ones
792
+ validates :email, uniqueness: true
793
+ validates :password, confirmation: true
663
794
 
664
795
  define_schema do
665
796
  title "User Registration"
666
797
  description "User registration information"
667
- property :name, String, description: "User's full name"
798
+ property :name, String, min_length: 2, max_length: 100, description: "User's full name"
668
799
  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"
800
+ property :password, String, min_length: 8, max_length: 128, description: "User's password"
801
+ property :notify, T::Boolean, optional: true, description: "Whether to send notifications"
671
802
  end
803
+ # Auto-generated validations:
804
+ # validates :name, presence: true, length: { minimum: 2, maximum: 100 }
805
+ # validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
806
+ # validates :password, presence: true, length: { minimum: 8, maximum: 128 }
807
+ # validates :notify, inclusion: { in: [true, false] } - only if present (optional: true)
672
808
  end
809
+
810
+ # Usage with automatic validation
811
+ user = User.new(
812
+ name: "John Doe",
813
+ email: "john@example.com",
814
+ password: "secretpassword123",
815
+ notify: true
816
+ )
817
+ user.valid? # => true (assuming email is unique)
818
+
819
+ # Invalid data triggers auto-generated validations
820
+ invalid_user = User.new(
821
+ name: "J", # Too short
822
+ email: "invalid-email", # Invalid format
823
+ password: "123" # Too short
824
+ )
825
+ invalid_user.valid? # => false
826
+ invalid_user.errors.full_messages
827
+ # => ["Name is too short (minimum is 2 characters)",
828
+ # "Email is invalid",
829
+ # "Password is too short (minimum is 8 characters)"]
673
830
  ```
674
831
 
675
832
  ### Payment Processing
@@ -852,13 +1009,285 @@ property :status, String, enum: ["active", "inactive", "pending"]
852
1009
 
853
1010
  ### Best Practices
854
1011
 
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
1012
+ 1. **Define clear property names and descriptions** for better documentation
1013
+ 2. **Use appropriate types** for each property with proper constraints
1014
+ 3. **Leverage automatic validations** by defining schema constraints instead of manual validations
1015
+ 4. **Keep schemas focused and modular** - extract nested objects to separate classes
1016
+ 5. **Reuse models when appropriate** instead of duplicating schema definitions
1017
+ 6. **Use explicit types** instead of relying on inference
1018
+ 7. **Test your schemas with sample data** to ensure validations work as expected
1019
+ 8. **Configure auto-validations globally** to maintain consistency across your application
1020
+ 9. **Use nullable_optional_property** for fields that can be omitted or null
1021
+ 10. **Document breaking changes** when updating schema definitions
1022
+
1023
+ # Nullable vs Optional Properties in EasyTalk
1024
+
1025
+ One of the most important distinctions when defining schemas is understanding the difference between **nullable** properties and **optional** properties. This guide explains these concepts and how to use them effectively in EasyTalk.
1026
+
1027
+ ## Key Concepts
1028
+
1029
+ | Concept | Description | JSON Schema Effect | EasyTalk Syntax |
1030
+ |---------|-------------|-------------------|-----------------|
1031
+ | **Nullable** | Property can have a `null` value | Adds `"null"` to the type array | `T.nilable(Type)` |
1032
+ | **Optional** | Property doesn't have to exist | Omits property from `"required"` array | `optional: true` constraint |
1033
+
1034
+ ## Nullable Properties
1035
+
1036
+ A **nullable** property can contain a `null` value, but the property itself must still be present in the object:
1037
+
1038
+ ```ruby
1039
+ property :age, T.nilable(Integer)
1040
+ ```
1041
+
1042
+ This produces the following JSON Schema:
1043
+
1044
+ ```json
1045
+ {
1046
+ "properties": {
1047
+ "age": { "type": ["integer", "null"] }
1048
+ },
1049
+ "required": ["age"]
1050
+ }
1051
+ ```
1052
+
1053
+ In this case, the following data would be valid:
1054
+ - `{ "age": 25 }`
1055
+ - `{ "age": null }`
1056
+
1057
+ But this would be invalid:
1058
+ - `{ }` (missing the age property entirely)
1059
+
1060
+ ## Optional Properties
1061
+
1062
+ An **optional** property doesn't have to be present in the object at all:
1063
+
1064
+ ```ruby
1065
+ property :nickname, String, optional: true
1066
+ ```
1067
+
1068
+ This produces:
1069
+
1070
+ ```json
1071
+ {
1072
+ "properties": {
1073
+ "nickname": { "type": "string" }
1074
+ }
1075
+ // Note: "nickname" is not in the "required" array
1076
+ }
1077
+ ```
1078
+
1079
+ In this case, the following data would be valid:
1080
+ - `{ "nickname": "Joe" }`
1081
+ - `{ }` (omitting nickname entirely)
1082
+
1083
+ But this would be invalid:
1084
+ - `{ "nickname": null }` (null is not allowed because the property isn't nullable)
1085
+
1086
+ ## Nullable AND Optional Properties
1087
+
1088
+ For properties that should be both nullable and optional (can be omitted or null), you need to combine both approaches:
1089
+
1090
+ ```ruby
1091
+ property :bio, T.nilable(String), optional: true
1092
+ ```
1093
+
1094
+ This produces:
1095
+
1096
+ ```json
1097
+ {
1098
+ "properties": {
1099
+ "bio": { "type": ["string", "null"] }
1100
+ }
1101
+ // Note: "bio" is not in the "required" array
1102
+ }
1103
+ ```
1104
+
1105
+ For convenience, EasyTalk also provides a helper method:
1106
+
1107
+ ```ruby
1108
+ nullable_optional_property :bio, String
1109
+ ```
1110
+
1111
+ Which is equivalent to the above.
1112
+
1113
+ ## Configuration Options
1114
+
1115
+ By default, nullable properties are still required. You can change this global behavior:
1116
+
1117
+ ```ruby
1118
+ EasyTalk.configure do |config|
1119
+ config.nilable_is_optional = true # Makes all T.nilable properties also optional
1120
+ end
1121
+ ```
1122
+
1123
+ With this configuration, any property defined with `T.nilable(Type)` will be treated as both nullable and optional.
1124
+
1125
+ ## Practical Examples
1126
+
1127
+ ### User Profile Schema
1128
+
1129
+ ```ruby
1130
+ class UserProfile
1131
+ include EasyTalk::Model
1132
+
1133
+ define_schema do
1134
+ # Required properties (must exist, cannot be null)
1135
+ property :id, String
1136
+ property :name, String
1137
+
1138
+ # Required but nullable (must exist, can be null)
1139
+ property :age, T.nilable(Integer)
1140
+
1141
+ # Optional but not nullable (can be omitted, cannot be null if present)
1142
+ property :email, String, optional: true
1143
+
1144
+ # Optional and nullable (can be omitted, can be null if present)
1145
+ nullable_optional_property :bio, String
1146
+ end
1147
+ end
1148
+ ```
1149
+
1150
+ This creates clear expectations for data validation:
1151
+ - `id` and `name` must be present and cannot be null
1152
+ - `age` must be present but can be null
1153
+ - `email` doesn't have to be present, but if it is, it cannot be null
1154
+ - `bio` doesn't have to be present, and if it is, it can be null
1155
+
1156
+ ## Common Gotchas
1157
+
1158
+ ### Misconception: Nullable Implies Optional
1159
+
1160
+ A common mistake is assuming that `T.nilable(Type)` makes a property optional. By default, it only allows the property to have a null value - the property itself is still required to exist in the object.
1161
+
1162
+ ### Misconception: Optional Properties Accept Null
1163
+
1164
+ An optional property (defined with `optional: true`) can be omitted entirely, but if it is present, it must conform to its type constraint. If you want to allow null values, you must also make it nullable with `T.nilable(Type)`.
1165
+
1166
+ ## Migration from Earlier Versions
1167
+
1168
+ If you're upgrading from EasyTalk version 1.0.1 or earlier, be aware that the handling of nullable vs optional properties has been improved for clarity.
1169
+
1170
+ To maintain backward compatibility with your existing code, you can use:
1171
+
1172
+ ```ruby
1173
+ EasyTalk.configure do |config|
1174
+ config.nilable_is_optional = true # Makes T.nilable properties behave as they did before
1175
+ end
1176
+ ```
1177
+
1178
+ We recommend updating your schema definitions to explicitly declare which properties are optional using the `optional: true` constraint, as this makes your intent clearer.
1179
+
1180
+ ## Best Practices
1181
+
1182
+ 1. **Be explicit about intent**: Always clarify whether properties should be nullable, optional, or both
1183
+ 2. **Use the helper method**: For properties that are both nullable and optional, use `nullable_optional_property`
1184
+ 3. **Document expectations**: Use comments to clarify validation requirements for complex schemas
1185
+ 4. **Consider validation implications**: Remember that ActiveModel validations operate independently of the schema definition
1186
+
1187
+ ## JSON Schema Comparison
1188
+
1189
+ | EasyTalk Definition | Required | Nullable | JSON Schema Equivalent |
1190
+ |--------------------|----------|----------|------------------------|
1191
+ | `property :p, String` | Yes | No | `{ "properties": { "p": { "type": "string" } }, "required": ["p"] }` |
1192
+ | `property :p, T.nilable(String)` | Yes | Yes | `{ "properties": { "p": { "type": ["string", "null"] } }, "required": ["p"] }` |
1193
+ | `property :p, String, optional: true` | No | No | `{ "properties": { "p": { "type": "string" } } }` |
1194
+ | `nullable_optional_property :p, String` | No | Yes | `{ "properties": { "p": { "type": ["string", "null"] } } }` |
1195
+
1196
+ ## Migration Guide from v1.x to v2.0
1197
+
1198
+ ### Breaking Changes Summary
1199
+
1200
+ 1. **Removed Block-Style Sub-Schemas**: Hash-based nested definitions are no longer supported
1201
+ 2. **Enhanced Validation System**: Automatic validation generation is now enabled by default
1202
+ 3. **Improved Model Initialization**: Better support for nested model instantiation
1203
+
1204
+ ### Migration Steps
1205
+
1206
+ #### 1. Replace Hash-based Nested Schemas
1207
+
1208
+ ```ruby
1209
+ # OLD (v1.x) - No longer works
1210
+ class User
1211
+ include EasyTalk::Model
1212
+ define_schema do
1213
+ property :address, Hash do
1214
+ property :street, String
1215
+ property :city, String
1216
+ end
1217
+ end
1218
+ end
1219
+
1220
+ # NEW (v2.x) - Extract to separate classes
1221
+ class Address
1222
+ include EasyTalk::Model
1223
+ define_schema do
1224
+ property :street, String
1225
+ property :city, String
1226
+ end
1227
+ end
1228
+
1229
+ class User
1230
+ include EasyTalk::Model
1231
+ define_schema do
1232
+ property :address, Address
1233
+ end
1234
+ end
1235
+ ```
1236
+
1237
+ #### 2. Review Automatic Validations
1238
+
1239
+ With `auto_validations: true` (default), you may need to remove redundant manual validations:
1240
+
1241
+ ```ruby
1242
+ # OLD (v1.x) - Manual validations required
1243
+ class User
1244
+ include EasyTalk::Model
1245
+
1246
+ validates :name, presence: true, length: { minimum: 2 }
1247
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
1248
+
1249
+ define_schema do
1250
+ property :name, String
1251
+ property :email, String
1252
+ end
1253
+ end
1254
+
1255
+ # NEW (v2.x) - Automatic validations from constraints
1256
+ class User
1257
+ include EasyTalk::Model
1258
+
1259
+ # Only add validations not covered by schema constraints
1260
+ validates :email, uniqueness: true
1261
+
1262
+ define_schema do
1263
+ property :name, String, min_length: 2 # Auto-generates presence + length
1264
+ property :email, String, format: "email" # Auto-generates presence + format
1265
+ end
1266
+ end
1267
+ ```
1268
+
1269
+ #### 3. Configuration Updates
1270
+
1271
+ Review your configuration for new options:
1272
+
1273
+ ```ruby
1274
+ EasyTalk.configure do |config|
1275
+ # New option in v2.0
1276
+ config.auto_validations = true # Enable/disable automatic validation generation
1277
+
1278
+ # Existing options (unchanged)
1279
+ config.nilable_is_optional = false
1280
+ config.exclude_foreign_keys = true
1281
+ # ... other existing config
1282
+ end
1283
+ ```
1284
+
1285
+ ### Compatibility Notes
1286
+
1287
+ - **Ruby Version**: Still requires Ruby 3.2+
1288
+ - **Dependencies**: Core dependencies remain the same
1289
+ - **JSON Schema Output**: No changes to generated schemas
1290
+ - **ActiveRecord Integration**: Fully backward compatible
862
1291
 
863
1292
  ## Development and Contributing
864
1293
 
@@ -878,6 +1307,13 @@ Run the test suite with:
878
1307
  bundle exec rake spec
879
1308
  ```
880
1309
 
1310
+ ### Code Quality
1311
+ Run the linter:
1312
+
1313
+ ```bash
1314
+ bundle exec rubocop
1315
+ ```
1316
+
881
1317
  ### Contributing Guidelines
882
1318
  Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
883
1319