easy_talk 2.0.0 → 3.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.
metadata CHANGED
@@ -1,42 +1,68 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_talk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Bayona
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-05 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activemodel
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
- - - "~>"
26
+ - - ">="
24
27
  - !ruby/object:Gem::Version
25
28
  version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: activesupport
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
- - - "~>"
36
+ - - ">="
31
37
  - !ruby/object:Gem::Version
32
38
  version: '7.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9.0'
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
- - - "~>"
46
+ - - ">="
38
47
  - !ruby/object:Gem::Version
39
48
  version: '7.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9.0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: js_regex
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '3.0'
59
+ type: :runtime
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '3.0'
40
66
  - !ruby/object:Gem::Dependency
41
67
  name: sorbet-runtime
42
68
  requirement: !ruby/object:Gem::Requirement
@@ -51,7 +77,8 @@ dependencies:
51
77
  - - "~>"
52
78
  - !ruby/object:Gem::Version
53
79
  version: '0.5'
54
- description: Generate json-schema from plain Ruby classes.
80
+ description: Generate json-schema from plain Ruby classes with ActiveModel integration
81
+ for validations and serialization.
55
82
  email:
56
83
  - bayona.sergio@gmail.com
57
84
  executables: []
@@ -73,7 +100,6 @@ files:
73
100
  - docs/index.markdown
74
101
  - easy_talk.gemspec
75
102
  - lib/easy_talk.rb
76
- - lib/easy_talk/active_record_schema_builder.rb
77
103
  - lib/easy_talk/builders/base_builder.rb
78
104
  - lib/easy_talk/builders/boolean_builder.rb
79
105
  - lib/easy_talk/builders/collection_helpers.rb
@@ -121,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
147
  - !ruby/object:Gem::Version
122
148
  version: '0'
123
149
  requirements: []
124
- rubygems_version: 3.6.2
150
+ rubygems_version: 3.7.2
125
151
  specification_version: 4
126
- summary: Generate json-schema from Ruby classes.
152
+ summary: Generate json-schema from Ruby classes with ActiveModel integration.
127
153
  test_files: []
