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.
1model User < ApplicationRecord 2 before_save :downcase_email, if :email_present? 3 4 private 5 6 def email_present? 7 email.present? 8 end 9 10 def downcase_email 11 email.downcase! 12 end 13end
In Rails 7.1, you can refactor the above to the below code.
1model User < ApplicationRecord 2 normalizes :email, with: -> email { email.downcase } 3end
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.
1model User < ApplicationRecord 2 normalizes :user_name, with: 3 -> user_name { user_name.parameterize.underscore } 4 5 normalizes :email, with: -> { _1.strip.downcase } 6 7 normalizes :profile_image, with: 8 -> profile_image { 9 profile_image.present? ? URI.parse(profile_image).to_s : 10 "https://source.boringavatars.com/beam" }, 11 apply_to_nil: true 12end
1# rails console 2>> User.create!(user_name: "Eve Smith", email: "[email protected]") 3 4#<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> 5 6>> user = User.find_by!(email: "[email protected]") 7>> user.email # => "[email protected]" 8 9>> 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.
1# rails console 2>> legacy_user = User.find(1) 3>> legacy_user.email # => "[email protected]" 4>> legacy_user.normalize_attribute(:email) 5>> legacy_user.email # => "[email protected]" 6>> legacy_user.save
Please check out this pull request for more details.