Normalization in Rails 7.1 era
Tags: rails, ruby, webdev, programming
Published: 2024-05-21 14:00:00


### 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](https://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html#method-i-sanitize) and formatted correctly). Back then, they used clever techniques such as callbacks like [before_save](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Callbacks/ClassMethods.html#method-i-before_save) and [before_validation](https://edgeapi.rubyonrails.org/classes/ActiveModel/Validations/Callbacks/ClassMethods.html#method-i-before_validation), attribute setters, or even the [normalize](https://github.com/eet-nu/normalize) gem to get the job done. Here are some of those old tricks and tips on how they used to do it.

```ruby
# 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](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Normalization/ClassMethods.html) `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](https://edgeapi.rubyonrails.org/classes/ActiveRecord/Normalization.html)" 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](https://github.com/rails/rails/pull/43945) for model attributes. All developers need to do is pass the attribute's name. Here is how they demonstrated their solution. Pow 💥

```ruby
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](https://israilsdead.com/) happily ever after Rails 7.1 period.
The end.