class Tapioca::Dsl::Compilers::ActiveRecordColumns

~~~
def title; end
sig { returns(T.untyped) }
~~~rbi
and if the option is set to ‘untyped`, the `title` method will be generated as:
~~~
def title; end
sig { returns(T.nilable(::String)) }
~~~rbi
However, if `ActiveRecordColumnTypes` is set to `nilable`, the `title` method will be generated as:
~~~
end
end
## Also the methods added by api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/BeforeTypeCast.html<br>## Also the methods added by api.rubyonrails.org/classes/ActiveModel/Dirty.html<br>## Also the methods added by api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html<br><br>def updated_at?; end
sig { returns(T::Boolean) }
def updated_at=; end
sig { params(value: ::ActiveSupport::TimeWithZone).returns(::ActiveSupport::TimeWithZone) }
def updated_at; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def title?; end
sig { returns(T::Boolean) }
def title=(value); end
sig { params(value: ::String).returns(::String) }
def title; end
sig { returns(::String) }
def published?; end
sig { returns(T::Boolean) }
def published=; end
sig { params(value: T::Boolean).returns(T::Boolean) }
def published; end
sig { returns(T.nilable(T::Boolean)) }
def created_at?; end
sig { returns(T::Boolean) }
def created_at=; end
sig { params(value: ::ActiveSupport::TimeWithZone).returns(::ActiveSupport::TimeWithZone) }
def created_at; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def body?; end
sig { returns(T::Boolean) }
def body=; end
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
def body; end
sig { returns(T.nilable(::String)) }
module GeneratedAttributeMethods
include GeneratedAttributeMethods
class Post
# typed: true
# post.rbi
~~~rbi
`post.rbi`:
this compiler will, by default, produce the following methods in the RBI file
~~~
end
t.timestamps
t.boolean :published
t.string :body
t.string :title, null: false
create_table :posts do |t|
# db/schema.rb
~~~rb
and the following database schema:
~~~
end
class Post < ActiveRecord::Base
~~~rb
For example, with the following model class:
ready to start typing your models strictly yet, but still want to generate RBI files for them.
- `untyped`: The methods will be generated with `T.untyped` return types. This mode is practical if you are not
persisted models.
handle the `nil` cases explicitly using `T.must` or the safe navigation operator `&.`, even for valid
correct way to type the methods, but it can make working with the models more cumbersome, as you will have to
- `nilable`: All column methods will be generated with `T.nilable` return types. This is strictly the most
have all kinds of non-sensical values in their column attributes.
Active Record models easier, but does not match the behaviour of non-persisted or invalid models, which can
mode basically treats each model as if it was a valid and persisted model. Note that this makes typing
will be `String`, but if the column is also nullable, then the return type will be `T.nilable(String)`. This
column type as the return type. This means that if the column is a string, the method return type
- `persisted` (default): The methods will be generated with the type that matches the actual database
how the types of the column related methods should be generated. The option can be one of the following:
This compiler accepts a `ActiveRecordColumnTypes` option that can be used to specify
model.
created for columns and virtual attributes that are defined in the Active Record
This compiler is only responsible for defining the attribute methods that would be<br>(api.rubyonrails.org/classes/ActiveRecord/Base.html).
`Tapioca::Dsl::Compilers::ActiveRecordColumns` refines RBI files for subclasses of

def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])

def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])
  klass.create_method(
    name,
    parameters: parameters,
    return_type: return_type,
  ) if methods_to_add.nil? || methods_to_add.include?(name)
end

def add_methods_for_attribute(klass, attribute_name, column_name = attribute_name, methods_to_add = nil)

def add_methods_for_attribute(klass, attribute_name, column_name = attribute_name, methods_to_add = nil)
  getter_type, setter_type = Helpers::ActiveRecordColumnTypeHelper
    .new(constant, column_type_option: column_type_option)
    .type_for(attribute_name, column_name)
  # Added by ActiveRecord::AttributeMethods::Read
  #
  add_method(
    klass,
    attribute_name.to_s,
    methods_to_add,
    return_type: getter_type,
  )
  # Added by ActiveRecord::AttributeMethods::Write
  #
  add_method(
    klass,
    "#{attribute_name}=",
    methods_to_add,
    parameters: [create_param("value", type: setter_type)],
    return_type: setter_type,
  )
  # Added by ActiveRecord::AttributeMethods::Query
  #
  add_method(
    klass,
    "#{attribute_name}?",
    methods_to_add,
    return_type: "T::Boolean",
  )
  # Added by ActiveRecord::AttributeMethods::Dirty
  #
  add_method(
    klass,
    "#{attribute_name}_before_last_save",
    methods_to_add,
    return_type: as_nilable_type(getter_type),
  )
  add_method(
    klass,
    "#{attribute_name}_change_to_be_saved",
    methods_to_add,
    return_type: "T.nilable([#{getter_type}, #{getter_type}])",
  )
  add_method(
    klass,
    "#{attribute_name}_in_database",
    methods_to_add,
    return_type: as_nilable_type(getter_type),
  )
  add_method(
    klass,
    "saved_change_to_#{attribute_name}",
    methods_to_add,
    return_type: "T.nilable([#{getter_type}, #{getter_type}])",
  )
  add_method(
    klass,
    "saved_change_to_#{attribute_name}?",
    methods_to_add,
    return_type: "T::Boolean",
  )
  add_method(
    klass,
    "will_save_change_to_#{attribute_name}?",
    methods_to_add,
    return_type: "T::Boolean",
  )
  # Added by ActiveModel::Dirty
  #
  add_method(
    klass,
    "#{attribute_name}_change",
    methods_to_add,
    return_type: "T.nilable([#{getter_type}, #{getter_type}])",
  )
  add_method(
    klass,
    "#{attribute_name}_changed?",
    methods_to_add,
    return_type: "T::Boolean",
    parameters: [
      create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
      create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
    ],
  )
  add_method(
    klass,
    "#{attribute_name}_will_change!",
    methods_to_add,
  )
  add_method(
    klass,
    "#{attribute_name}_was",
    methods_to_add,
    return_type: as_nilable_type(getter_type),
  )
  add_method(
    klass,
    "#{attribute_name}_previous_change",
    methods_to_add,
    return_type: "T.nilable([#{getter_type}, #{getter_type}])",
  )
  add_method(
    klass,
    "#{attribute_name}_previously_changed?",
    methods_to_add,
    return_type: "T::Boolean",
    parameters: [
      create_kw_opt_param("from", type: setter_type, default: "T.unsafe(nil)"),
      create_kw_opt_param("to", type: setter_type, default: "T.unsafe(nil)"),
    ],
  )
  add_method(
    klass,
    "#{attribute_name}_previously_was",
    methods_to_add,
    return_type: as_nilable_type(getter_type),
  )
  add_method(
    klass,
    "restore_#{attribute_name}!",
    methods_to_add,
  )
  # Added by ActiveRecord::AttributeMethods::BeforeTypeCast
  #
  add_method(
    klass,
    "#{attribute_name}_before_type_cast",
    methods_to_add,
    return_type: "T.untyped",
  )
  add_method(
    klass,
    "#{attribute_name}_came_from_user?",
    methods_to_add,
    return_type: "T::Boolean",
  )
end

def column_type_option

def column_type_option
  @column_type_option ||= T.let(
    ColumnTypeOption.from_options(options) do |value, default_column_type_option|
      add_error(<<~MSG.strip)
        Unknown value for compiler option `ActiveRecordColumnTypes` given: `#{value}`.
        Proceeding with the default value: `#{default_column_type_option.serialize}`.
      MSG
    end,
    T.nilable(ColumnTypeOption),
  )
