Normalization in Rails 7.1 era

Normalization in Rails 7.1 era

ยท

2 min read

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.

Did you find this article valuable?

Support Ahmed Nadar by becoming a sponsor. Any amount is appreciated!

ย