easy_talk 1.0.4 → 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:
225
+
226
+ ```ruby
227
+ class User
228
+ include EasyTalk::Model
229
+
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:
189
253
 
190
254
  ```ruby
191
255
  class User
192
256
  include EasyTalk::Model
193
257
 
194
- validates :name, presence: true
195
- validates :age, numericality: { greater_than_or_equal_to: 18 }
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,16 @@ 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
862
1022
 
863
1023
  # Nullable vs Optional Properties in EasyTalk
864
1024
 
@@ -983,14 +1143,6 @@ class UserProfile
983
1143
 
984
1144
  # Optional and nullable (can be omitted, can be null if present)
985
1145
  nullable_optional_property :bio, String
986
-
987
- # Nested object with mixed property types
988
- property :address, Hash do
989
- property :street, String # Required
990
- property :city, String # Required
991
- property :state, String, optional: true # Optional
992
- nullable_optional_property :zip, String # Optional and nullable
993
- end
994
1146
  end
995
1147
  end
996
1148
  ```
@@ -1041,6 +1193,102 @@ We recommend updating your schema definitions to explicitly declare which proper
1041
1193
  | `property :p, String, optional: true` | No | No | `{ "properties": { "p": { "type": "string" } } }` |
1042
1194
  | `nullable_optional_property :p, String` | No | Yes | `{ "properties": { "p": { "type": ["string", "null"] } } }` |
1043
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
1291
+
1044
1292
  ## Development and Contributing
1045
1293
 
1046
1294
  ### Setting Up the Development Environment
@@ -1059,6 +1307,13 @@ Run the test suite with:
1059
1307
  bundle exec rake spec
1060
1308
  ```
1061
1309
 
1310
+ ### Code Quality
1311
+ Run the linter:
1312
+
1313
+ ```bash
1314
+ bundle exec rubocop
1315
+ ```
1316
+
1062
1317
  ### Contributing Guidelines
1063
1318
  Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/easy_talk.
1064
1319
 
data/easy_talk.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/easy_talk/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'easy_talk'
7
+ spec.version = EasyTalk::VERSION
8
+ spec.authors = ['Sergio Bayona']
9
+ spec.email = ['bayona.sergio@gmail.com']
10
+
11
+ spec.summary = 'Generate json-schema from Ruby classes.'
12
+ spec.description = 'Generate json-schema from plain Ruby classes.'
13
+ spec.homepage = 'https://github.com/sergiobayona/easy_talk'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.2'
16
+
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['changelog_uri'] = 'https://github.com/sergiobayona/easy_talk/blob/main/CHANGELOG.md'
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (File.expand_path(f) == __FILE__) ||
27
+ f.start_with?(*%w[bin/ spec/ .git .github Gemfile])
28
+ end
29
+ end
30
+
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'activemodel', '~> 7.0'
34
+ spec.add_dependency 'activesupport', '~> 7.0'
35
+ spec.add_dependency 'sorbet-runtime', '~> 0.5'
36
+
37
+ spec.metadata['rubygems_mfa_required'] = 'true'
38
+ end
@@ -136,7 +136,11 @@ module EasyTalk
136
136
  end
137
137
 
138
138
  # Add default value if present and not a proc
139
- constraints[:default] = column.default if column.default && !column.default.is_a?(Proc)
139
+ if column.default && !column.default.is_a?(Proc) && column.type == :boolean
140
+ constraints[:default] = ActiveModel::Type::Boolean.new.cast(column.default)
141
+ elsif column.default && !column.default.is_a?(Proc)
142
+ constraints[:default] = column.default
143
+ end
140
144
 
141
145
  # Remove nil values
142
146
  constraints.compact
@@ -50,7 +50,18 @@ module EasyTalk
50
50
  # @return [Array<Hash>] The array of schemas.
51
51
  def schemas
52
52
  items.map do |type|
53
- type.respond_to?(:schema) ? type.schema : { type: type.to_s.downcase }
53
+ if type.respond_to?(:schema)
54
+ type.schema
55
+ else
56
+ # Map Float type to 'number' in JSON Schema
57
+ json_type = case type.to_s
58
+ when 'Float', 'BigDecimal'
59
+ 'number'
60
+ else
61
+ type.to_s.downcase
62
+ end
63
+ { type: json_type }
64
+ end
54
65
  end
55
66
  end
56
67
 
@@ -8,7 +8,7 @@ module EasyTalk
8
8
  # ObjectBuilder is responsible for turning a SchemaDefinition of an "object" type
9
9
  # into a validated JSON Schema hash. It:
10
10
  #
11
- # 1) Recursively processes the schemas :properties,
11
+ # 1) Recursively processes the schema's :properties,
12
12
  # 2) Determines which properties are required (unless optional),
13
13
  # 3) Handles sub-schema composition (allOf, anyOf, oneOf, not),
14
14
  # 4) Produces the final object-level schema hash.
@@ -55,7 +55,7 @@ module EasyTalk
55
55
 
56
56
  ##
57
57
  # Main aggregator: merges the top-level schema keys (like :properties, :subschemas)
58
- # into a single hash that well feed to BaseBuilder.
58
+ # into a single hash that we'll feed to BaseBuilder.
59
59
  def build_options_hash
60
60
  # Start with a copy of the raw schema
61
61
  merged = @original_schema.dup
@@ -119,9 +119,7 @@ module EasyTalk
119
119
  return true if prop_options.dig(:constraints, :optional)
120
120
 
121
121
  # Check for nil_optional config to determine if nilable should also mean optional
122
- if prop_options[:type].respond_to?(:nilable?) && prop_options[:type].nilable?
123
- return EasyTalk.configuration.nilable_is_optional
124
- end
122
+ return EasyTalk.configuration.nilable_is_optional if prop_options[:type].respond_to?(:nilable?) && prop_options[:type].nilable?
125
123
 
126
124
  false
127
125
  end
@@ -134,24 +132,12 @@ module EasyTalk
134
132
  @property_cache ||= {}
135
133
 
136
134
  # Memoize so we only build each property once
137
- @property_cache[prop_name] ||= if prop_options[:properties]
138
- # This indicates block-style definition => nested schema
139
- nested_schema_builder(prop_options)
140
- else
141
- # Remove optional constraints from the property
142
- constraints = prop_options[:constraints].except(:optional)
143
- # Normal property: e.g. { type: String, constraints: {...} }
144
- Property.new(prop_name, prop_options[:type], constraints)
145
- end
146
- end
147
-
148
- ##
149
- # Build a child schema by calling another ObjectBuilder on the nested SchemaDefinition.
150
- #
151
- def nested_schema_builder(prop_options)
152
- child_schema_def = prop_options[:properties]
153
- # If user used T.nilable(...) with a block, unwrap the nilable
154
- ObjectBuilder.new(child_schema_def).build
135
+ @property_cache[prop_name] ||= begin
136
+ # Remove optional constraints from the property
137
+ constraints = prop_options[:constraints].except(:optional)
138
+ # Normal property: e.g. { type: String, constraints: {...} }
139
+ Property.new(prop_name, prop_options[:type], constraints)
140
+ end
155
141
  end
156
142
 
157
143
  ##