Size, Length e Count: Qual a diferença?

No Ruby, vira e mexe a gente se depara com métodos que parecem fazer a mesma coisa. Alguns são só apelidos (aliases), mas outros têm diferenças que podem dar um nó na cabeça — principalmente quando o Rails entra na jogada com o ActiveRecord.
No Ruby puro
O length é o mais comum. Ele te diz quantos elementos tem num array, numa string ou num hash. Ele conta o que já está ali na memória e pronto.
array = [1, 2, 3, 4, 5]
puts array.length # => 5
string = "Hello, World!"
puts string.length # => 13Já o size é basicamente a mesma coisa que o length para arrays e strings. A escolha entre um ou outro é mais questão de gosto ou contexto: eu costumo usar length para strings e size para coleções, mas o resultado é o mesmo.
O count já é um pouco diferente. Além de contar o total, você pode passar um argumento ou um bloco para filtrar o que quer contar.
numeros = [1, 2, 3, 4, 5, 6]
# Quantos são pares?
numeros.count(&:even?) # => 3
# Quantas vezes aparece o número 2?
numeros.count(2) # => 1No Rails a coisa muda
Quando entramos no contexto do ActiveRecord, a escolha entre esses métodos impacta diretamente a performance, já que cada um fala com o banco de dados de um jeito diferente.
O count vai sempre fazer uma query SELECT COUNT(*) no banco. Ele é útil quando você quer o valor exato que está lá no banco naquele momento, ignorando qualquer alteração que você tenha feito no objeto mas ainda não salvou. Só é bom evitar chamar ele várias vezes seguidas, senão vira uma chuva de queries desnecessárias.
Já o length traz todo mundo para a memória. Ele carrega os registros do banco, transforma em objetos Ruby e só depois conta o tamanho do array. Se você já carregou os dados antes ou sabe que vai precisar usar eles logo em seguida, o length é uma boa. O problema é se a tabela for gigante: carregar milhares de linhas na memória só para saber o total vai ser um gargalo enorme.
O size acaba sendo o meio termo ideal. Ele é “esperto”: se os dados já estão carregados, ele só conta o que está na memória (como o length). Se não estão, ele faz o COUNT(*) no banco (como o count).
Na prática, eu acabo usando o size quase sempre. Ele resolve a maioria dos casos de forma eficiente sem que eu precise ficar conferindo se os dados já foram carregados ou não.