Module: SorbetRails::ModelColumnUtils

Extended by:
T::Helpers, T::Sig
Included in:
ModelUtils
Defined in:
lib/sorbet-rails/model_column_utils.rb

Overview

typed: strict

Defined Under Namespace

Classes: ColumnType

Instance Method Summary collapse

Instance Method Details

#active_record_type_to_sorbet_type(klass, time_zone_aware: false) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
122
123
124
125
126
127
128
# 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_classObject



28
# File 'lib/sorbet-rails/model_column_utils.rb', line 28

def model_class; end

#nilable_column?(column_def) ⇒ Boolean

Returns:

  • (Boolean)


76
77
78
# 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) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 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) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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