tapioca
tapioca copied to clipboard
Suggestion: Add an ability to enforce a type on ActiveModel::Attribute
The idea would be to add a way to express the Sorbet type for attributes.
Example model:
class Foo
include ActiveModel::Model
attribute :model
validates :model, presence: true, type: T.class_of(ApplicationRecord)
end
I have some code that works on my project, but the extension is a bit hacky:
Validator:
module ActiveModel
module Validations
class TypeValidator < EachValidator
sig { params(options: T::Hash[Symbol, T.untyped]).void }
def initialize(options)
with = options.delete(:with)
raise ArgumentError, ":with cannot be blank" if with.nil?
@with = T.let(with, T.any(T::Types::Base, String, Module, Proc))
super
end
sig { params(record: ActiveModel::Validations, attribute: Symbol, value: T.untyped).void }
def validate_each(record, attribute, value)
unless (message = type.error_message_for_obj(value)).nil?
record.errors.add(attribute, :class, message:)
end
end
sig { returns(T::Types::Base) }
def type
@type ||= T.let(
begin
with = @with
with = with.call if with.is_a?(Proc)
with = with.safe_constantize if with.is_a?(String)
with = T::Utils.coerce(with) if with.is_a?(Module)
with
end,
T.nilable(T::Types::Base),
)
end
end
end
end
Extension:
# typed: ignore
# frozen_string_literal: true
require "active_model/attributes"
require "tapioca/dsl/compilers/active_model_attributes"
module Tapioca
module Compilers
module ActiveModelTypeValidator
def attribute_methods_for_constant
return super unless constant.is_a?(ActiveModel::Validations::ClassMethods)
attribute_methods = super.to_h # Convert to hash for easier manipulation
attribute_methods.each do |method, _|
constant
.validators_on(method)
.grep(ActiveModel::Validations::TypeValidator)
.each do |validator|
attribute_methods[method] = validator.type.to_s
attribute_methods["#{method}="] = validator.type.to_s
break
end
end
attribute_methods.to_a
end
end
Dsl::Compilers::ActiveModelAttributes.prepend(ActiveModelTypeValidator)
end
end
See also known-type signatures: https://github.com/Shopify/tapioca/issues/2250