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?

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.


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.


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.


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.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.