Como Encontrar e Excluir Registros Órfãos com Ruby on Rails

Digamos que você tenha um aplicativo onde um usuário pode se inscrever em um
canal . Com associações ActiveRecord, seria algo assim
:

# app/models/subscription.rb
class Subscription < ActiveRecord::Base
belongs_to
:channel
belongs_to
:user
end

# app/models/user.rb
class User < ActiveRecord::Base
has_many
:subscriptions
has_many
:users, through: :subscriptions
end

# app/models/channel.rb
class Channel < ActiveRecord::Base
has_many
:subscriptions
has_many
:users, through: :subscriptions
end

Infelizmente, alguém se esqueceu de adicionar dependent: :destroyao
has_many :subscriptions. Quando um usuário ou canal foi excluído, uma
assinatura órfã foi deixada para trás.

Esse problema foi corrigido por dependent: :destroy, mas ainda havia um
grande número de registros órfãos remanescentes.

Existem três maneiras de remover os registros órfãos.

Abordagem # 1 – Ruim

Subscription.find_each do |subscription|
if subscription.channel.nil? || subscription.user.nil?
subscription
.destroy
end
end

Isso executa uma consulta SQL separada para cada registro, verifica se ele é
órfão e o destrói se estiver.

Abordagem # 2 – Melhor, mas ainda muito ruim

Subscription.all.each do |subscription|
if subscription.channel.nil? || subscription.user.nil?
subscription
.destroy
end
end

Isso carrega todos os registros na memória e, em seguida, itera sobre eles
executando a mesma verificação acima.

Abordagem # 3 – Boa

Subscription.where([
"user_id NOT IN (?) OR channel_id NOT IN (?)",
User.pluck("id"),
Channel.pluck("id")
]).destroy_all

Essa abordagem primeiro obtém os IDs de todos os usuários e canais e, em seguida,
executa uma consulta para encontrar todas as assinaturas que não pertencem a
um usuário ou a uma consulta.

Benchmarks

Vamos dar uma olhada em quanto tempo leva em cada caso.

Quando você executa em 2596 assinaturas, das quais 1.058 são órfãs,
você obtém:

          user       system     total    real
bad
3.020000 0.160000 3.180000 ( 4.058246)
better
2.950000 0.170000 3.120000 ( 3.982329)
good
0.010000 0.000000 0.010000 ( 0.030346)

Mesmo que o número de assinaturas seja reduzido, ele permanece mais rápido do que , porque executa menos consultas SQL.
Subscription.all.each
Subscriptions.find_each

Com 10 assinaturas, os resultados foram

          user       system     total    real
bad
0.010000 0.000000 0.010000 ( 0.022374)
beter
0.010000 0.010000 0.020000 ( 0.017584)
good
0.010000 0.000000 0.010000 ( 0.014330)

Conclusão

A abordagem # 3 é 134 vezes mais rápida do que a abordagem # 1.

Embora a diferença de tempo em nosso cenário fosse de apenas 4 segundos, poderia
ser horas para milhões de registros.

Isso demonstra como a refatoração é importante para o
processo de desenvolvimento . Refatorar seu código aumenta sua compreensão das
tecnologias com as quais você trabalha. Ele também ilustra como é importante
minimizar as consultas SQL realizadas em seu aplicativo da web. Se esta
for uma ação iniciada pelo usuário, a diferença no tempo da solicitação
seria 4,02 segundos vs. 30 ms.