esquema 0.1.0 → 0.1.2

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +9 -1
  4. data/README.md +11 -9
  5. data/lib/esquema/builder.rb +73 -28
  6. data/lib/esquema/configuration.rb +2 -1
  7. data/lib/esquema/keyword_validator.rb +98 -0
  8. data/lib/esquema/model.rb +4 -0
  9. data/lib/esquema/property.rb +185 -26
  10. data/lib/esquema/schema_enhancer.rb +55 -30
  11. data/lib/esquema/type_caster.rb +16 -4
  12. data/lib/esquema/version.rb +1 -1
  13. data/lib/esquema/virtual_column.rb +46 -0
  14. data/lib/esquema.rb +7 -1
  15. data/lib/generators/esquema/install/install_generator.rb +1 -0
  16. data/sorbet/config +4 -0
  17. data/sorbet/rbi/annotations/.gitattributes +1 -0
  18. data/sorbet/rbi/annotations/activemodel.rbi +89 -0
  19. data/sorbet/rbi/annotations/activerecord.rbi +92 -0
  20. data/sorbet/rbi/annotations/activesupport.rbi +421 -0
  21. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  22. data/sorbet/rbi/gems/.gitattributes +1 -0
  23. data/sorbet/rbi/gems/activemodel@7.1.3.rbi +8 -0
  24. data/sorbet/rbi/gems/activerecord@7.1.3.rbi +8 -0
  25. data/sorbet/rbi/gems/activesupport@7.1.3.rbi +192 -0
  26. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  27. data/sorbet/rbi/gems/base64@0.2.0.rbi +8 -0
  28. data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +8 -0
  29. data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
  30. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  31. data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
  32. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
  33. data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1130 -0
  34. data/sorbet/rbi/gems/drb@2.2.0.rbi +1272 -0
  35. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  36. data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
  37. data/sorbet/rbi/gems/json@2.7.1.rbi +1553 -0
  38. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  39. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  40. data/sorbet/rbi/gems/minitest@5.22.2.rbi +8 -0
  41. data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +8 -0
  42. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  43. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  44. data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
  45. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  46. data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
  47. data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
  48. data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
  49. data/sorbet/rbi/gems/racc@1.7.3.rbi +157 -0
  50. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  51. data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
  52. data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
  53. data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
  54. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  55. data/sorbet/rbi/gems/rspec-core@3.13.0.rbi +10978 -0
  56. data/sorbet/rbi/gems/rspec-expectations@3.13.0.rbi +8153 -0
  57. data/sorbet/rbi/gems/rspec-mocks@3.13.0.rbi +5340 -0
  58. data/sorbet/rbi/gems/rspec-support@3.13.0.rbi +1629 -0
  59. data/sorbet/rbi/gems/rspec@3.13.0.rbi +82 -0
  60. data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7006 -0
  61. data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57383 -0
  62. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  63. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
  64. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  65. data/sorbet/rbi/gems/sqlite3@1.7.2.rbi +1691 -0
  66. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23133 -0
  67. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
  68. data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
  69. data/sorbet/rbi/gems/timeout@0.4.1.rbi +142 -0
  70. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
  71. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
  72. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  73. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  74. data/sorbet/rbi/todo.rbi +20 -0
  75. data/sorbet/tapioca/config.yml +13 -0
  76. data/sorbet/tapioca/require.rb +4 -0
  77. metadata +74 -12
  78. data/esquema.gemspec +0 -38
@@ -1,26 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "date"
2
4
  require "bigdecimal"
5
+ require_relative "keyword_validator"
3
6
 
4
7
  module Esquema
8
+ # The SchemaEnhancer class is responsible for enhancing the schema of a model.
5
9
  class SchemaEnhancer
