Pourquoi l'utilisation des rails default_scope est-elle souvent déconseillée?

127

Partout sur le peuple Internet mentionnent que l' utilisation des rails default_scopeest une mauvaise idée, et les premiers résultats pourdefault_scope le stackoverflow sont sur la façon de le remplacer. Cela semble foiré et mérite une question explicite (je pense).

Alors: pourquoi est-il default_scoperecommandé d' utiliser les rails ?

wrtsprt
la source

Réponses:

192

Problème 1

Prenons l'exemple de base:

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

La motivation pour faire la valeur par défaut published: true, peut être de vous assurer que vous devez être explicite lorsque vous souhaitez afficher des messages non publiés (privés). Jusqu'ici tout va bien.

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

Eh bien, c'est à peu près ce à quoi nous nous attendons. Essayons maintenant:

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

Et là, nous avons le premier gros problème avec la portée par défaut:

=> default_scope affectera l'initialisation de votre modèle

Dans une instance nouvellement créée d'un tel modèle, le default_scopesera reflété. Ainsi, bien que vous ayez peut-être voulu être sûr de ne pas lister les articles non publiés par hasard, vous créez maintenant des articles publiés par défaut.

Problème 2

Prenons un exemple plus élaboré:

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

class User < ActiveRecord::Base
  has_many :posts
end

Permet d'obtenir les premiers messages des utilisateurs:

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]]

Cela ressemble à ce que vous attendez (assurez-vous de faire défiler tout le chemin vers la droite pour voir la partie concernant le user_id).

Maintenant, nous voulons obtenir la liste de tous les messages - non publiés inclus - par exemple pour la vue de l'utilisateur connecté. Vous réaliserez que vous devez «écraser» ou «annuler» l'effet de default_scope. Après un rapide google, vous découvrirez probablement unscoped. Voyez ce qui se passe ensuite:

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

=> Unscoped supprime TOUTES les étendues qui pourraient normalement s'appliquer à votre sélection, y compris (mais sans s'y limiter) les associations.

Il existe plusieurs façons d'écraser les différents effets du default_scope. Obtenir ce droit se complique très rapidement et je dirais que ne pas utiliser le, default_scopeen premier lieu, serait un choix plus sûr.

wrtsprt
la source
2
Pour empiler: la seule fois où j'ai trouvé default_scope utile, c'est lorsque vous voulez absolument charger certaines associations par défaut. default_scope {eager_load ([: category,: comments])}. Toutefois!!! Si vous effectuez une requête de comptage sur ce modèle comme Product.count, les associations eager_load pour tous les produits. Et si vous avez 50K enregistrements, votre requête de comptage est simplement passée de 15 ms à 500 ms, car bien que tout ce que vous voulez, c'est compter, votre default_scope laissera rejoindre tout le reste.
konung
16
Pour moi, il semble que le problème soit unscopedplutôt que default_scopedans le problème n ° 2
Captain Fogetti
4
@CaptainFogetti En effet. Je pense toujours que c'est une bonne idée de présenter les inconvénients de unscoped comme un inconvénient possible de default_scope. Dans la plupart des cas non triviaux, l'utilisation de default_scope vous obligera à utiliser unscoped. Il s'agit d'une mise en garde de deuxième degré (en l'absence d'un meilleur terme), qu'il est facile de manquer lors de la recherche d'une méthode.
wrtsprt le
1
Le problème avec le cas d'utilisation dans votre réponse est qu'il existe de nombreux cas où vous souhaitez trouver des articles non publiés. En fait, je dirais que trouver des articles publiés est un cas particulier. Le seul moment où vous souhaitez publier des articles, c'est lorsque quelqu'un consulte la page publique. Mais il arrive souvent que vous souhaitiez voir des articles non publiés.
B Seven
3
Je suppose une bonne utilisation de default_scopec'est quand vous voulez quelque chose à trier: default_scope { order(:name) }.
user2985898
9

Une autre raison de ne pas utiliser default_scopeest lorsque vous supprimez une instance d'un modèle qui a une relation 1 à plusieurs avec le default_scopemodèle

Considérez par exemple:

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

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

L'appel user.destroysupprimera tous les messages published, mais ne supprimera pas les messages qui le sont unpublished. Par conséquent, la base de données lèvera une violation de clé étrangère car elle contient des enregistrements faisant référence à l'utilisateur que vous souhaitez supprimer.

Koekenbakker28
la source
6

default_scope est souvent déconseillé car il est parfois utilisé de manière incorrecte pour limiter le jeu de résultats. Une bonne utilisation de default_scope est d'ordonner le jeu de résultats.

Je resterais loin d'utiliser wheredans default_scope et créerais plutôt une portée pour cela.

Nahankid
la source
1
Le deuxième problème «Unscoped supprime TOUTES les étendues qui pourraient normalement s'appliquer à votre sélection, y compris (mais sans s'y limiter) les associations» existe toujours même si la default_scopeseule contient order. Ce comportement de unscopedest assez inattendu.
Zack Xu
1

Pour moi n'est pas une mauvaise idée mais doit être utilisé avec prudence !. Il y a un cas où j'ai toujours voulu masquer certains enregistrements lorsqu'un champ est défini.

  1. De préférence le default_scope doit correspondre à la valeur par défaut de la base de données (par exemple:{ where(hidden_id: nil) } )
  2. Lorsque vous êtes sûr de vouloir montrer ces enregistrements, il y a toujours unscoped méthode qui vous éviteradefault_scope

Cela dépendra donc des besoins réels.

Sposmen
la source
0

Je ne trouve default_scopeutile que pour ordonner que certains paramètres soient dans ascou en descordre dans toutes les situations. Sinon je l'évite comme la peste

Moses Liao GZ
la source