type_for_attribute & has_attribute? in Rails 7.2 Active Model

Method signature #
ClassName.type_for_attribute(attribute_name) # => ActiveModel::Type::Value subclass
Rails 7.2 moved type_for_attribute from Active Record to Active Model. Any class that includes ActiveModel::Attributes now exposes the same type lookup that Active Record models have always had. The method returns the type object Rails uses internally to cast values, so you can ask “what is the declared type of :age?” without instantiating the model.
The change shipped in Rails PR #51653 and is documented in the Rails 7.2 release notes .

Active Model example #
class MyModel
include ActiveModel::Attributes
attribute :my_attribute, :integer
end
MyModel.type_for_attribute(:my_attribute)
# => #<ActiveModel::Type::Integer ...>
MyModel.type_for_attribute(:my_attribute).cast("42")
# => 42
The return value is an ActiveModel::Type::Value subclass - Integer, String, Boolean, DateTime, and so on - with cast, serialize, and deserialize methods.
Class method vs instance access #
type_for_attribute is a class method. You call it on the class, not an instance:
MyModel.type_for_attribute(:my_attribute) # ✓ works
MyModel.new.type_for_attribute(:my_attribute) # NoMethodError
To inspect a value on a specific instance, combine it with the attribute getter:
record = MyModel.new(my_attribute: "42")
type = MyModel.type_for_attribute(:my_attribute)
type.cast(record.my_attribute) # => 42
Related: has_attribute? #
has_attribute? is the sibling method that returns true/false for whether an attribute exists, without raising on unknown keys. It’s an instance method (the inverse of type_for_attribute’s class-method shape):
record = MyModel.new(my_attribute: 42)
record.has_attribute?(:my_attribute) # => true
record.has_attribute?(:unknown) # => false
Use both together when you want to introspect dynamic input: has_attribute? checks whether the key is defined, then type_for_attribute(key).cast(value) casts it. That’s the pattern in the SignupForm example below.
Active Record still works the same way #
class User < ApplicationRecord
end
User.type_for_attribute(:email)
# => #<ActiveModel::Type::String ...>
Active Record inherits the method through the same code path now, so the API is identical across both layers.
Use case: validating form input by declared type #
class SignupForm
include ActiveModel::Attributes
attribute :email, :string
attribute :age, :integer
def initialize(params)
params.each do |key, value|
raise ArgumentError, "Unknown attribute: #{key}" unless has_attribute?(key.to_sym)
type = self.class.type_for_attribute(key.to_sym)
public_send("#{key}=", type.cast(value))
end
end
end
form = SignupForm.new(email: "test@example.com", age: "42")
form.age # => 42 (cast from "42")
Before Rails 7.2 you had to maintain a parallel hash of attribute types or duplicate the Active Record helper. The method does one job: hand back the type object so you can cast or inspect it.
Why it matters #
If you build form objects, command objects, or any plain Ruby class that uses ActiveModel::Attributes, you get the same introspection Active Record models have. No more attributes.fetch(:age).type workarounds. No custom registries.
Building Rails apps that ship? #
We rescue Rails projects that other shops left in a broken state - upgrades, performance work, test coverage. If your team is stuck on an older Rails version or your app is bleeding money on N+1 queries, talk to us.