Rails String#upcase_first VS capitalize and titleize

What comes in your mind when you have a task to make the first letter of a string uppercase? You might think about using capitalize method, or titleize method.

In one of my recent projects, we’ve been asked to ensure first capital letter in first and last name of a user. Sounds simple right? We’ve used Rails 7.1 normalization API together with String#titleize.

normalizes :first_name, with: ->(value) { value.capitalize }
normalizes :last_name, with: ->(value) { value.capitalize }

This worked well until we’ve got a user with a last name written like mcCartney. The result was not as expected, as well as the result of String#titleize method.

"mcCartney".titleize # => "Mc Cartney"
"mcCartney".capitalize # => "Mccartney"

The good news is that Rails has a solution for almost every problem, you just should know about them. In this case, the solution is String#upcase_first method, that has been introduced in Rails 5.0.

"mcCartney".upcase_first # => "McCartney"

To make everything clear, let’s take a look at the source code of these methods. I will start with titleize. For Rails 7.1, you can find the source code here.

# activesupport/lib/active_support/inflector/methods.rb
def titleize(word, keep_id_suffix: false)
  humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
    match.capitalize
  end
end

The key part of this method is the gsub call with regex pattern. This pattern is used to match lowercase letters at the beginning of words, except when those letters are preceded by a word character followed by punctuation like an apostrophe or parentheses.

String#capitalize method is a part of Ruby core library, so its source code is written in C. You can find it here. Intead of pasting C code here, I will show you the implementation of this method from a Crystal language, that is very readable for Ruby developers. The method is a bit more complex than titleize, but it’s still simple. It returns a new String with the first letter converted to uppercase and every subsequent letter converted to lowercase.

def capitalize(options : Unicode::CaseOptions = :none) : String
  return self if empty?

  if single_byte_optimizable? && (options.none? || options.ascii?)
    return String.new(bytesize) do |buffer|
      bytesize.times do |i|
        byte = to_unsafe[i]

        buffer[i] = if byte >= 0x80
                      byte
                    elsif i.zero?
                      byte.unsafe_chr.upcase.ord.to_u8!
                    else
                      byte.unsafe_chr.downcase.ord.to_u8!
                    end
      end
      {@bytesize, @length}
    end
  end

  String.build(bytesize) { |io| capitalize io, options }
end

And finally, the implementation of upcase_first method.

# activesupport/lib/active_support/inflector/methods.rb
def upcase_first(string)
  string.length > 0 ? string[0].upcase.concat(string[1..-1]) : +""
end

As you can see, upcase_first method is the simplest one. It just returns a new string with the first letter converted to uppercase and the rest of the string remains the same.

To finalize this post, I want to show you the performance comparison of these methods. I’ve created a simple benchmark that compares the performance of titleize, capitalize, and upcase_first methods. The result is quite predictable, upcase_first and capitalize are the fastest, and titleize is the slowest one. The difference is significant and you should know about it when you have to deal with a large amount of data.

# demos/titleize_capitalize_upcase_first.rb
require 'benchmark'
require 'active_support/core_ext/string/inflections'

n = 100_000

Benchmark.bm(20) do |x|
  x.report("#titleize") do
    n.times { "mcCartney".titleize }
  end

  x.report("#capitalize") do
    n.times { "mcCartney".capitalize }
  end

  x.report("#upcase_first") do
    n.times { "mcCartney".upcase_first }
  end
end
➜  igor-alexandrov.github.io git:(main) ✗ ruby demos/titleize_capitalize_upcase_first.rb
                           user     system      total        real
#titleize              0.572192   0.004364   0.576556 (  0.578087)
#capitalize            0.017191   0.000084   0.017275 (  0.017498)
#upcase_first          0.021698   0.000100   0.021798 (  0.021906)

Today you learned

There are at least three options to make the first letter of a string uppercase in Rails. They are similar to each other, but have different implementations and performance characteristics. Stay tuned!