Substituindo um Rails default_scope

155

Se eu tiver um modelo ActiveRecord :: Base com um escopo padrão:

class Foo < ActiveRecord::Base

  default_scope :conditions => ["bar = ?",bar]

end

Existe alguma maneira de fazer um Foo.find sem usar as default_scopecondições? Em outras palavras, você pode substituir um escopo padrão?

Eu teria pensado que o uso de 'default' no nome sugeriria que fosse substituível, caso contrário, seria chamado de algo como global_scope, certo?

Gareth
fonte

Respostas:

151

Resposta curta: Não use a default_scopemenos que você realmente precise. Você provavelmente estará melhor com escopos nomeados. Com isso dito, você pode with_exclusive_scopesubstituir o escopo padrão, se necessário.

Dê uma olhada nesta pergunta para obter mais detalhes.

Pär Wieslander
fonte
Obrigado pelo link para a pergunta anterior
Gareth
10
> Não use default_scope a menos que você realmente precise. Um excelente conselho! Obrigado!
Install1 de
2
Tão verdade. Usar default_scopepode parecer uma boa ideia, mas provavelmente causará várias dores de cabeça durante a vida útil do seu aplicativo.
thomax
1
Você está exagerando um pouco senhor. default_scopeé uma excelente ferramenta e há situações em que você poderia outra maneira, mas default_scopeé a coisa certa a fazer. Por exemplo, quando você tem um Productmodelo que tem um inactivesinalizador, definir a default_scope { where inactive: false }é a melhor coisa a fazer, uma vez que 99% ou casos você não deseja exibir um produto inativo. Depois, basta chamar unscopedos casos restantes de 1%, que provavelmente é um painel do administrador.
precisa saber é
215

No Rails 3:

foos = Foo.unscoped.where(:baz => baz)
Vincent
fonte
58
Isso tem um efeito colateral, se Publicar has_many Comment, Post.first.comments.unscoped retornar TODOS os comentários.
Enrico Carlesso
3
Isso realmente me ferrou por um tempo. Especialmente se você acabar colocando isso em um método de classe como: def self.random; unscoped.order('rand()'); endunscoped remove ALL sql antes dele, não apenas o que está listado em default_scope. Embora tecnicamente uma resposta correta, tenha cuidado usandounstopped
Schneems
7
AVISO! Unscoped NÃO remove apenas o default_scope, já foi dito em outro comentário, mas pode realmente atrapalhar as coisas.
dsimard
15
Uma boa regra geral é somente unscopedquando ele pode seguir diretamente um modelo, por exemplo, Foo.unscoped.blah()está ok, mas nunca Foo.blah().unscoped.
22313 Grant Birchmeier
stackoverflow.com/questions/1834159/... funciona em torno o efeito colateral mencionado por Enrico
wbharding
107

Se tudo que você precisa é mudar a ordem definida em default_scope, você pode usar o reordermétodo .

class Foo < ActiveRecord::Base
  default_scope order('created_at desc')
end

Foo.reorder('created_at asc')

executa o seguinte SQL:

SELECT * FROM "foos" ORDER BY created_at asc
GuiGS
fonte
3
Dica: defina um escopo como scope :without_default_order, -> { reorder("") }e você pode fazer coisas como: Foo.without_default_order.order("created_at ASC")Em algumas situações, ele lê melhor (talvez não seja exatamente essa situação, mas eu tive uma).
Henrik N
Reordenar fez isso por mim. Muito obrigado!
Andre Zimpel
49

Como 4.1você pode usar ActiveRecord::QueryMethods#unscopepara combater o escopo padrão:

class User < ActiveRecord::Base
  default_scope { where tester: false }
  scope :testers, -> { unscope(:where).where tester: true }
  scope :with_testers, -> { unscope(:where).where tester: [true, false] }
  # ...
end

Atualmente, é possível unscopecoisas como::where, :select, :group, :order, :lock, :limit, :offset, :joins, :includes, :from, :readonly, :having .

Mas ainda assim evite usar default_scopese puder . É para o seu próprio bem.

jibiel
fonte
Essa resposta deve ser maior
Stephen Corwin
5

O default_scope do Rails 3 não parece ser substituído, como ocorreu no Rails 2.

por exemplo

class Foo < ActiveRecord::Base
  belongs_to :bar
  default_scope :order=>"created_at desc"
end

class Bar < ActiveRecord::Base
  has_many :foos
end

> Bar.foos
  SELECT * from Foo where bar_id = 2 order by "created_at desc";
> Bar.unscoped.foos
  SELECT * from Foo;  (WRONG!  removes the "has" relationship)
> Bar.foos( :order=>"created_at asc" )  # trying to override ordering
  SELECT * from Foo where bar_id = 2 order by "created_at desc, created_at asc"

No meu aplicativo, usando o PostgreSQL, a ordem no escopo padrão WINS. Estou removendo todos os meus default_scopes e codificando-os explicitamente em todos os lugares.

Pitfall Rails3!

vanboom
fonte
1
Você tem que usarBar.foos.reorder(:created_at => :asc)
Ivan Stana 23/02
5

Com o Rails 3+, você pode usar uma combinação de sem escopo e mesclagem:

# model User has a default scope
query = User.where(email: "[email protected]")

# get rid of default scope and then merge the conditions
query = query.unscoped.merge(query)
santervo
fonte
Isso também funcionou para mim, chamar primeiro sem escopo (Rails 4.2):User.unscoped.where(email: "[email protected]")
Nick
3

No Rails 5.1 ou posterior (e talvez antes, mas já testei que funciona no 5.1), é possível remover uma coluna específica, que é a solução ideal para remover a default_scopede uma maneira que possa ser usada dentro de um escopo nomeado. No caso dos PO default_scope,

Foo.unscope(where: :bar)

Ou

scope :not_default, -> { unscope(where: :bar) }
Foo.not_default

Ambos resultarão em uma consulta sql que não aplica o escopo original, mas aplica quaisquer outras condições que sejam mescladas no arel.

wbharding
fonte
2

Bem, você sempre pode usar o favorito dos velhos tempos find_by_sqlcom a consulta completa. Por exemplo: Model.find_by_sql ("SELECT * FROM models WHERE id = 123")

Ady Rosen
fonte