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?

Gareth
la source

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.

Pär Wieslander
la source
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)
Vincent
la source
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
GuiGS
la source
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.

jibiel
la source
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!

vanboom
la source
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: "[email protected]")

# get rid of default scope and then merge the conditions
query = query.unscoped.merge(query)
santervo
la source
Cela a également fonctionné pour moi, pour appeler les unscoped en premier (Rails 4.2):User.unscoped.where(email: "[email protected]")
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.

wbharding
la source
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")

Ady Rosen
la source