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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +663 -165
- data/easy_talk.gemspec +5 -4
- data/lib/easy_talk/builders/integer_builder.rb +1 -0
- data/lib/easy_talk/builders/object_builder.rb +95 -1
- data/lib/easy_talk/builders/string_builder.rb +9 -0
- data/lib/easy_talk/builders/typed_array_builder.rb +5 -2
- data/lib/easy_talk/configuration.rb +22 -9
- data/lib/easy_talk/keywords.rb +2 -0
- data/lib/easy_talk/model.rb +66 -49
- data/lib/easy_talk/property.rb +69 -3
- data/lib/easy_talk/validation_builder.rb +37 -49
- data/lib/easy_talk/version.rb +1 -1
- metadata +36 -10
- data/lib/easy_talk/active_record_schema_builder.rb +0 -299
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:
|
|
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:
|
|
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.
|
|
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
|