end

def decorate

def decorate
  return unless constant.table_exists?
  # We need to call this to ensure that some attribute aliases are defined, e.g.
  # `id_value` as an alias for `id`.
  # I think this is a regression on Rails 7.1, but we are where we are.
  constant.define_attribute_methods
  root.create_path(constant) do |model|
    model.create_module(AttributeMethodsModuleName) do |mod|
      (constant.attribute_names + ["id"]).uniq.each do |attribute_name|
        add_methods_for_attribute(mod, attribute_name)
      end
      constant.attribute_aliases.each do |attribute_name, column_name|
        attribute_name = attribute_name.to_s
        column_name = column_name.to_s
        patterns = if constant.respond_to?(:attribute_method_patterns)
          # https://github.com/rails/rails/pull/44367
          constant.attribute_method_patterns
        else
          T.unsafe(constant).attribute_method_matchers
        end
        new_method_names = patterns.map { |m| m.method_name(attribute_name) }
        old_method_names = patterns.map { |m| m.method_name(column_name) }
        methods_to_add = new_method_names - old_method_names
        add_methods_for_attribute(mod, attribute_name, column_name, methods_to_add)
      end
    end
    model.create_include(AttributeMethodsModuleName)
  end
end

def gather_constants

def gather_constants
  descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
end