Dlaczego używanie szyn default_scope jest często odradzane?

126

Wszędzie na tych ludzi internetowych wspomnieć, że za pomocą szyn default_scopejest to zły pomysł, a hity dla default_scopena stackoverflow są o tym, jak go zastąpić. To wydaje się pomieszane i zasługuje na wyraźne pytanie (myślę).

A więc: dlaczego default_scopezaleca się stosowanie szyn ?

wrtsprt
źródło

Odpowiedzi:

192

Zadanie 1

Rozważmy podstawowy przykład:

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

Motywacją do ustawienia jako domyślnego published: truemoże być upewnienie się, że musisz być jawny, gdy chcesz pokazać niepublikowane (prywatne) posty. Na razie w porządku.

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

Cóż, tego właśnie oczekujemy. Teraz spróbujmy:

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

I tu mamy pierwszy duży problem z domyślnym zakresem:

=> default_scope wpłynie na inicjalizację twojego modelu

W nowo utworzonej instancji takiego modelu default_scopezostanie odzwierciedlone. Więc chociaż możesz chcieć nie wyświetlać przypadkowo niepublikowanych postów, teraz domyślnie tworzysz opublikowane.

Zadanie 2

Rozważmy bardziej rozbudowany przykład:

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

class User < ActiveRecord::Base
  has_many :posts
end

Pozwala uzyskać pierwsze posty użytkowników:

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

Wygląda to zgodnie z oczekiwaniami (pamiętaj, aby przewinąć całą drogę w prawo, aby zobaczyć część dotyczącą user_id).

Teraz chcemy uzyskać listę wszystkich postów - w tym niepublikowanych - powiedzmy dla zalogowanego użytkownika. Zrozumiesz, że musisz „nadpisać” lub „cofnąć” efekt default_scope. Po krótkim wygooglowaniu prawdopodobnie się dowiesz unscoped. Zobacz, co będzie dalej:

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

=> Bez zakresu usuwa WSZYSTKIE zakresy, które normalnie mogą mieć zastosowanie do twojego wyboru, w tym (ale nie tylko) skojarzenia.

Istnieje wiele sposobów nadpisywania różnych efektów pliku default_scope. Uzyskanie tego dobrze komplikuje się bardzo szybko i argumentowałbym, że nie używanie default_scopew pierwszej kolejności byłoby bezpieczniejszym wyborem.

wrtsprt
źródło
2
Do kupienia: jedyny przypadek, w którym uważam, że default_scope jest przydatny, jest wtedy, gdy absolutnie chcesz domyślnie załadować niektóre skojarzenia. default_scope {eager_load ([: category,: comments])}. Jednak!!! Jeśli wykonujesz zapytanie zliczające w tym modelu, takim jak Product.count, będzie to asocjacje eager_load dla wszystkich produktów. A jeśli masz 50K rekordów, twoje zapytanie zliczające właśnie przeszło z 15 ms do 500 ms, ponieważ podczas gdy wszystko, czego chcesz, to zliczanie, twój default_scope zostanie dołączony do wszystkiego innego.
konung
16
Wydaje mi się, że problem tkwi w problemie nr 2, a unscopednie default_scopew problemie nr 2
Kapitan Fogetti
4
@CaptainFogetti Rzeczywiście. Nadal uważam, że dobrym pomysłem jest przedstawienie wady unscoped jako możliwej wady default_scope. W większości nietrywialnych przypadków użycie default_scope spowoduje, że będziesz musiał używać bez zakresu. Jest to ostrzeżenie drugiego stopnia (z braku lepszego określenia), które łatwo przeoczyć badając metodę.
wrtsprt
1
Problem z przypadkiem użycia w Twojej odpowiedzi polega na tym, że istnieje wiele przypadków, w których chcesz znaleźć niepublikowane posty. W rzeczywistości argumentowałbym, że znajdowanie opublikowanych postów jest przypadkiem szczególnym. Publikowane posty chcesz mieć tylko wtedy, gdy ktoś przegląda stronę publiczną. Ale jest wiele razy, kiedy chcesz zobaczyć niepublikowane posty.
B Seven
3
Chyba Zastosowanie Dobro default_scopejest, kiedy chcesz coś być posortowane: default_scope { order(:name) }.
user2985898,
9

Innym powodem, dla którego nie należy używać, default_scopejest usuwanie wystąpienia modelu, który ma relację 1 do wielu z default_scopemodelem

Rozważmy na przykład:

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

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

Dzwonienie user.destroyusunie wszystkie posty, które są published, ale nie usunie postów, które są unpublished. Dlatego baza danych zgłosi naruszenie klucza obcego, ponieważ zawiera rekordy odnoszące się do użytkownika, którego chcesz usunąć.

Koekenbakker28
źródło
6

default_scope jest często zalecany, ponieważ czasami jest nieprawidłowo używany do ograniczania zestawu wyników. Dobrym zastosowaniem default_scope jest uporządkowanie zestawu wyników.

Trzymałbym się z daleka od używania wherew default_scope i raczej utworzyłbym dla tego zakres.

nahankid
źródło
1
Drugi problem „Bez zakresu usuwa WSZYSTKIE zakresy, które normalnie mogą dotyczyć wybranego przez Ciebie, w tym (ale nie tylko) skojarzeń”, nadal istnieje, nawet jeśli default_scopejedyny zawiera order. Takie zachowanie unscopedjest dość nieoczekiwane.
Zack Xu
1

Dla mnie nie jest to zły pomysł, ale należy go używać ostrożnie !. Jest przypadek, w którym zawsze chciałem ukryć pewne rekordy, gdy pole jest ustawione.

  1. Najlepiej, gdy default_scopemusi pasować do domyślnej wartości DB (np . { where(hidden_id: nil) }:)
  2. Kiedy jesteś całkowicie pewien, że chcesz pokazać te rekordy, zawsze istnieje unscopedmetoda, która pozwoli Ci uniknąćdefault_scope

Zależy to więc od rzeczywistych potrzeb.

Sposmen
źródło
0

I tylko znaleźć default_scopesię przydatne tylko w zamawianiu niektóre parametry się w asclub descporządek w całej sytuacji. W przeciwnym razie unikam tego jak zarazy

Moses Liao GZ
źródło