@@ -1,299 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module EasyTalk
4
- # This class is responsible for building a SchemaDefinition from an ActiveRecord model
5
- # It analyzes the database schema and creates a SchemaDefinition that can be
6
- # passed to ObjectBuilder for final schema generation
7
- class ActiveRecordSchemaBuilder
8
- # Mapping of ActiveRecord column types to Ruby classes
9
- COLUMN_TYPE_MAP = {
10
- string: String,
11
- text: String,
12
- integer: Integer,
13
- bigint: Integer,
14
- float: Float,
15
- decimal: Float,
16
- boolean: T::Boolean,
17
- date: Date,
18
- datetime: DateTime,
19
- timestamp: DateTime,
20
- time: Time,
21
- json: Hash,
22
- jsonb: Hash
23
- }.freeze
24
-
25
- # Mapping for format constraints based on column type
26
- FORMAT_MAP = {
27
- date: 'date',
28
- datetime: 'date-time',
29
- timestamp: 'date-time',
30
- time: 'time'
31
- }.freeze
32
-
33
- attr_reader :model
34
-
35
- # Initialize the builder with an ActiveRecord model
36
- #
37
- # @param model [Class] An ActiveRecord model class
38
- # @raise [ArgumentError] If the provided class is not an ActiveRecord model
39
- def initialize(model)
40
- raise ArgumentError, 'Class must be an ActiveRecord model' unless model.ancestors.include?(ActiveRecord::Base)
41
-
42
- @model = model
43
- end
44
-
45
- # Build a SchemaDefinition object from the ActiveRecord model
46
- #
47
- # @return [EasyTalk::SchemaDefinition] A schema definition built from the database structure
48
- def build_schema_definition
49
- schema_def = SchemaDefinition.new(model.name)
50
-
51
- # Apply basic schema metadata
52
- apply_schema_metadata(schema_def)
53
-
54
- # Add all database columns as properties
55
- add_column_properties(schema_def)
56
-
57
- # Add model associations as properties
58
- add_association_properties(schema_def) unless EasyTalk.configuration.exclude_associations
59
-
60
- # Add virtual properties defined in schema_enhancements
61
- add_virtual_properties(schema_def)
62
-
63
- schema_def
64
- end
65
-
66
- private
67
-
68
- # Set top-level schema metadata like title, description, and additionalProperties
69
- #
70
- # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
71
- def apply_schema_metadata(schema_def)
72
- # Set title (from enhancements or derive from model name)
73
- title = schema_enhancements['title'] || model.name.demodulize.humanize
74
- schema_def.title(title)
75
-
76
- # Set description if provided
77
- if (description = schema_enhancements['description'])
78
- schema_def.description(description)
79
- end
80
-
81
- # Set additionalProperties (from enhancements or configuration default)
82
- additional_props = if schema_enhancements.key?('additionalProperties')
83
- schema_enhancements['additionalProperties']
84
- else
85
- EasyTalk.configuration.default_additional_properties
86
- end
87
- schema_def.additional_properties(additional_props)
88
- end
89
-
90
- # Add properties based on database columns
91
- #
92
- # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
93
- def add_column_properties(schema_def)
94
- filtered_columns.each do |column|
95
- # Get column enhancement info if it exists
96
- column_enhancements = schema_enhancements.dig('properties', column.name.to_s) || {}
97
-
98
- # Map the database type to Ruby type
99
- ruby_type = COLUMN_TYPE_MAP.fetch(column.type, String)
100
-
101
- # If the column is nullable, wrap the type in a Union with NilClass
102
- ruby_type = T::Types::Union.new([ruby_type, NilClass]) if column.null
103
-
104
- # Build constraints hash for this column
105
- constraints = build_column_constraints(column, column_enhancements)
106
-
107
- # Add the property to schema definition
108
- schema_def.property(column.name.to_sym, ruby_type, constraints)
109
- end
110
- end
111
-
112
- # Build constraints hash for a database column
113
- #
114
- # @param column [ActiveRecord::ConnectionAdapters::Column] The database column
115
- # @param enhancements [Hash] Any schema enhancements for this column
116
- # @return [Hash] The constraints hash
117
- def build_column_constraints(column, enhancements)
118
- constraints = {
119
- optional: enhancements['optional'],
120
- description: enhancements['description'],
121
- title: enhancements['title']
122
- }
123
-
124
- # Add format constraint for date/time columns
125
- if (format = FORMAT_MAP[column.type])
126
- constraints[:format] = format
127
- end
128
-
129
- # Add max_length for string columns with limits
130
- constraints[:max_length] = column.limit if column.type == :string && column.limit
131
-
132
- # Add precision/scale for numeric columns
133
- if column.type == :decimal && column.precision
134
- constraints[:precision] = column.precision
135
- constraints[:scale] = column.scale if column.scale
136
- end
137
-
138
- # Add default value if present and not 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
144
-
145
- # Remove nil values
146
- constraints.compact
147
- end
148
-
149
- # Add properties based on ActiveRecord associations
150
- #
151
- # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
152
- def add_association_properties(schema_def)
153
- model.reflect_on_all_associations.each do |association|
154
- # Skip if we can't determine the class or it's in the association exclusion list
155
- next if association_excluded?(association)
156
-
157
- # Get association enhancement info if it exists
158
- assoc_enhancements = schema_enhancements.dig('properties', association.name.to_s) || {}
159
-
160
- case association.macro
161
- when :belongs_to, :has_one
162
- schema_def.property(
163
- association.name,
164
- association.klass,
165
- { optional: assoc_enhancements['optional'], description: assoc_enhancements['description'] }.compact
166
- )
167
- when :has_many, :has_and_belongs_to_many
168
- schema_def.property(
169
- association.name,
170
- T::Array[association.klass],
171
- { optional: assoc_enhancements['optional'], description: assoc_enhancements['description'] }.compact
172
- )
173
- end
174
- end
175
- end
176
-
177
- # Add virtual properties defined in schema_enhancements
178
- #
179
- # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
180
- def add_virtual_properties(schema_def)
181
- return unless schema_enhancements['properties']
182
-
183
- schema_enhancements['properties'].each do |name, options|
184
- next unless options['virtual']
185
-
186
- # Map string type name to Ruby class
187
- ruby_type = map_type_string_to_ruby_class(options['type'] || 'string')
188
-
189
- # Build constraints for virtual property
190
- constraints = {
191
- description: options['description'],
192
- title: options['title'],
193
- optional: options['optional'],
194
- format: options['format'],
195
- default: options['default'],
196
- min_length: options['minLength'],
197
- max_length: options['maxLength'],
198
- enum: options['enum']
199
- }.compact
200
-
201
- # Add the virtual property
202
- schema_def.property(name.to_sym, ruby_type, constraints)
203
- end
204
- end
205
-
206
- # Map a type string to a Ruby class
207
- #
208
- # @param type_str [String] The type string (e.g., 'string', 'integer')
209
- # @return [Class] The corresponding Ruby class
210
- def map_type_string_to_ruby_class(type_str)
211
- case type_str.to_s.downcase
212
- when 'string' then String
213
- when 'integer' then Integer
214
- when 'number' then Float
215
- when 'boolean' then T::Boolean
216
- when 'object' then Hash
217
- when 'array' then Array
218
- when 'date' then Date
219
- when 'datetime' then DateTime
220
- when 'time' then Time
221
- else String # Default fallback
222
- end
223
- end
224
-
225
- # Get all columns that should be included in the schema
226
- #
227
- # @return [Array<ActiveRecord::ConnectionAdapters::Column>] Filtered columns
228
- def filtered_columns
229
- model.columns.reject do |column|
230
- config = EasyTalk.configuration
231
- excluded_columns.include?(column.name.to_sym) ||
232
- (config.exclude_primary_key && column.name == model.primary_key) ||
233
- (config.exclude_timestamps && timestamp_column?(column.name)) ||
234
- (config.exclude_foreign_keys && foreign_key_column?(column.name))
235
- end
236
- end
237
-
238
- # Check if a column is a timestamp column
239
- #
240
- # @param column_name [String] The column name
241
- # @return [Boolean] True if the column is a timestamp column
242
- def timestamp_column?(column_name)
243
- %w[created_at updated_at].include?(column_name)
244
- end
245
-
246
- # Check if a column is a foreign key column
247
- #
248
- # @param column_name [String] The column name
249
- # @return [Boolean] True if the column is a foreign key column
250
- def foreign_key_column?(column_name)
251
- column_name.end_with?('_id')
252
- end
253
-
254
- # Check if an association should be excluded
255
- #
256
- # @param association [ActiveRecord::Reflection::AssociationReflection] The association
257
- # @return [Boolean] True if the association should be excluded
258
- def association_excluded?(association)
259
- !association.klass ||
260
- excluded_associations.include?(association.name.to_sym) ||
261
- association.options[:polymorphic] # Skip polymorphic associations (complex to model)
262
- end
263
-
264
- # Get schema enhancements
265
- #
266
- # @return [Hash] Schema enhancements
267
- def schema_enhancements
268
- @schema_enhancements ||= if model.respond_to?(:schema_enhancements)
269
- model.schema_enhancements.deep_transform_keys(&:to_s)
270
- else
271
- {}
272
- end
273
- end
274
-
275
- # Get all excluded columns
276
- #
277
- # @return [Array<Symbol>] Excluded column names
278
- def excluded_columns
279
- @excluded_columns ||= begin
280
- config = EasyTalk.configuration
281
- global_exclusions = config.excluded_columns || []
282
- model_exclusions = schema_enhancements['ignore'] || []
283
-
284
- # Combine and convert to symbols for consistent comparison
285
- (global_exclusions + model_exclusions).map(&:to_sym)
286
- end
287
- end
288
-
289
- # Get all excluded associations
290
- #
291
- # @return [Array<Symbol>] Excluded association names
292
- def excluded_associations
293
- @excluded_associations ||= begin
294
- model_exclusions = schema_enhancements['ignore_associations'] || []
295
- model_exclusions.map(&:to_sym)
296
- end
297
- end
298
- end
299
- end