Port type_for_attribute to Active Model

This moves `type_for_attribute` from `ActiveRecord::ModelSchema::ClassMethods`
to `ActiveModel::AttributeRegistration::ClassMethods`, where
`attribute_types` is also defined.

Co-authored-by: Petrik <petrik@deheus.net>
This commit is contained in:
Jonathan Hefner 2023-11-03 11:17:33 -05:00
parent d0f40f71b0
commit 83f543b876
7 changed files with 66 additions and 22 deletions

@ -1,2 +1,17 @@
* Port the `type_for_attribute` method to Active Model. Classes that include
`ActiveModel::Attributes` will now provide this method. This method behaves
the same for Active Model as it does for Active Record.
```ruby
class MyModel
include ActiveModel::Attributes
attribute :my_attribute, :integer
end
MyModel.type_for_attribute(:my_attribute) # => #<ActiveModel::Type::Integer ...>
```
*Jonathan Hefner*
Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activemodel/CHANGELOG.md) for previous changes.

@ -40,6 +40,21 @@ def attribute_types # :nodoc:
end
end
# Returns the type of the specified attribute after applying any
# modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. The return value
# of this method will implement the interface described by
# ActiveModel::Type::Value (though the object itself may not subclass it).
def type_for_attribute(attribute_name, &block)
attribute_name = resolve_attribute_name(attribute_name)
if block
attribute_types.fetch(attribute_name, &block)
else
attribute_types[attribute_name]
end
end
private
PendingType = Struct.new(:name, :type) do # :nodoc:
def apply_to(attribute_set)

@ -73,6 +73,17 @@ def initialize(name, cast_type)
assert_equal Type::Value.new, klass.attribute_types["bar"]
end
test ".type_for_attribute returns the registered attribute type" do
klass = class_with { attribute :foo, TYPE_1 }
assert_same TYPE_1, klass.type_for_attribute("foo")
assert_same TYPE_1, klass.type_for_attribute(:foo)
end
test ".type_for_attribute returns the default type when an unregistered attribute is specified" do
klass = class_with { attribute :foo, TYPE_1 }
assert_equal Type::Value.new, klass.type_for_attribute("bar")
end
test "new attributes can be registered at any time" do
klass = class_with { attribute :foo, TYPE_1 }
assert_includes klass._default_attributes, "foo"

@ -175,5 +175,13 @@ def attribute=(_, _)
ModelForAttributesTest.attribute :foo, :unknown
end
end
test ".type_for_attribute supports attribute aliases" do
with_alias = Class.new(ModelForAttributesTest) do
alias_attribute :integer_field, :x
end
assert_equal with_alias.type_for_attribute(:integer_field), with_alias.type_for_attribute(:x)
end
end
end

@ -249,6 +249,15 @@ def _default_attributes # :nodoc:
end
end
##
# :method: type_for_attribute
# :call-seq: type_for_attribute(attribute_name, &block)
#
# See ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
#
# This method will access the database and load the model's schema if
# necessary.
protected
def reload_schema_from_cache(*)
reset_default_attributes!

@ -439,28 +439,6 @@ def yaml_encoder # :nodoc:
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
end
# Returns the type of the attribute with the given name, after applying
# all modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. This method will
# access the database and load the model's schema if it is required.
#
# The return value of this method will implement the interface described
# by ActiveModel::Type::Value (though the object itself may not subclass
# it).
#
# +attr_name+ The name of the attribute to retrieve the type for. Must be
# a string or a symbol.
def type_for_attribute(attr_name, &block)
attr_name = attr_name.to_s
attr_name = attribute_aliases[attr_name] || attr_name
if block
attribute_types.fetch(attr_name, &block)
else
attribute_types[attr_name]
end
end
# Returns the column object for the named attribute.
# Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
# named attribute does not exist.

@ -51,6 +51,14 @@ class CustomPropertiesTest < ActiveRecord::TestCase
assert_equal 3, data.overloaded_float
end
test ".type_for_attribute supports attribute aliases" do
with_alias = Class.new(OverloadedType) do
alias_attribute :overloaded_float, :x
end
assert_equal with_alias.type_for_attribute(:overloaded_float), with_alias.type_for_attribute(:x)
end
test "overloaded properties with limit" do
assert_equal 50, OverloadedType.type_for_attribute("overloaded_string_with_limit").limit
assert_equal 255, UnoverloadedType.type_for_attribute("overloaded_string_with_limit").limit