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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +20 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +13 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +111 -0
  8. data/Rakefile +12 -0
  9. data/esquema.gemspec +38 -0
  10. data/lib/esquema/builder.rb +155 -0
  11. data/lib/esquema/configuration.rb +34 -0
  12. data/lib/esquema/keyword_validator.rb +98 -0
  13. data/lib/esquema/model.rb +31 -0
  14. data/lib/esquema/property.rb +238 -0
  15. data/lib/esquema/schema_enhancer.rb +90 -0
  16. data/lib/esquema/type_caster.rb +53 -0
  17. data/lib/esquema/version.rb +5 -0
  18. data/lib/esquema/virtual_column.rb +46 -0
  19. data/lib/esquema.rb +14 -0
  20. data/lib/generators/esquema/install/install_generator.rb +16 -0
  21. data/lib/generators/esquema/install/templates/esquema_initializer.rb +22 -0
  22. data/sorbet/config +4 -0
  23. data/sorbet/rbi/annotations/.gitattributes +1 -0
  24. data/sorbet/rbi/annotations/activemodel.rbi +89 -0
  25. data/sorbet/rbi/annotations/activerecord.rbi +92 -0
  26. data/sorbet/rbi/annotations/activesupport.rbi +421 -0
  27. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  28. data/sorbet/rbi/gems/.gitattributes +1 -0
  29. data/sorbet/rbi/gems/activemodel@7.1.3.rbi +8 -0
  30. data/sorbet/rbi/gems/activerecord@7.1.3.rbi +8 -0
  31. data/sorbet/rbi/gems/activesupport@7.1.3.rbi +192 -0
  32. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  33. data/sorbet/rbi/gems/base64@0.2.0.rbi +8 -0
  34. data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +8 -0
  35. data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
  36. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  37. data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
  38. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
  39. data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1130 -0
  40. data/sorbet/rbi/gems/drb@2.2.0.rbi +1272 -0
  41. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  42. data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
  43. data/sorbet/rbi/gems/json@2.7.1.rbi +1553 -0
  44. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  45. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  46. data/sorbet/rbi/gems/minitest@5.22.2.rbi +8 -0
  47. data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +8 -0
  48. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  49. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  50. data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
  51. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  52. data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
  53. data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
  54. data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
  55. data/sorbet/rbi/gems/racc@1.7.3.rbi +157 -0
  56. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  57. data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
  58. data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
  59. data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
  60. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  61. data/sorbet/rbi/gems/rspec-core@3.13.0.rbi +10978 -0
  62. data/sorbet/rbi/gems/rspec-expectations@3.13.0.rbi +8153 -0
  63. data/sorbet/rbi/gems/rspec-mocks@3.13.0.rbi +5340 -0
  64. data/sorbet/rbi/gems/rspec-support@3.13.0.rbi +1629 -0
  65. data/sorbet/rbi/gems/rspec@3.13.0.rbi +82 -0
  66. data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7006 -0
  67. data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57383 -0
  68. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  69. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
  70. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  71. data/sorbet/rbi/gems/sqlite3@1.7.2.rbi +1691 -0
  72. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23133 -0
  73. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
  74. data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
  75. data/sorbet/rbi/gems/timeout@0.4.1.rbi +142 -0
  76. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
  77. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
  78. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  79. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  80. data/sorbet/rbi/todo.rbi +20 -0
  81. data/sorbet/tapioca/config.yml +13 -0
  82. data/sorbet/tapioca/require.rb +4 -0
  83. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esquema
4
+ VERSION = "0.1.0"
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 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,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