May 9, 2023
This blog is part of our Rails 7 series.
Rails 7.1 has introduced a new method in Active Record that can be used to declare normalizations for attribute values. This can be especially useful for sanitizing user input, ensuring consistent formatting, or cleaning up data from external sources.
Before Rails 7.1, you could normalize attributes using before_save
callback.
model User < ApplicationRecord
before_save :downcase_email, if :email_present?
private
def email_present?
email.present?
end
def downcase_email
email.downcase!
end
end
In Rails 7.1, you can refactor the above to the below code.
model User < ApplicationRecord
normalizes :email, with: -> email { email.downcase }
end
The normalization is applied when the attribute is assigned or updated, and the normalized value will be persisted to the database. The normalization is also applied to the corresponding keyword argument of finder methods. This allows a record to be created and later queried using unnormalized values.
By default, the normalization will not be applied to nil
values. To normalize
nil
value, you can enable it using :apply_to_nil
option.
model User < ApplicationRecord
normalizes :user_name, with:
-> user_name { user_name.parameterize.underscore }
normalizes :email, with: -> { _1.strip.downcase }
normalizes :profile_image, with:
-> profile_image {
profile_image.present? ? URI.parse(profile_image).to_s :
"https://source.boringavatars.com/beam" },
apply_to_nil: true
end
# rails console
>> User.create!(user_name: "Eve Smith", email: "[email protected]")
#<User:0x000000010b757090 id: 1, user_name: "eve_smith", profile_image:"https://source.boringavatars.com/beam", email: "[email protected]", created_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00, updated_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00>
>> user = User.find_by!(email: "[email protected]")
>> user.email # => "[email protected]"
>> User.exists?(email: "[email protected]") # => true
If a user's email was already stored in the database before the normalization statement was added to the model, the email will not be retrieved in the normalized format.
This is because in the database it's stored in mixed case, that is, without the
normalization. If you have legacy data, you can normalize it explicitly using
the Normalization#normalize_attribute
method.
# rails console
>> legacy_user = User.find(1)
>> legacy_user.email # => "[email protected]"
>> legacy_user.normalize_attribute(:email)
>> legacy_user.email # => "[email protected]"
>> legacy_user.save
Please check out this pull request for more details.
If this blog was helpful, check out our full blog archive.