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.