Rails pluralize Just Got 4x Faster
· 4 min read
Rails’ pluralize helper just received a significant performance boost that makes it up to 4 times faster for uncountable words. The optimization, merged in PR #55485, tackles inefficiencies in the ActiveSupport Inflector through regex caching and structural improvements.
If you’re not familiar with pluralize, it’s the helper that converts singular words to their plural forms:
1
2
3
4
5
6
7
8
"post".pluralize # => "posts"
"person".pluralize # => "people"
"sheep".pluralize # => "sheep" (uncountable)
# With counts
pluralize(1, "post") # => "1 post"
pluralize(2, "post") # => "2 posts"
pluralize(5, "person") # => "5 people"
The Performance Problem
Rails’ pluralization system was performing redundant operations for every pluralization check. While the regex patterns were compiled once, the system still had to iterate through and check each pattern individually. The Uncountables class also inherited from Array, creating unnecessary overhead.
Before the optimization, checking if a word was uncountable meant:
- Iterating through each uncountable word pattern
- Performing multiple individual regex matches
- Maintaining complex array inheritance structure
Rails Optimization Approach
The optimization introduces three key improvements:
1. Regex Caching with Regexp.union()
Before:
1
2
3
4
5
6
7
8
class Uncountables < Array
def uncountable?(str)
each do |word|
return true if /#{Regexp.escape(word)}/i.match?(str)
end
false
end
end
After:
1
2
3
4
5
6
class Uncountables
def uncountable?(str)
@pattern ||= Regexp.union(@members.map { |w| /#{Regexp.escape(w)}/i })
@pattern.match?(str)
end
end
The new approach creates a single, cached regex pattern using Regexp.union() instead of compiling individual patterns for each check.
2. Composition Over Inheritance
The Uncountables class no longer inherits from Array. Instead, it uses composition with an internal @members array and delegates necessary methods:
1
2
3
4
5
6
7
8
class Uncountables
extend Forwardable
def_delegators :@members, :<<, :concat, :each, :clear, :to_a
def initialize
@members = []
end
end
3. English Inflection Fast Path
A dedicated cache for English inflections reduces instance lookups:
1
2
3
4
5
6
7
8
9
def self.instance(locale = :en)
@__instances__ ||= {}
@__instances__[locale] ||=
if locale == :en
@__en_instance__ ||= new
else
new
end
end
Performance Results
The optimization delivers substantial improvements across all pluralization types:
1
2
3
4
5
# Benchmark results (iterations/second)
Before After Improvement
regular 252.175k 298.382k 18% faster
irregular 851.502k 1.943M 128% faster
uncountable 1.487M 6.008M 304% faster (4x)
Uncountable words see the most dramatic improvement, going from 1.487M to 6.008M iterations per second. That’s a 4x performance increase.
When This Optimization Matters
This optimization benefits applications that:
- Frequently pluralize words in views and helpers
- Process large datasets with text inflection
- Use Rails’ built-in pluralization in tight loops
- Handle user-generated content requiring pluralization
Common scenarios include:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Dashboard counters
pluralize(@users.count, 'user')
pluralize(@orders.count, 'order')
# Data processing
products.each do |product|
puts "#{product.quantity} #{product.name.pluralize(product.quantity)}"
end
# API responses
render json: {
message: "Found #{results.count} #{model_name.pluralize(results.count)}"
}
Technical Deep Dive
The optimization works by combining multiple regex checks into one. Previously, each uncountable? check would iterate through all patterns:
1
2
3
# Before: Check each regex individually
uncountable_patterns.any? { |pattern| pattern.match?('equipment') }
# This means potentially checking many patterns one by one
Now, a single unified regex handles all uncountable words:
1
2
3
# After: One regex for all uncountable words
pattern = Regexp.union(uncountable_patterns)
pattern.match?('equipment') # Single check against combined pattern
The Regexp.union() method creates an optimized alternation pattern that the Ruby regex engine can process efficiently.
Memory and CPU Benefits
Beyond raw speed improvements, the changes reduce:
- CPU overhead: Single regex match vs. multiple iterations
- Performance overhead: No need to loop through patterns
- Structural complexity: Removed unnecessary Array inheritance
Rails applications with heavy text processing will see the most benefit from these efficiency gains.
Conclusion
Rails’ pluralize optimization demonstrates how targeted performance improvements can deliver significant gains. By caching regex patterns and simplifying class structure, the change achieves 4x faster performance for uncountable words while improving overall inflection efficiency.
You might wonder why I’m covering an internal Rails optimization that developers don’t need to act on. Truth is, these kinds of improvements fascinate me. A 4x performance boost from clever regex caching and removing unnecessary inheritance? It’s elegant problem-solving at its finest. While we won’t change our code because of this, understanding how Rails evolves under the hood makes us better developers. Plus, seeing the framework we use daily getting faster without any work on our part? That’s just satisfying.