6
- VALID_OPTIONS = %i[title description maxLength minLength pattern maxItems minItems uniqueItems
7
- maxProperties minProperties properties additionalProperties dependencies
8
- enum format multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
9
- const allOf anyOf oneOf not default].freeze
10
-
11
- TYPE_MAPPINGS = {
12
- date: Date,
13
- datetime: DateTime,
14
- time: Time,
15
- string: String,
16
- text: String,
17
- integer: Integer,
18
- float: Float,
19
- decimal: BigDecimal,
20
- boolean: [TrueClass, FalseClass],
21
- array: Array,
22
- object: Object
23
- }.freeze
24
10
  attr_reader :model
25
11
 
26
12
  def initialize(model, schema_enhancements)
@@ -28,38 +14,77 @@ module Esquema
28
14
  @model = model
29
15
  end
30
16
 
17
+ # Sets the description for the model.
18
+ #
19
+ # @param description [String] The description of the model.
31
20
  def model_description(description)
32
21
  @schema_enhancements[:model_description] = description
33
22
  end
34
23
 
24
+ # Sets the title for the model.
25
+ #
26
+ # @param title [String] The title of the model.
35
27
  def model_title(title)
36
28
  @schema_enhancements[:model_title] = title
37
29
  end
38
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.
39
35
  def property(name, options = {})
40
- db_type = model.type_for_attribute(name).type
41
- klass_type = Array(TYPE_MAPPINGS[db_type])
36
+ validate_property_as_attribute_for(name, options)
42
37
 
43
- validate_default_value(options[:default], klass_type, db_type)
44
- validate_enum_values(options[:enum], klass_type, db_type)
38
+ type = resolve_type(name, options)
39
+
40
+ KeywordValidator.validate!(name, type, options)
45
41
 
46
- options.assert_valid_keys(VALID_OPTIONS)
47
42
  @schema_enhancements[:properties] ||= {}
48
43
  @schema_enhancements[:properties][name] = options
49
44
  end
50
45
 
51
- private
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
52
54
 
53
- def validate_default_value(default_value, klass_type, db_type)
54
- return unless default_value.present? && !klass_type.include?(default_value.class)
55
+ private
55
56
 
56
- raise ArgumentError, "Default value must be of type #{db_type}"
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
57
68
  end
58
69
 
59
- def validate_enum_values(enum_values, klass_type, db_type)
60
- return unless enum_values.present? && !enum_values.all? { |value| klass_type.include?(value.class) }
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
61
79
 
62
- raise ArgumentError, "Enum values must be of type #{db_type}"
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)
63
88
  end
64
89
  end
65
90
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Esquema
4
- module TypeCaster
5
- def self.cast(type, value) # rubocop:disable Metrics/MethodLength
4
+ module TypeCaster # rubocop:disable Style/Documentation
5
+ def self.cast(type, value) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
6
6
  case type
7
7
  when :string, :text
8
8
  value.to_s
@@ -18,14 +18,26 @@ module Esquema
18
18
  rescue StandardError
19
19
  nil
20
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
21
35
  when :boolean
22
36
  case value
23
37
  when true, "true", "1", 1
24
38
  true
25
39
  when false, "false", "0", 0
26
40
  false
27
- else
28
- nil # or handle as desired
29
41
  end
30
42
  when :array
31
43
  Array(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Esquema
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  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 CHANGED
@@ -3,6 +3,12 @@
3
3
  require_relative "esquema/version"
4
4
  require_relative "esquema/configuration"
5
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"
6
12
 
7
- module Esquema
13
+ module Esquema # rubocop:disable Style/Documentation
8
14
  end
@@ -4,6 +4,7 @@ require "rails/generators"
4
4
 
5
5
  module Esquema
6
6
  module Generators
7
+ # This generator is responsible for installing the Esquema gem.
7
8
  class InstallGenerator < Rails::Generators::Base
8
9
  source_root File.expand_path("templates", __dir__)
9
10
 
data/sorbet/config ADDED
@@ -0,0 +1,4 @@
1
+ --dir
2
+ .
3
+ --ignore=tmp/
4
+ --ignore=vendor/
@@ -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