Normalization in Rails 7.1 era
Before Rails 7.1
Once upon a time, way long before the Rails 7.1 era, a smart Rails developer (like yourself) needed to ensure user email addresses were properly normalized (sanitized and formatted correctly). Back then, they used clever techniques such as callbacks like before_save and before_validation, attribute setters, or even the normalize gem to get the job done. Here are some of those old tricks and tips on how they used to do it.
# normalize with before_save callback
class User < ApplicationRecord
before_save :sanitize_email
private
def sanitize_email
self.email = email.strip.downcase
end
end
# normalize with before_validation callback
class User < ApplicationRecord
before_validation :sanitize_email
private
def sanitize_email
self.email = email.strip.downcase
end
end
# override the setter from ActiveRecord
class User < ApplicationRecord
def email=(value)
super(value.strip.downcase)
end
end
# Don't like callbacks? Use the normalize gem in `app/normalizers`
class EmailNormalizer
def self.call(email)
email.strip.downcase
end
end
class User < ApplicationRecord
normalize :email, with: EmailNormalizer
end
After Rails 7.1
As time passed, the Rails community reached the Rails 7.1 era. A group of those smart Rails developers (maybe it's you) gathered around the Rails core team and agreed on a better way to normalize attributes. They came up with a nifty idea.
Imagine having a ClassMethod normalizes
that comes with a set of rules, such as converting all email addresses to lowercase, removing leading/trailing whitespace, or enforcing a specific format before they are saved to the database. This "Normalization" class reduces data redundancy and minimizes inconsistencies. Also, it organizes data in a structured and consistent way, making it easier to query, update, and maintain.
The Rails core team wanted to make it easy for today's and future Rails developers by providing a simple API for model attributes. All developers need to do is pass the attribute's name. Here is how they demonstrated their solution. Pow 💥
class User < ActiveRecord::Base
normalizes :email, with: -> email { email.strip.downcase }
normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
end
user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
user.email # => "cruise-control@example.com"
user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
user.email # => "cruise-control@example.com"
user.email_before_type_cast # => "cruise-control@example.com"
User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
And Rails developers live happily ever after Rails 7.1 period. The end.
Subscribe to my newsletter
Read articles from Ahmed Nadar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ahmed Nadar
Ahmed Nadar
Developer and Product Design. RapidRails UI components creator Run RapidRails Agency. https://rapidrails.cc