Fala, gente!
Hoje vamos falar um pouco sobre os métodos
. O primeiro é o mais comum, mas os outros muita gente desconhece, então acho que vale comentar sobre eles. Vamos lá!.each
,
.find_each
,
.find_in_batches
e o
in_batches
.each
Bom, o
é o mais conhecido e você já deve ter usado em algum momento. Em resumo ele vai servir para iterar em uma coleção e você pode passar um bloco de código para ser executado para cada um dos elementos da coleção. Veja o exemplo:.each
arr = [1,2,3,4,5]
arr.each do |i|
puts i * 2
end
2
4
6
8
10
Como podemos observar para cada elemento do array imprimi o próprio elemento * 2. Até aqui nenhuma novidade.
.find_each
A primeira coisa que precisamos saber sobre o
ele só vai funcionar no Rails. .find_each
é que ele é um método do **ActiveRecord** (assim como os próximos dois métodos que veremos), ou seja, diferente do seu irmão
.each
Sua funcionalidade também é iterar em uma coleção, no entanto a diferença é que enquanto o
vai carregar (inicialmente) 1000 elementos por vez, ou seja, são feitos lotes de 1000 elementos que são iterados um a um. Para nosso exemplo vamos começar criando uma app rails..each
carrega todos os elementos da coleção em memória para só depois iterar, o
.find_each
/> rails new testapp
/> cd testapp
/> rails g model Person name
/> rails db:create
/> rails db:migrate
/> rails console
Já dentro do rails console faça:
(1..10_000).each do |i|
Person.create!(name: "person#{i}")
end
Prontinho, com isso temos uma app rails já com 10.000 pessoas cadastradas.
O exemplo básico seria rodar algo como :
Person.find_each do |person|
puts person.name
end
Mas rodando o comando acima praticamente não veremos diferença entre o
, no entanto, perceba a saída SQL no terminal informando que deve ser buscado apenas 1000 itens no banco de dados.each
e o
find_each
Person Load (0.2ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 10000], ["LIMIT", 1000]]
Daí temos a prova que só foram carregados 1000 elementos por vez. Mas a coisa pode ficar ainda melhor, podemos escolher carregar 5 elementos por vez ao invés de 1000, para isso rode:
Person.find_each(batch_size: 5) do |person|
puts person.name
end
Assim teremos uma saída como essa abaixo, mostrando que o SQL busca agora de 5 em 5 registros.
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 9990], ["LIMIT", 5]]
person9991
person9992
person9993
person9994
person9995
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 9995], ["LIMIT", 5]]
person9996
person9997
person9998
person9999
person10000
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 10000], ["LIMIT", 5]]
Só com isso já temos uma bela vantagem sobre o .each
pois imagine uma grande aplicação com milhões de registros a serem trabalhados, o .find_each
vem pra resolver esse tipo de situação.
Além dessa opção de especificar a quantidade de items por lote, podemos usar algumas outras (:start, :finish e :error_on_ignore) que valem serem olhadas na documentação oficial.
.find_in_batches
Uma outra possibilidade é usar o
e sua diferença fica por conta de que ele vai carregar em memória o lote de elementos e você decide o que fazer com eles, com por exemplo, iterar nesse lote ou fazer qualquer outra coisa. Veja esse exemplo:.find_in_batches
, que em resumo se parece muito como
.find_each
Person.find_in_batches(batch_size: 5) do |people|
puts people.inspect
end
...
[#<Person id: 9996, name: "person9996", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9997, name: "person9997", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9998, name: "person9998", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9999, name: "person9999", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 10000, name: "person10000", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">]
Perceba que o resultado é um array de Person, daí precisamos, por exemplo, iterar nesse array e fazer o que quiser. Veja:
Person.find_in_batches(batch_size: 5) do |people|
people.each do |person|
puts person.name
end
end
...
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 9990], ["LIMIT", 5]]
person9991
person9992
person9993
person9994
person9995
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 9995], ["LIMIT", 5]]
person9996
person9997
person9998
person9999
person10000
Person Load (0.1ms) SELECT "people".* FROM "people" WHERE "people"."id" > ? ORDER BY "people"."id" ASC LIMIT ? [["id", 10000], ["LIMIT", 5]]
As opções são praticamente as mesmas do
, mas claro que vale olhar a documentação para ver os exemplos.find_each
in_batches
Por fim temos o
, no entanto ao invés de devolver um array de elementos, ele devolve um ActiveRecord::Relation, veja:.in_batches
que é muito parecido com o
.find_in_batches
Person.in_batches(of: 5) do |relation|
puts relation.inspect
end
...
#<ActiveRecord::Relation [#<Person id: 9996, name: "person9996", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9997, name: "person9997", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9998, name: "person9998", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 9999, name: "person9999", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">, #<Person id: 10000, name: "person10000", created_at: "2020-02-23 23:13:44", updated_at: "2020-02-23 23:13:44">]>
A grande magia aqui é que por retornar uma ActiveRecord::Relation você pode aplicar os métodos que já conhecemos como .delete_all, .update_all, dentre outros.
Aqui também vale ressaltar que as opções disponíveis para o uso com o
), então, como sempre, vale consultar a documentação e conhecer todos os detalhes..in_batches
são diferentes (observe que usei o
:of
ao invés de
:batch_size
Enfim, espero que tenha curtido mais essa curiosidade.
É isso! Até a próxima! 😉