Почему часто не рекомендуется использовать направляющие default_scope?


127

Повсюду на тех интернет - людей , говоря , что использование рельсов default_scopeявляется плохой идеей, и топ хиты для default_scopeна StackOverflow это о том , как переписать его. Это кажется запутанным и заслуживает четкого вопроса (я думаю).

Итак: почему не default_scopeрекомендуется использовать рельсы ?

Ответы:


192

Проблема 1

Давайте рассмотрим основной пример:

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

Мотивация использовать значение по умолчанию published: trueможет заключаться в том, чтобы убедиться, что вы должны быть явным, когда хотите показывать неопубликованные (частные) сообщения. Все идет нормально.

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

Что ж, это в значительной степени то, что мы ожидаем. Теперь попробуем:

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

И вот у нас есть первая большая проблема с областью действия по умолчанию:

=> default_scope повлияет на инициализацию вашей модели

Во вновь созданном экземпляре такой модели default_scopeбудет отражен. Поэтому, хотя вы, возможно, хотели быть уверены, что случайно не перечислили неопубликованные сообщения, теперь вы создаете опубликованные по умолчанию.

Проблема 2

Рассмотрим более сложный пример:

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

class User < ActiveRecord::Base
  has_many :posts
end

Получим первые сообщения пользователей:

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

Это похоже на ожидаемое (обязательно прокрутите страницу до конца вправо, чтобы увидеть часть о user_id).

Теперь мы хотим получить список всех сообщений - включая неопубликованные - скажем, для просмотра вошедшего в систему пользователя. Вы поймете, что вам нужно «перезаписать» или «отменить» эффект default_scope. После быстрого поиска в Google вы, вероятно, узнаете о unscoped. Посмотрим, что будет дальше:

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

=> Без области действия удаляет ВСЕ области, которые обычно могут применяться к вашему выбору, включая (но не ограничиваясь) ассоциации.

Есть несколько способов перезаписать различные эффекты default_scope. Получение этого права очень быстро усложняется, и я бы сказал, что не использовать default_scopeвообще, было бы более безопасным выбором.


2
Напомним: я нашел значение default_scope полезным только тогда, когда вы абсолютно хотите загрузить некоторые ассоциации по умолчанию. default_scope {eager_load ([: категория,: комментарии])}. Тем не мение!!! Если вы выполняете запрос count для этой модели, например Product.count, он будет связывать eager_load для всех продуктов. И если у вас есть 50K записей, ваш запрос count просто изменился с 15 мс до 500 мс, потому что, хотя все, что вам нужно, это счет, ваш default_scope оставит присоединиться ко всему остальному.
konung

16
Мне кажется , что проблема с unscopedвместо default_scopeв проблеме # 2
Капитан Fogetti

4
@CaptainFogetti Верно. Я все еще считаю хорошей идеей представить недостатки незаданной области как возможный недостаток default_scope. В большинстве нетривиальных случаев использование default_scope приведет к тому, что вам понадобится использовать unscoped. Это предостережение второй степени (за неимением лучшего термина), которое легко упустить при исследовании метода.
wrtsprt

1
Проблема с вариантом использования в вашем ответе заключается в том, что есть много случаев, когда вы хотите найти неопубликованные сообщения. На самом деле, я бы сказал, что поиск опубликованных сообщений - это особый случай. Единственный раз, когда вы хотите публиковать сообщения, - это когда кто-то просматривает общедоступную страницу. Но бывает много раз, когда вы хотите увидеть неопубликованные сообщения.
B Seven

3
Я думаю , хорошее использование default_scope, когда вы хотите что - то для сортировки: default_scope { order(:name) }.
user2985898

9

Еще одна причина не использовать default_scope- это когда вы удаляете экземпляр модели, который имеет отношение 1 ко многим с default_scopeмоделью.

Рассмотрим, например:

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

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

Вызов user.destroyудалит все существующие сообщения published, но не удалит сообщения, которые есть unpublished. Следовательно, база данных вызовет нарушение внешнего ключа, поскольку она содержит записи, которые ссылаются на пользователя, которого вы хотите удалить.


6

default_scope часто не рекомендуется, потому что он иногда неправильно используется для ограничения набора результатов. Хорошее использование default_scope - это упорядочить набор результатов.

Я бы не стал использовать wheredefault_scope и скорее создал бы для этого область.


1
Вторая проблема «Без области действия удаляет ВСЕ области, которые обычно могут применяться к вашему выбору, включая (но не ограничиваясь) ассоциации», все еще существует, даже если default_scopeтолько содержит order. Такое поведение unscopedдовольно неожиданно.
Zack Xu

1

Для меня это не плохая идея , но следует использовать с осторожностью !. Есть случай, когда я всегда хотел скрыть определенные записи, когда поле установлено.

  1. Предпочтительно, default_scopeдолжен соответствовать значению БД по умолчанию (например: { where(hidden_id: nil) })
  2. Когда вы полностью уверены, что хотите показать эти записи, всегда есть unscopedспособ избежать вашегоdefault_scope

Так что это будет зависеть и от реальных потребностей.


0

Я только найти , default_scopeчтобы быть полезным только в заказе некоторых параметров , чтобы быть в ascили descпорядок во всей ситуации. В противном случае я избегаю этого как чумы

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.