module SorbetRails::ModelColumnUtils

typed: strict

Public Instance Methods

active_record_type_to_sorbet_type(klass, time_zone_aware: false) click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 86
def active_record_type_to_sorbet_type(klass, time_zone_aware: false)
  case klass
  when ActiveRecord::Type::Boolean
    "T::Boolean"
  when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
    time_zone_aware ? ActiveSupport::TimeWithZone : Time
  when ActiveRecord::Type::Date
    Date
  when ActiveRecord::Type::Decimal
    BigDecimal
  when ActiveRecord::Type::Float
    Float
  when ActiveRecord::Type::BigInteger, ActiveRecord::Type::Integer, ActiveRecord::Type::DecimalWithoutScale, ActiveRecord::Type::UnsignedInteger
    Integer
  when ActiveRecord::Type::Binary, ActiveRecord::Type::String, ActiveRecord::Type::Text
    String
  else
    # Json type is only supported in Rails 5.2 and above
    case
    when Object.const_defined?('ActiveRecord::Type::Json') && klass.is_a?(ActiveRecord::Type::Json)
      "T.any(T::Array[T.untyped], T::Boolean, Float, T::Hash[T.untyped, T.untyped], Integer, String)"
    when Object.const_defined?('ActiveRecord::Enum::EnumType') && klass.is_a?(ActiveRecord::Enum::EnumType)
      String
    # For Postgres UUIDs, they're represented as Strings
    when Object.const_defined?('ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid') && klass.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid)
      String
    else
      "T.untyped"
    end
  end
end
attribute_has_unconditional_presence_validation?(attribute) click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 119
def attribute_has_unconditional_presence_validation?(attribute)
  model_class_srb = model_class
  model_class_srb < ActiveRecord::Base &&
  model_class_srb.validators_on(attribute).any? do |validator|
    validator.is_a?(ActiveModel::Validations::PresenceValidator) &&
      !validator.options.key?(:if) &&
      !validator.options.key?(:unless) &&
      !validator.options.key?(:on)
  end
end
model_class() click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 28
def model_class; end
nilable_column?(column_def) click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 76
def nilable_column?(column_def)
  !!(column_def.null && !attribute_has_unconditional_presence_validation?(column_def.name))
end
time_zone_aware_column?(column_def, cast_type) click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 60
def time_zone_aware_column?(column_def, cast_type)
  # this private class method returns true if the attribute should be "time
  # zone aware"; it takes into account various global and model-specific
  # configuration options as described here:
  # https://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
  #
  # although it's private, it's better this than trying to reimplement the "is
  # this attribute tz aware?" logic ourselves
  model_class.send(
    :create_time_zone_conversion_attribute?,
    column_def.name,
    cast_type
  )
end
type_for_column_def(column_def) click to toggle source
# File lib/sorbet-rails/model_column_utils.rb, line 31
def type_for_column_def(column_def)
  cast_type = ActiveRecord::Base.connection.respond_to?(:lookup_cast_type_from_column) ?
    ActiveRecord::Base.connection.lookup_cast_type_from_column(column_def) :
    column_def.cast_type

  array_type = false
  if column_def.try(:array?)
    cast_type = cast_type.subtype if cast_type.try(:subtype)
    array_type = true
  end
  strict_type =
    active_record_type_to_sorbet_type(
      cast_type,
      time_zone_aware: time_zone_aware_column?(column_def, cast_type),
    )

  ColumnType.new(
    base_type: strict_type,
    nilable: nilable_column?(column_def),
    array_type: array_type,
  )
end