model-to-schema 0.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +12 -0
- data/esquema.gemspec +38 -0
- data/lib/esquema/builder.rb +155 -0
- data/lib/esquema/configuration.rb +34 -0
- data/lib/esquema/keyword_validator.rb +98 -0
- data/lib/esquema/model.rb +31 -0
- data/lib/esquema/property.rb +238 -0
- data/lib/esquema/schema_enhancer.rb +90 -0
- data/lib/esquema/type_caster.rb +53 -0
- data/lib/esquema/version.rb +5 -0
- data/lib/esquema/virtual_column.rb +46 -0
- data/lib/esquema.rb +14 -0
- data/lib/generators/esquema/install/install_generator.rb +16 -0
- data/lib/generators/esquema/install/templates/esquema_initializer.rb +22 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activemodel.rbi +89 -0
- data/sorbet/rbi/annotations/activerecord.rbi +92 -0
- data/sorbet/rbi/annotations/activesupport.rbi +421 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activemodel@7.1.3.rbi +8 -0
- data/sorbet/rbi/gems/activerecord@7.1.3.rbi +8 -0
- data/sorbet/rbi/gems/activesupport@7.1.3.rbi +192 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +8 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +8 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1130 -0
- data/sorbet/rbi/gems/drb@2.2.0.rbi +1272 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
- data/sorbet/rbi/gems/json@2.7.1.rbi +1553 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/minitest@5.22.2.rbi +8 -0
- data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +8 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
- data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +157 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
- data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rspec-core@3.13.0.rbi +10978 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.0.rbi +8153 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.0.rbi +5340 -0
- data/sorbet/rbi/gems/rspec-support@3.13.0.rbi +1629 -0
- data/sorbet/rbi/gems/rspec@3.13.0.rbi +82 -0
- data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7006 -0
- data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57383 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/sqlite3@1.7.2.rbi +1691 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23133 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
- data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
- data/sorbet/rbi/gems/timeout@0.4.1.rbi +142 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
- data/sorbet/rbi/todo.rbi +20 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +176 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "type_caster"
|
|
4
|
+
module Esquema
|
|
5
|
+
# Represents a property in the Esquema schema.
|
|
6
|
+
class Property # rubocop:disable Metrics/ClassLength
|
|
7
|
+
# Mapping of database types to JSON types.
|
|
8
|
+
DB_TO_JSON_TYPE_MAPPINGS = {
|
|
9
|
+
date: "date",
|
|
10
|
+
datetime: "date-time",
|
|
11
|
+
time: "time",
|
|
12
|
+
string: "string",
|
|
13
|
+
text: "string",
|
|
14
|
+
integer: "integer",
|
|
15
|
+
float: "number",
|
|
16
|
+
number: "number",
|
|
17
|
+
decimal: "number",
|
|
18
|
+
boolean: "boolean",
|
|
19
|
+
array: "array",
|
|
20
|
+
object: "object"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
NUMERIC_CONSTRAINT_KEYWORDS = %i[minimum maximum exclusiveMinimum exclusiveMaximum multipleOf].freeze
|
|
24
|
+
STRING_CONSTRAINT_KEYWORDS = %i[maxLength minLength pattern format].freeze
|
|
25
|
+
ARRAY_CONSTRAINT_KEYWORDS = %i[maxItems minItems uniqueItems items].freeze
|
|
26
|
+
OBJECT_CONSTRAINT_KEYWORDS = %i[maxProperties minProperties properties additionalProperties dependencies].freeze
|
|
27
|
+
LOGICAL_KEYWORDS = %i[allOf anyOf oneOf not].freeze
|
|
28
|
+
GENERIC_KEYWORDS = %i[type default title description enum const].freeze
|
|
29
|
+
|
|
30
|
+
KEYWORDS = (
|
|
31
|
+
NUMERIC_CONSTRAINT_KEYWORDS +
|
|
32
|
+
STRING_CONSTRAINT_KEYWORDS +
|
|
33
|
+
ARRAY_CONSTRAINT_KEYWORDS +
|
|
34
|
+
OBJECT_CONSTRAINT_KEYWORDS +
|
|
35
|
+
LOGICAL_KEYWORDS +
|
|
36
|
+
GENERIC_KEYWORDS
|
|
37
|
+
).freeze
|
|
38
|
+
|
|
39
|
+
FORMAT_OPTIONS = %i[date-time date time email hostname ipv4 ipv6 uri uuid uri-reference uri-template].freeze
|
|
40
|
+
|
|
41
|
+
attr_reader :object, :options
|
|
42
|
+
|
|
43
|
+
# Initializes a new Property instance.
|
|
44
|
+
#
|
|
45
|
+
# @param object [Object] The object to build the property for.
|
|
46
|
+
# An object can be any of the following instance types:
|
|
47
|
+
# An ActiveRecord column. Example: ActiveRecord::ConnectionAdapters::SQLite3::Column
|
|
48
|
+
# An ActiveRecord association reflection. Example: ActiveRecord::Reflection::BelongsToReflection
|
|
49
|
+
# An Esquema virtual column. Example: Esquema::VirtualColumn
|
|
50
|
+
# @param options [Hash] Additional options for the property.
|
|
51
|
+
# @raise [ArgumentError] If the property does not have a name.
|
|
52
|
+
def initialize(object, options = {})
|
|
53
|
+
raise ArgumentError, "property must have a name" unless object.respond_to?(:name)
|
|
54
|
+
|
|
55
|
+
@object = object
|
|
56
|
+
@options = options
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Converts the Property instance to a JSON representation.
|
|
60
|
+
#
|
|
61
|
+
# @return [Hash] The JSON representation of the Property.
|
|
62
|
+
def as_json
|
|
63
|
+
KEYWORDS.each_with_object({}) do |property, hash|
|
|
64
|
+
value = send("build_#{property.downcase}")
|
|
65
|
+
next if value.nil? || (value.is_a?(String) && value.empty?)
|
|
66
|
+
|
|
67
|
+
hash[property] = value
|
|
68
|
+
end.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Builds the title attribute for the Property.
|
|
72
|
+
#
|
|
73
|
+
# @return [String] The title attribute.
|
|
74
|
+
def build_title
|
|
75
|
+
options[:title].presence || object.name.to_s.humanize
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Builds the default attribute for the Property.
|
|
79
|
+
#
|
|
80
|
+
# @return [Object, nil] The default attribute.
|
|
81
|
+
def build_default
|
|
82
|
+
return unless object.respond_to?(:default)
|
|
83
|
+
|
|
84
|
+
default_value = object.default || options[:default].presence
|
|
85
|
+
|
|
86
|
+
@default = TypeCaster.cast(object.type, default_value) unless default_value.nil?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Builds the type attribute for the Property.
|
|
90
|
+
#
|
|
91
|
+
# @return [String, nil] The type attribute.
|
|
92
|
+
def build_type
|
|
93
|
+
return DB_TO_JSON_TYPE_MAPPINGS[:array] if object.try(:collection?)
|
|
94
|
+
|
|
95
|
+
return unless object.respond_to?(:type)
|
|
96
|
+
|
|
97
|
+
@type = DB_TO_JSON_TYPE_MAPPINGS[object.type]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Builds the description attribute for the Property.
|
|
101
|
+
#
|
|
102
|
+
# @return [String, nil] The description attribute.
|
|
103
|
+
def build_description
|
|
104
|
+
options[:description]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Builds the items attribute for the Property.
|
|
108
|
+
#
|
|
109
|
+
# @return [Hash, nil] The items attribute.
|
|
110
|
+
def build_items
|
|
111
|
+
return unless object.try(:collection?)
|
|
112
|
+
|
|
113
|
+
case object.type
|
|
114
|
+
when :array
|
|
115
|
+
{ type: DB_TO_JSON_TYPE_MAPPINGS[object.item_type] }
|
|
116
|
+
else
|
|
117
|
+
Builder.new(object.klass).build_schema
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Builds the enum attribute for the Property.
|
|
122
|
+
#
|
|
123
|
+
# @return [Array, nil] The enum attribute.
|
|
124
|
+
def build_enum
|
|
125
|
+
options[:enum]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_minimum
|
|
129
|
+
options[:minimum]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_maximum
|
|
133
|
+
options[:maximum]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def build_exclusiveminimum
|
|
137
|
+
options[:exclusiveMinimum]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def build_exclusivemaximum
|
|
141
|
+
options[:exclusiveMaximum]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def build_multipleof
|
|
145
|
+
options[:multipleof]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_maxlength
|
|
149
|
+
options[:maxLength]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_minlength
|
|
153
|
+
options[:minLength]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def build_pattern
|
|
157
|
+
options[:pattern]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def build_format
|
|
161
|
+
options[:format]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def build_maxitems # rubocop:disable Metrics/AbcSize
|
|
165
|
+
raise ArgumentError, "maxItems must be an integer" if options[:maxItems] && !options[:maxItems].is_a?(Integer)
|
|
166
|
+
|
|
167
|
+
if options[:maxItems]&.negative?
|
|
168
|
+
raise ArgumentError,
|
|
169
|
+
"maxItems must be a non-negative integer"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if options[:maxItems] && options[:type] != :array
|
|
173
|
+
raise ArgumentError, "maxItems must be use for array type properties only."
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
options[:maxItems]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def build_minitems # rubocop:disable Metrics/AbcSize
|
|
180
|
+
raise ArgumentError, "minItems must be an integer" if options[:minItems] && !options[:minItems].is_a?(Integer)
|
|
181
|
+
|
|
182
|
+
if options[:minItems]&.negative?
|
|
183
|
+
raise ArgumentError,
|
|
184
|
+
"minItems must be a non-negative integer"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if options[:minItems] && options[:type] != :array
|
|
188
|
+
raise ArgumentError, "minItems must be use for array type properties only."
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
options[:minItems]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def build_uniqueitems
|
|
195
|
+
options[:uniqueItems]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def build_properties
|
|
199
|
+
options[:properties]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def build_maxproperties
|
|
203
|
+
options[:maxProperties]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def build_minproperties
|
|
207
|
+
options[:minProperties]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def build_additionalproperties
|
|
211
|
+
options[:additionalProperties]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def build_dependencies
|
|
215
|
+
options[:dependencies]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def build_allof
|
|
219
|
+
options[:allOf]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def build_anyof
|
|
223
|
+
options[:anyOf]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def build_oneof
|
|
227
|
+
options[:oneOf]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def build_not
|
|
231
|
+
options[:not]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def build_const
|
|
235
|
+
options[:const]
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "bigdecimal"
|
|
5
|
+
require_relative "keyword_validator"
|
|
6
|
+
|
|
7
|
+
module Esquema
|
|
8
|
+
# The SchemaEnhancer class is responsible for enhancing the schema of a model.
|
|
9
|
+
class SchemaEnhancer
|
|
10
|
+
attr_reader :model
|
|
11
|
+
|
|
12
|
+
def initialize(model, schema_enhancements)
|
|
13
|
+
@schema_enhancements = schema_enhancements
|
|
14
|
+
@model = model
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Sets the description for the model.
|
|
18
|
+
#
|
|
19
|
+
# @param description [String] The description of the model.
|
|
20
|
+
def model_description(description)
|
|
21
|
+
@schema_enhancements[:model_description] = description
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Sets the title for the model.
|
|
25
|
+
#
|
|
26
|
+
# @param title [String] The title of the model.
|
|
27
|
+
def model_title(title)
|
|
28
|
+
@schema_enhancements[:model_title] = title
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Adds a property to the schema.
|
|
32
|
+
#
|
|
33
|
+
# @param name [Symbol] The name of the property.
|
|
34
|
+
# @param options [Hash] Additional options for the property.
|
|
35
|
+
def property(name, options = {})
|
|
36
|
+
validate_property_as_attribute_for(name, options)
|
|
37
|
+
|
|
38
|
+
type = resolve_type(name, options)
|
|
39
|
+
|
|
40
|
+
KeywordValidator.validate!(name, type, options)
|
|
41
|
+
|
|
42
|
+
@schema_enhancements[:properties] ||= {}
|
|
43
|
+
@schema_enhancements[:properties][name] = options
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Adds a virtual property to the schema.
|
|
47
|
+
#
|
|
48
|
+
# @param name [Symbol] The name of the virtual property.
|
|
49
|
+
# @param options [Hash] Additional options for the virtual property.
|
|
50
|
+
def virtual_property(name, options = {})
|
|
51
|
+
options[:virtual] = true
|
|
52
|
+
property(name, options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Resolves the type of a property.
|
|
58
|
+
#
|
|
59
|
+
# @param name [Symbol] The name of the property.
|
|
60
|
+
# @param options [Hash] Additional options for the property.
|
|
61
|
+
# @return [Symbol] The resolved type of the property.
|
|
62
|
+
def resolve_type(name, options = {})
|
|
63
|
+
if options[:virtual] == true
|
|
64
|
+
options[:type]
|
|
65
|
+
else
|
|
66
|
+
model.type_for_attribute(name).type
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Retrieves the valid properties for the model.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<Symbol>] The valid properties for the model.
|
|
73
|
+
def valid_properties
|
|
74
|
+
@valid_properties ||= begin
|
|
75
|
+
properties = model.column_names + model.reflect_on_all_associations.map(&:name)
|
|
76
|
+
properties.map(&:to_sym)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Validates that a property is a valid attribute for the model.
|
|
81
|
+
#
|
|
82
|
+
# @param prop_name [Symbol] The name of the property.
|
|
83
|
+
# @param options [Hash] Additional options for the property.
|
|
84
|
+
# @raise [ArgumentError] If the property is not a valid attribute for the model.
|
|
85
|
+
def validate_property_as_attribute_for(prop_name, options = {})
|
|
86
|
+
return if options[:virtual] == true
|
|
87
|
+
raise ArgumentError, "`#{prop_name}` is not a model attribute." unless valid_properties.include?(prop_name.to_sym)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Esquema
|
|
4
|
+
module TypeCaster # rubocop:disable Style/Documentation
|
|
5
|
+
def self.cast(type, value) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
6
|
+
case type
|
|
7
|
+
when :string, :text
|
|
8
|
+
value.to_s
|
|
9
|
+
when :integer
|
|
10
|
+
begin
|
|
11
|
+
Integer(value)
|
|
12
|
+
rescue StandardError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
when :float
|
|
16
|
+
begin
|
|
17
|
+
Float(value)
|
|
18
|
+
rescue StandardError
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
when :number
|
|
22
|
+
if value.to_s.include?(".")
|
|
23
|
+
begin
|
|
24
|
+
Float(value)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
begin
|
|
30
|
+
Integer(value)
|
|
31
|
+
rescue StandardError
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
when :boolean
|
|
36
|
+
case value
|
|
37
|
+
when true, "true", "1", 1
|
|
38
|
+
true
|
|
39
|
+
when false, "false", "0", 0
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
when :array
|
|
43
|
+
Array(value)
|
|
44
|
+
when :object
|
|
45
|
+
value.is_a?(Hash) ? value : nil # or convert as desired
|
|
46
|
+
when :null
|
|
47
|
+
nil if value.nil?
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "Unsupported type: #{type}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Esquema
|
|
4
|
+
class VirtualColumn # rubocop:disable Style/Documentation
|
|
5
|
+
def initialize(property_name, options = {})
|
|
6
|
+
@property_name = property_name
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
@property_name.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def class_name
|
|
15
|
+
@property_name.to_s.classify
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def type
|
|
19
|
+
@options[:type]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def item_type
|
|
23
|
+
@options.dig(:items, :type)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def default
|
|
27
|
+
@options[:default]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def title
|
|
31
|
+
@options[:title]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def description
|
|
35
|
+
@options[:description]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def columns
|
|
39
|
+
[]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def collection?
|
|
43
|
+
@options[:type] == :array
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/esquema.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "esquema/version"
|
|
4
|
+
require_relative "esquema/configuration"
|
|
5
|
+
require_relative "esquema/model"
|
|
6
|
+
require_relative "esquema/schema_enhancer"
|
|
7
|
+
require_relative "esquema/keyword_validator"
|
|
8
|
+
require_relative "esquema/builder"
|
|
9
|
+
require_relative "esquema/property"
|
|
10
|
+
require_relative "esquema/type_caster"
|
|
11
|
+
require_relative "esquema/virtual_column"
|
|
12
|
+
|
|
13
|
+
module Esquema # rubocop:disable Style/Documentation
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Esquema
|
|
6
|
+
module Generators
|
|
7
|
+
# This generator is responsible for installing the Esquema gem.
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
def copy_initializer_file
|
|
12
|
+
template "esquema_initializer.rb", "config/initializers/esquema.rb"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "esquema"
|
|
4
|
+
|
|
5
|
+
Esquema.configure do |config|
|
|
6
|
+
# Exclude Associations:
|
|
7
|
+
# Exclude associated models from the json-schema output.
|
|
8
|
+
# By default, all associated models are included.
|
|
9
|
+
config.exclude_associations = false
|
|
10
|
+
|
|
11
|
+
# Exclude Foreign Keys:
|
|
12
|
+
# Specify whether or not to exclude foreign keys from the json-schema output.
|
|
13
|
+
# By default, foreign keys are excluded.
|
|
14
|
+
# foreign keys are loosely defined as columns that end with "_id".
|
|
15
|
+
config.exclude_foreign_keys = true
|
|
16
|
+
|
|
17
|
+
# Excluded Columns:
|
|
18
|
+
# Specify the columns that you want to exclude from the json-schema output.
|
|
19
|
+
# These are columns common to all models, such as id, created_at, updated_at, etc.
|
|
20
|
+
# It's okay if not all models have these columns, they will be ignored.
|
|
21
|
+
config.excluded_columns = %i[id created_at updated_at deleted_at]
|
|
22
|
+
end
|
data/sorbet/config
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
**/*.rbi linguist-vendored=true
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
# This file was pulled from a central RBI files repository.
|
|
5
|
+
# Please run `bin/tapioca annotations` to update it.
|
|
6
|
+
|
|
7
|
+
class ActiveModel::Errors
|
|
8
|
+
Elem = type_member { { fixed: ActiveModel::Error } }
|
|
9
|
+
|
|
10
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
|
|
11
|
+
def [](attribute); end
|
|
12
|
+
|
|
13
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(ActiveModel::Error) }
|
|
14
|
+
def add(attribute, type = :invalid, **options); end
|
|
15
|
+
|
|
16
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T::Boolean) }
|
|
17
|
+
def added?(attribute, type = :invalid, options = {}); end
|
|
18
|
+
|
|
19
|
+
sig { params(options: T.untyped).returns(T::Hash[T.untyped, T.untyped]) }
|
|
20
|
+
def as_json(options = nil); end
|
|
21
|
+
|
|
22
|
+
sig { returns(T::Array[Symbol]) }
|
|
23
|
+
def attribute_names; end
|
|
24
|
+
|
|
25
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T.nilable(T::Array[String])) }
|
|
26
|
+
def delete(attribute, type = nil, **options); end
|
|
27
|
+
|
|
28
|
+
sig { returns(T::Hash[Symbol, T::Array[T::Hash[Symbol, T.untyped]]]) }
|
|
29
|
+
def details; end
|
|
30
|
+
|
|
31
|
+
sig { returns(T::Array[Elem]) }
|
|
32
|
+
def errors; end
|
|
33
|
+
|
|
34
|
+
sig { params(attribute: T.any(Symbol, String), message: String).returns(String) }
|
|
35
|
+
def full_message(attribute, message); end
|
|
36
|
+
|
|
37
|
+
sig { returns(T::Array[String]) }
|
|
38
|
+
def full_messages; end
|
|
39
|
+
|
|
40
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
|
|
41
|
+
def full_messages_for(attribute); end
|
|
42
|
+
|
|
43
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(String) }
|
|
44
|
+
def generate_message(attribute, type = :invalid, options = {}); end
|
|
45
|
+
|
|
46
|
+
sig { returns(T::Hash[Symbol, T::Array[ActiveModel::Error]]) }
|
|
47
|
+
def group_by_attribute; end
|
|
48
|
+
|
|
49
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
|
|
50
|
+
def has_key?(attribute); end
|
|
51
|
+
|
|
52
|
+
sig { params(error: ActiveModel::Error, override_options: T.untyped).returns(T::Array[ActiveModel::Error]) }
|
|
53
|
+
def import(error, override_options = {}); end
|
|
54
|
+
|
|
55
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
|
|
56
|
+
def include?(attribute); end
|
|
57
|
+
|
|
58
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
|
|
59
|
+
def key?(attribute); end
|
|
60
|
+
|
|
61
|
+
sig { params(other: T.untyped).returns(T::Array[ActiveModel::Error]) }
|
|
62
|
+
def merge!(other); end
|
|
63
|
+
|
|
64
|
+
sig { returns(T::Hash[Symbol, T::Array[String]]) }
|
|
65
|
+
def messages; end
|
|
66
|
+
|
|
67
|
+
sig { params(attribute: T.any(Symbol, String)).returns(T::Array[String]) }
|
|
68
|
+
def messages_for(attribute); end
|
|
69
|
+
|
|
70
|
+
sig { returns(T::Array[Elem]) }
|
|
71
|
+
def objects; end
|
|
72
|
+
|
|
73
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped).returns(T::Boolean) }
|
|
74
|
+
def of_kind?(attribute, type = :invalid); end
|
|
75
|
+
|
|
76
|
+
sig { returns(T::Array[String]) }
|
|
77
|
+
def to_a; end
|
|
78
|
+
|
|
79
|
+
sig { params(full_messages: T.untyped).returns(T::Hash[Symbol, T::Array[String]]) }
|
|
80
|
+
def to_hash(full_messages = false); end
|
|
81
|
+
|
|
82
|
+
sig { params(attribute: T.any(Symbol, String), type: T.untyped, options: T.untyped).returns(T::Array[ActiveModel::Error]) }
|
|
83
|
+
def where(attribute, type = nil, **options); end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module ActiveModel::Validations
|
|
87
|
+
sig { returns(ActiveModel::Errors) }
|
|
88
|
+
def errors; end
|
|
89
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
# This file was pulled from a central RBI files repository.
|
|
5
|
+
# Please run `bin/tapioca annotations` to update it.
|
|
6
|
+
|
|
7
|
+
class ActiveRecord::Schema
|
|
8
|
+
sig { params(info: T::Hash[T.untyped, T.untyped], blk: T.proc.bind(ActiveRecord::Schema).void).void }
|
|
9
|
+
def self.define(info = nil, &blk); end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ActiveRecord::Migration
|
|
13
|
+
# @shim: Methods on migration are delegated to `SchemaStatements` using `method_missing`
|
|
14
|
+
include ActiveRecord::ConnectionAdapters::SchemaStatements
|
|
15
|
+
|
|
16
|
+
# @shim: Methods on migration are delegated to `DatabaseStatements` using `method_missing`
|
|
17
|
+
include ActiveRecord::ConnectionAdapters::DatabaseStatements
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class ActiveRecord::Base
|
|
21
|
+
sig { returns(FalseClass) }
|
|
22
|
+
def blank?; end
|
|
23
|
+
|
|
24
|
+
# @shim: since `present?` is always true, `presence` always returns `self`
|
|
25
|
+
sig { returns(T.self_type) }
|
|
26
|
+
def presence; end
|
|
27
|
+
|
|
28
|
+
sig { returns(TrueClass) }
|
|
29
|
+
def present?; end
|
|
30
|
+
|
|
31
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
32
|
+
def self.after_initialize(*args, **options, &block); end
|
|
33
|
+
|
|
34
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
35
|
+
def self.after_find(*args, **options, &block); end
|
|
36
|
+
|
|
37
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
38
|
+
def self.after_touch(*args, **options, &block); end
|
|
39
|
+
|
|
40
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
41
|
+
def self.before_validation(*args, **options, &block); end
|
|
42
|
+
|
|
43
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
44
|
+
def self.after_validation(*args, **options, &block); end
|
|
45
|
+
|
|
46
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
47
|
+
def self.before_save(*args, **options, &block); end
|
|
48
|
+
|
|
49
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
50
|
+
def self.around_save(*args, **options, &block); end
|
|
51
|
+
|
|
52
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
53
|
+
def self.after_save(*args, **options, &block); end
|
|
54
|
+
|
|
55
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
56
|
+
def self.before_create(*args, **options, &block); end
|
|
57
|
+
|
|
58
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
59
|
+
def self.around_create(*args, **options, &block); end
|
|
60
|
+
|
|
61
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
62
|
+
def self.after_create(*args, **options, &block); end
|
|
63
|
+
|
|
64
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
65
|
+
def self.before_update(*args, **options, &block); end
|
|
66
|
+
|
|
67
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
68
|
+
def self.around_update(*args, **options, &block); end
|
|
69
|
+
|
|
70
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
71
|
+
def self.after_update(*args, **options, &block); end
|
|
72
|
+
|
|
73
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
74
|
+
def self.before_destroy(*args, **options, &block); end
|
|
75
|
+
|
|
76
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
77
|
+
def self.around_destroy(*args, **options, &block); end
|
|
78
|
+
|
|
79
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
80
|
+
def self.after_destroy(*args, **options, &block); end
|
|
81
|
+
|
|
82
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
83
|
+
def self.after_commit(*args, **options, &block); end
|
|
84
|
+
|
|
85
|
+
sig { params(args: T.untyped, options: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).params(record: T.attached_class).void)).void }
|
|
86
|
+
def self.after_rollback(*args, **options, &block); end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class ActiveRecord::Relation
|
|
90
|
+
sig { returns(T::Boolean) }
|
|
91
|
+
def blank?; end
|
|
92
|
+
end
|