Remplacer un Rails default_scope


156

Si j'ai un modèle ActiveRecord :: Base avec une portée par défaut:

class Foo < ActiveRecord::Base

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

end

Existe-t-il un moyen de faire un Foo.find sans utiliser les default_scopeconditions? En d'autres termes, pouvez-vous remplacer une étendue par défaut?

J'aurais pensé que l'utilisation de «default» dans le nom suggérerait qu'il était remplaçable, sinon cela s'appellerait quelque chose comme global_scope, non?


Réponses:


151

Réponse courte: ne pas utiliser default_scopesauf si c'est vraiment nécessaire. Vous serez probablement mieux avec des portées nommées. Cela dit, vous pouvez utiliser with_exclusive_scopepour remplacer la portée par défaut si vous en avez besoin.

Jetez un œil à cette question pour plus de détails.


Merci pour le lien vers la question précédente
— Gareth

10
> N'utilisez pas default_scope sauf si vous y êtes vraiment obligé. Un excellent conseil! Je vous remercie!
— installero

2
Tellement vrai. L'utilisation default_scopepeut sembler une bonne idée, mais causera probablement plusieurs maux de tête pendant la durée de vie de votre application.
— thomax


1
Vous exagérez un peu monsieur. default_scopeest un excellent outil et il y a des situations où vous pourriez faire autrement mais c'est default_scopejuste la bonne chose à faire. Par exemple, lorsque vous avez un Productmodèle avec un inactiveindicateur, définir a default_scope { where inactive: false }est la meilleure chose à faire, puisque 99% ou des cas, vous ne voudrez pas afficher un produit inactif. Ensuite, vous appelez simplement unscopedles 1% restants, ce qui est probablement un panneau d'administration.
— pedrozath

215

Dans Rails 3:

foos = Foo.unscoped.where(:baz => baz)

58
Cela a un effet secondaire, si Post has_many Comment, Post.first.comments.unscoped renvoie TOUS les commentaires.
— Enrico Carlesso

3
Cela m'a vraiment foutu en l'air pendant un moment. Surtout si vous finissez par mettre cela dans une méthode de classe comme: def self.random; unscoped.order('rand()'); endunscoped supprime TOUT SQL avant lui, pas seulement ce qui est répertorié sous default_scope. Bien que techniquement une réponse correcte, soyez prudent en utilisantunstopped
— Schneems

7
AVERTISSEMENT! Unscoped ne supprime PAS uniquement le default_scope, cela a déjà été dit dans un autre commentaire mais cela peut vraiment gâcher les choses.
— dsimard le

15
Une bonne règle de base est de seulement unscopedquand il peut suivre directement un modèle, par exemple Foo.unscoped.blah()c'est ok mais jamais Foo.blah().unscoped.
— Grant Birchmeier

stackoverflow.com/questions/1834159/… fonctionne autour de l'effet secondaire mentionné par Enrico
— wbharding

107

Si tout ce dont vous avez besoin est de modifier l'ordre défini dans default_scope, vous pouvez utiliser la reorderméthode .

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

Foo.reorder('created_at asc')

exécute le SQL suivant:

SELECT * FROM "foos" ORDER BY created_at asc

3
Astuce: définissez une portée comme scope :without_default_order, -> { reorder("") }et vous pouvez faire des choses comme Foo.without_default_order.order("created_at ASC")Dans certaines situations, elle se lit mieux (peut-être pas cette situation exacte, mais j'en avais une).
— Henrik N

Reorder l'a fait pour moi. Merci beaucoup!
— Andre Zimpel

49

Puisque 4.1vous pouvez utiliser ActiveRecord::QueryMethods#unscopepour combattre la portée par défaut:

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

Il est actuellement possible de unscopechoses comme: :where, :select, :group, :order, :lock, :limit, :offset, :joins, :includes, :from, :readonly, :having.

Mais évitezdefault_scope toujours d' utiliser ou si vous le pouvez . C'est pour ton bien.


Cette réponse devrait être plus élevée
— Stephen Corwin


5

Rails 3 default_scope ne semble pas être remplacé comme il l'a fait dans Rails 2.

par exemple

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"

Dans mon application, en utilisant PostgreSQL, le classement dans la portée par défaut WINS. Je supprime tous mes default_scopes et je le code explicitement partout.

Pitfall Rails3!


1
Vous devez utiliserBar.foos.reorder(:created_at => :asc)
— Ivan Stana

5

Avec Rails 3+, vous pouvez utiliser une combinaison de non-cadré et de fusion:

# model User has a default scope
query = User.where(email: "foo@example.com")

# get rid of default scope and then merge the conditions
query = query.unscoped.merge(query)

Cela a également fonctionné pour moi, pour appeler les unscoped en premier (Rails 4.2):User.unscoped.where(email: "foo@example.com")
— Nick

4

Sur Rails 5.1+ (et peut-être plus tôt, mais j'ai testé cela fonctionne sur 5.1), il est possible de délimiter une colonne spécifique, ce qui à mon humble avis est la solution idéale pour supprimer un default_scoped'une manière qui peut être utilisée dans une portée nommée. Dans le cas des PO default_scope,

Foo.unscope(where: :bar)

Ou

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

Les deux aboutiront à une requête SQL qui n'applique pas la portée d'origine, mais qui applique toutes les autres conditions fusionnées dans l'arel.


2

Eh bien, vous pouvez toujours utiliser l'ancien favori find_by_sqlavec la requête complète. Par exemple: Model.find_by_sql ("SELECT * FROM models WHERE id = 123")

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.