Rails 6.1 allows attribute's default to be configured but keeping its type


Rails allows us to change the type and set the default value of the attribute.

Let’s consider Message class with sent_at attribute of type datetime.

class Message
end

> Message.type_for_attribute(:sent_at)
=> <ActiveRecord::Type::DateTime:0x00007fa01c1fd650 @precision=6, @scale=nil, @limit=nil>
> Message.new.sent_at
=> nil

We can change the sent_at attribute’s type to integer in the following way:

class Message
  attribute :sent_at, :integer
end

> Message.type_for_attribute(:sent_at)
=> <ActiveModel::Type::Integer:0x00007fa01bd86ba8 @precision=nil, @scale=nil, @limit=nil, @range=-2147483648...2147483648>

We can also set the default value by providing a default option:

class Message
  attribute :sent_at, default: -> { Time.now.utc }
end

> Time.now.utc
=> 2020-10-15 12:10:16 UTC
> Message.new.sent_at
=> 2020-10-15 12:10:16 UTC

But the problem with this is, Rails also changes the type of the attribute when setting the default value, and drops the proper type:

> Message.type_for_attribute(:sent_at)
=> <ActiveModel::Type::Value:0x00007fd16d255418 @precision=nil, @scale=nil, @limit=nil>

Rails 6.1

Rails 6.1 has now fixed this issue.

class Message
  attribute :sent_at, default: -> { Time.now.utc }
end

> Time.now.utc
=> 2020-10-15 12:10:16 UTC
> Message.new.sent_at
=> 2020-10-15 12:10:16 UTC
> Message.type_for_attribute(:sent_at)
=> <ActiveRecord::Type::DateTime:0x00007fa01c1fd650 @precision=6, @scale=nil, @limit=nil>

As we can see, the type here is set to ActiveRecord::Type::DateTime based on the default value, instead of dropping the type.