Introduction
Rails 7.1 introduced normalizes, which automatically transforms attribute values before validation. Instead of before_validation callbacks to strip whitespace or downcase emails, you declare normalizations directly on the model.
Key Concepts
normalizes: A class method that registers a transformation to run automatically when an attribute is set.- Applied on assignment: Normalizations run immediately when you assign a value, not just before save.
- Applied in queries: Normalizations are also applied to query values for consistent lookups.
Real World Context
Every application needs normalization. Emails should be lowercase. Phone numbers need consistent formatting. Before normalizes, this required before_validation callbacks. The normalizes method centralizes this with a clean, declarative API.
Deep Dive
Basic Normalization
rubyclass User < ApplicationRecord normalizes :email, with: -> e { e.strip.downcase } end
This normalizes the email whenever it is assigned:
rubyuser = User.new(email: " ALICE@Example.COM ") user.email # => "alice@example.com" — normalized immediately!
Multiple Normalizations
rubyclass Article < ApplicationRecord normalizes :title, with: -> t { t.strip.squeeze(" ") } normalizes :slug, with: -> s { s.strip.downcase.gsub(/\s+/, "-") } end
Each attribute gets its own normalization lambda.
Normalizations in Queries
Normalizations are applied to query values too:
rubyUser.find_by(email: " ALICE@Example.COM ") # SQL: SELECT * FROM users WHERE email = 'alice@example.com'
This prevents mismatches between stored values and query parameters.
Comparison with before_validation
ruby# Old approach class User < ApplicationRecord before_validation :normalize_email private def normalize_email self.email = email&.strip&.downcase end end # New approach (Rails 7.1+) class User < ApplicationRecord normalizes :email, with: -> e { e.strip.downcase } end
The normalizes approach is shorter, declarative, applies on assignment, and normalizes query values.
Common Pitfalls
- Normalizing to nil accidentally: If your lambda returns nil for blank strings, the attribute becomes nil.
- Expensive normalizations: The lambda runs on every assignment. Keep it lightweight.
- Not using normalizes in queries: If you normalize on save but query with raw input, you get mismatches.
normalizeshandles this automatically.
Best Practices
- Use
normalizesfor simple transformations: strip, downcase, formatting. - Keep normalization lambdas pure (no side effects).
- Replace
before_validationnormalization callbacks withnormalizeswhen on Rails 7.1+.
Summary
normalizesdeclares automatic attribute transformations that run on assignment.- Normalizations are also applied to query values for consistent lookups.
- Use
normalizesinstead ofbefore_validationcallbacks. - Keep normalization lambdas simple and pure.
- Handle nil carefully — normalizations skip nil by default.
Code Examples
class User < ApplicationRecord
normalizes :email, with: -> e { e.strip.downcase }
normalizes :name, with: -> n { n.strip.squeeze(" ") }
end
user = User.new(email: " ALICE@Example.COM ")
user.email # => "alice@example.com" — normalized on assignment
# Queries are normalized too
User.find_by(email: " Alice@EXAMPLE.com ")
# SQL: WHERE email = 'alice@example.com'