Por que o uso dos trilhos default_scope geralmente não é recomendado?

126

Em toda parte nas pessoas internet mencionar que usando os trilhos default_scopeé uma má idéia, e os top hits para default_scopeem stackoverflow são sobre como substituí-lo. Isso parece confuso e merece uma pergunta explícita (eu acho).

Então: por que os trilhos são default_scoperecomendados?

wrtsprt
fonte

Respostas:

192

Problema 1

Vamos considerar o exemplo básico:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
end

A motivação para fazer o padrão published: trueé garantir que você seja explícito quando desejar mostrar postagens (privadas) não publicadas. Por enquanto, tudo bem.

2.1.1 :001 > Post.all
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't'

Bem, isso é praticamente o que esperamos. Agora vamos tentar:

2.1.1 :004 > Post.new
 => #<Post id: nil, title: nil, published: true, created_at: nil, updated_at: nil>

E aí temos o primeiro grande problema com o escopo padrão:

=> default_scope afetará a inicialização do modelo

Em uma instância recém-criada de um modelo desse tipo, default_scopeisso será refletido. Portanto, embora você queira ter certeza de não listar postagens não publicadas por acaso, agora você está criando postagens publicadas por padrão.

Problema 2

Considere um exemplo mais elaborado:

class Post < ActiveRecord::Base
  default_scope { where(published: true) }
  belongs_to :user
end 

class User < ActiveRecord::Base
  has_many :posts
end

Permite obter as primeiras postagens de usuários:

2.1.1 :001 > User.first.posts
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."published" = 't' AND "posts"."user_id" = ?  [["user_id", 1]]

Isso parece esperado (certifique-se de rolar todo o caminho para a direita para ver a parte sobre o user_id).

Agora queremos obter a lista de todas as postagens - inclusive não publicadas - para a visualização do usuário conectado. Você perceberá que precisa "substituir" ou "desfazer" o efeito de default_scope. Depois de um rápido google, você provavelmente descobrirá unscoped. Veja o que acontece a seguir:

2.1.1 :002 > User.first.posts.unscoped
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"

=> Sem escopo remove TODOS os escopos que normalmente se aplicam ao seu select, incluindo (mas não limitado a) associações.

Existem várias maneiras de substituir os diferentes efeitos do default_scope. Conseguir isso direito fica complicado muito rapidamente e eu diria que não usar o default_scopeem primeiro lugar, seria uma escolha mais segura.

wrtsprt
fonte
2
Para empilhar: a única vez que achei útil o default_scope é quando você deseja carregar com urgência algumas associações por padrão. default_scope {eager_load ([: categoria,: comentários])}. Contudo!!! Se você estiver fazendo uma consulta de contagem nesse modelo, como Product.count, ele irá associar ansiosamente a carga de todos os produtos. E se você tiver 50K registros, sua consulta de contagem passou de 15ms a 500ms, porque enquanto tudo o que você quer é contagem, seu default_scope deixará de se juntar a todo o resto.
22715 konung
16
Para mim, parece que esse problema está relacionado ao unscopedinvés do default_scopeproblema # 2
Capitão Fogetti
4
@CaptainFogetti Indeed. Eu ainda acho que é uma boa idéia apresentar as desvantagens do não-escopo como uma possível desvantagem do default_scope. Na maioria dos casos não triviais, usar default_scope levará a você a necessidade de usar sem escopo. Esta é uma advertência de segundo grau (na falta de um termo melhor), que é fácil de perder ao pesquisar um método.
wrtsprt
1
O problema com o caso de uso em sua resposta é que existem muitos casos em que você deseja encontrar postagens não publicadas. Na verdade, eu argumentaria que encontrar posts publicados é um caso especial. A única vez que você deseja postagens publicadas é quando alguém está visualizando a página pública. Mas há muitas vezes em que você deseja ver postagens não publicadas.
B Seven
3
Eu acho que um bom uso de default_scopeé quando você quer algo a ser resolvido: default_scope { order(:name) }.
user2985898
9

Outro motivo para não usar default_scopeé quando você está excluindo uma instância de um modelo que tem uma relação de 1 para muitos com o default_scopemodelo

Considere, por exemplo:

    class User < ActiveRecord::Base
      has_many :posts, dependent: :destroy
    end 

    class Post < ActiveRecord::Base
      default_scope { where(published: true) }
      belongs_to :user
    end

A chamada user.destroyexcluirá todas as postagens que são published, mas não excluirá as postagens que são unpublished. Portanto, o banco de dados lançará uma violação de chave estrangeira porque contém registros que referenciam o usuário que você deseja remover.

Koekenbakker28
fonte
6

O default_scope é frequentemente recomendado, porque às vezes é usado incorretamente para limitar o conjunto de resultados. Um bom uso do default_scope é solicitar o conjunto de resultados.

Eu ficaria longe de usar o wheredefault_scope e criaria um escopo para isso.

nahankid
fonte
1
O segundo problema "Sem escopo remove TODOS os escopos que normalmente se aplicam ao seu select, incluindo (mas não limitado a) associações" ainda existe, mesmo que o default_scopeúnico contenha order. Esse comportamento unscopedé bastante inesperado.
Zack Xu
1

Para mim não é uma má idéia, mas deve ser usada com cautela !. Há um caso em que eu sempre quis ocultar certos registros quando um campo é definido.

  1. De preferência, o valor default_scopedeve corresponder ao valor padrão do banco de dados (por exemplo { where(hidden_id: nil) }:)
  2. Quando você tem certeza absoluta de que deseja mostrar esses registros, sempre existe o unscopedmétodo que evitarádefault_scope

Então, vai depender e as reais necessidades.

Sposmen
fonte
0

Eu só acho default_scopeque é útil apenas na ordenação de alguns parâmetros para estar ascou descordem em todas as situações. Caso contrário, eu evito isso como uma praga

Moses Liao GZ
fonte