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 creating multiple regex objects and performing redundant operations for every pluralization check. Each uncountable word required its own regex compilation, and the Uncountables
class inherited from Array, creating unnecessary overhead.
Before the optimization, checking if a word was uncountable meant:
- Creating individual regex patterns for each uncountable word
- Performing multiple 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 eliminating regex recompilation. Previously, each uncountable?
check would create new regex objects:
1
2
3
# Before: Creates new regex each time
words = ['equipment', 'information', 'software']
words.each { |word| /#{Regexp.escape(word)}/i.match?('equipment') }
Now, a single cached regex handles all uncountable words:
1
2
3
# After: One regex for all uncountable words
pattern = Regexp.union([/equipment/i, /information/i, /software/i])
pattern.match?('equipment') # Fast single check
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:
- Memory allocation: Fewer regex objects created
- CPU overhead: Single regex compilation vs. multiple
- GC pressure: Less object churn from repeated regex creation
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.