Skip to content

Commit

Permalink
Use ActiveModel::Attributes to define fields
Browse files Browse the repository at this point in the history
  • Loading branch information
nickpresta committed Apr 19, 2022
1 parent 1e96598 commit 61402d6
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 22 deletions.
49 changes: 45 additions & 4 deletions lib/tapioca/dsl/compilers/frozen_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,21 @@ def decorate
attributes = constant.attributes
return if attributes.empty?

instance = constant.first

root.create_path(constant) do |record|
module_name = "FrozenRecordAttributeMethods"

record.create_module(module_name) do |mod|
attributes.each do |attribute|
return_type = instance.attributes[attribute].class.name
return_type = "T::Boolean" if ["FalseClass", "TrueClass"].include?(return_type)
return_type = "T::untyped"
if constant.respond_to?(:attribute_types)
attribute_type = T.let(
T.unsafe(constant).attribute_types[attribute],
ActiveModel::Type::Value
)
has_default = T.let(constant.default_attributes.key?(attribute), T::Boolean)
return_type = type_for(attribute_type, has_default)
end

mod.create_method("#{attribute}?", return_type: "T::Boolean")
mod.create_method(attribute.to_s, return_type: return_type)
end
Expand All @@ -99,6 +105,41 @@ def self.gather_constants

private

sig { params(attribute_type_value: ::ActiveModel::Type::Value, has_default: T::Boolean).returns(::String) }
def type_for(attribute_type_value, has_default)
type = case attribute_type_value
when ActiveModel::Type::Boolean
"T::Boolean"
when ActiveModel::Type::Date
"::Date"
when ActiveModel::Type::DateTime, ActiveModel::Type::Time
"::DateTime"
when ActiveModel::Type::Decimal
"::BigDecimal"
when ActiveModel::Type::Float
"::Float"
when ActiveModel::Type::Integer
"::Integer"
when ActiveModel::Type::String
"::String"
else
other_type = attribute_type_value.type
case other_type
when :array
"::Array"
when :hash
"::Hash"
when :symbol
"::Symbol"
else
# we don't want untyped to be wrapped by T.nilable, so just return early
return "T.untyped"
end
end

has_default ? type : as_nilable_type(type)
end

sig { params(record: RBI::Scope).void }
def decorate_scopes(record)
scopes = T.unsafe(constant).__tapioca_scope_names
Expand Down
88 changes: 70 additions & 18 deletions spec/tapioca/dsl/compilers/frozen_record_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,19 @@ class Student
include FrozenRecordAttributeMethods
module FrozenRecordAttributeMethods
sig { returns(String) }
sig { returns(T::untyped) }
def first_name; end
sig { returns(T::Boolean) }
def first_name?; end
sig { returns(Integer) }
sig { returns(T::untyped) }
def id; end
sig { returns(T::Boolean) }
def id?; end
sig { returns(String) }
sig { returns(T::untyped) }
def last_name; end
sig { returns(T::Boolean) }
Expand All @@ -101,11 +101,63 @@ def last_name?; end
add_ruby_file("student.rb", <<~RUBY)
# typed: strong
class ArrayOfType < ActiveModel::Type::Value
attr_reader :element_type
def initialize(element_type:)
super()
@element_type = element_type
end
def type
:array
end
end
class HashOfType < ActiveModel::Type::Value
attr_reader :key_type
attr_reader :value_type
def initialize(key_type:, value_type:)
super()
@key_type = key_type
@value_type = value_type
end
def type
:hash
end
end
class SymbolType < ActiveModel::Type::Value
def type
:symbol
end
end
ActiveModel::Type.register(:array_of_type, ArrayOfType)
ActiveModel::Type.register(:hash_of_type, HashOfType)
ActiveModel::Type.register(:symbol, SymbolType)
class Student < FrozenRecord::Base
extend(T::Sig)
extend T::Sig
include ActiveModel::Attributes
# specifically missing the id field, should be untyped
attribute :first_name, :string
attribute :last_name, :string
attribute :age, :integer
attribute :location, :string
attribute :is_cool_person, :boolean
attribute :birth_date, :date
attribute :updated_at, :time
# custom attribute types
attribute :favourite_foods, :array_of_type, element_type: :string
attribute :skills, :hash_of_type, key_type: :symbol, value_type: :string
# attribute with a default, shouldn't be nilable
attribute :shirt_size, :symbol
self.base_path = __dir__
self.default_attributes = { shirt_size: :large }
sig { params(grain: Symbol).returns(String) }
Expand Down Expand Up @@ -161,67 +213,67 @@ class Student
include FrozenRecordAttributeMethods
module FrozenRecordAttributeMethods
sig { returns(Integer) }
sig { returns(T.nilable(::Integer)) }
def age; end
sig { returns(T::Boolean) }
def age?; end
sig { returns(Date) }
sig { returns(T.nilable(::Date)) }
def birth_date; end
sig { returns(T::Boolean) }
def birth_date?; end
sig { returns(Array) }
sig { returns(T.nilable(::Array)) }
def favourite_foods; end
sig { returns(T::Boolean) }
def favourite_foods?; end
sig { returns(String) }
sig { returns(T.nilable(::String)) }
def first_name; end
sig { returns(T::Boolean) }
def first_name?; end
sig { returns(Integer) }
sig { returns(T.untyped) }
def id; end
sig { returns(T::Boolean) }
def id?; end
sig { returns(T::Boolean) }
sig { returns(T.nilable(T::Boolean)) }
def is_cool_person; end
sig { returns(T::Boolean) }
def is_cool_person?; end
sig { returns(String) }
sig { returns(T.nilable(::String)) }
def last_name; end
sig { returns(T::Boolean) }
def last_name?; end
sig { returns(String) }
sig { returns(T.nilable(::String)) }
def location; end
sig { returns(T::Boolean) }
def location?; end
sig { returns(Symbol) }
sig { returns(::Symbol) }
def shirt_size; end
sig { returns(T::Boolean) }
def shirt_size?; end
sig { returns(Hash) }
sig { returns(T.nilable(::Hash)) }
def skills; end
sig { returns(T::Boolean) }
def skills?; end
sig { returns(Time) }
sig { returns(T.nilable(::DateTime)) }
def updated_at; end
sig { returns(T::Boolean) }
Expand Down Expand Up @@ -257,13 +309,13 @@ class Student
extend GeneratedRelationMethods
module FrozenRecordAttributeMethods
sig { returns(String) }
sig { returns(T::untyped) }
def course; end
sig { returns(T::Boolean) }
def course?; end
sig { returns(Integer) }
sig { returns(T::untyped) }
def id; end
sig { returns(T::Boolean) }
Expand Down

0 comments on commit 61402d6

Please sign in to comment.