Tại sao sử dụng rails default_scope thường đề xuất chống lại?


125

Ở khắp mọi nơi trên các người internet đề cập rằng việc sử dụng các đường ray default_scopelà một ý tưởng tồi, và hit hàng đầu cho default_scopetrên stackoverflow là về cách ghi đè lên nó. Điều này cảm thấy rối tung lên, và xứng đáng với một câu hỏi rõ ràng (tôi nghĩ).

Vì vậy: tại sao sử dụng đường ray được default_scopeđề nghị chống lại?

Câu trả lời:


192

Vấn đề 1

Hãy xem xét ví dụ cơ bản:

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

Động lực để làm mặc định published: true, có thể là để đảm bảo bạn phải bùng nổ khi muốn hiển thị các bài đăng (riêng tư) chưa được công bố. Càng xa càng tốt.

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

Vâng, đây là khá nhiều những gì chúng ta mong đợi. Bây giờ hãy thử:

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

Và ở đó chúng ta có vấn đề lớn đầu tiên với phạm vi mặc định:

=> default_scope sẽ ảnh hưởng đến việc khởi tạo mô hình của bạn

Trong một ví dụ mới được tạo ra của một mô hình như vậy, default_scopesẽ được phản ánh. Vì vậy, trong khi bạn có thể muốn chắc chắn không liệt kê các bài đăng chưa được công bố một cách tình cờ, thì bây giờ bạn đang tạo các bài đăng được xuất bản theo mặc định.

Vấn đề 2

Hãy xem xét một ví dụ phức tạp hơn:

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

class User < ActiveRecord::Base
  has_many :posts
end

Cho phép nhận bài đăng của người dùng đầu tiên:

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

Điều này trông giống như mong đợi (đảm bảo cuộn toàn bộ sang bên phải để xem phần về user_id).

Bây giờ chúng tôi muốn lấy danh sách tất cả các bài đăng - chưa được công bố - nói về chế độ xem của người dùng đã đăng nhập. Bạn sẽ nhận ra rằng bạn phải 'ghi đè' hoặc 'hoàn tác' hiệu ứng của default_scope. Sau khi google nhanh, bạn có thể sẽ tìm hiểu về unscoped. Xem những gì xảy ra tiếp theo:

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

=> Unscoped loại bỏ TẤT CẢ phạm vi thường có thể áp dụng cho lựa chọn của bạn, bao gồm (nhưng không giới hạn) các liên kết.

Có nhiều cách để ghi đè lên các hiệu ứng khác nhau của default_scope. Nhận quyền đó trở nên phức tạp rất nhanh và tôi sẽ tranh luận rằng không sử dụng ngay default_scopetừ đầu, sẽ là lựa chọn an toàn hơn.


2
Để tiếp tục: lần duy nhất tôi thấy default_scope hữu ích là khi bạn hoàn toàn muốn háo hức tải một số liên kết theo mặc định. default_scope {eager_load ([: danh mục ,: bình luận])}. Tuy nhiên!!! Nếu bạn đang thực hiện truy vấn đếm trên mô hình này như Product.count, nó sẽ háo hức tải các liên kết cho tất cả các sản phẩm. Và nếu bạn có các bản ghi 50K, truy vấn đếm của bạn chỉ tăng từ 15ms đến 500ms, bởi vì trong khi tất cả những gì bạn muốn là đếm, default_scope của bạn sẽ rời khỏi mọi thứ khác.
konung

16
Đối với tôi có vẻ như vấn đề xảy ra unscopedthay vì default_scopetrong vấn đề # 2
Thuyền trưởng Fogetti

4
@CaptainFogetti Thật vậy. Tôi vẫn nghĩ rằng đó là một ý tưởng tốt để trình bày những nhược điểm của không được kiểm soát là một nhược điểm có thể có của default_scope. Trong hầu hết các trường hợp không tầm thường khi sử dụng default_scope sẽ dẫn đến bạn cần sử dụng unscoped. Đây là một cảnh báo mức độ thứ hai (thiếu một thuật ngữ tốt hơn), rất dễ bỏ lỡ khi nghiên cứu một phương pháp.
wrtsprt

1
Vấn đề với trường hợp sử dụng trong câu trả lời của bạn là có nhiều trường hợp khi bạn muốn tìm các bài viết chưa được công bố. Trong thực tế, tôi sẽ lập luận rằng việc tìm các bài đăng được công bố là một trường hợp đặc biệt. Lần duy nhất bạn muốn đăng bài viết là khi ai đó đang xem trang công khai. Nhưng có nhiều lúc bạn muốn xem những bài viết chưa được công bố.
B Bảy

3
Tôi đoán một cách sử dụng tốt default_scopelà khi bạn muốn sắp xếp thứ gì đó : default_scope { order(:name) }.
dùng2985898

9

Một lý do khác để không sử dụng default_scopelà khi bạn xóa một thể hiện của một mô hình có mối quan hệ 1 đến nhiều với default_scopemô hình

Xem xét ví dụ:

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

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

Gọi user.destroysẽ xóa tất cả các bài viết published, nhưng nó sẽ không xóa các bài viết đó unpublished. Do đó, cơ sở dữ liệu sẽ vi phạm khóa ngoại vì nó chứa các bản ghi tham chiếu đến người dùng mà bạn muốn xóa.


6

default_scope thường được đề xuất vì đôi khi nó được sử dụng không chính xác để giới hạn tập kết quả. Một cách sử dụng tốt default_scope là đặt thứ tự tập kết quả.

Tôi sẽ tránh sử dụng wheretrong default_scope và thay vào đó tạo một phạm vi cho điều đó.


1
Vấn đề thứ hai "Unscoped loại bỏ TẤT CẢ phạm vi thường có thể áp dụng cho lựa chọn của bạn, bao gồm (nhưng không giới hạn) các liên kết" vẫn tồn tại ngay cả khi default_scopechỉ chứa order. Hành vi unscopednày là khá bất ngờ.
Zack Xu

1

Đối với tôi không phải là một ý tưởng tồi nhưng phải thận trọng! Có một trường hợp tôi luôn muốn ẩn các bản ghi nhất định khi một trường được đặt.

  1. Tốt nhất là default_scopephải khớp với giá trị mặc định DB (ví dụ { where(hidden_id: nil) }:)
  2. Khi bạn hoàn toàn chắc chắn rằng bạn muốn hiển thị những hồ sơ đó, luôn có unscopedphương pháp sẽ tránhdefault_scope

Vì vậy, nó sẽ phụ thuộc và nhu cầu thực sự.


0

Tôi chỉ thấy default_scopehữu ích khi sắp xếp một số tham số trong aschoặc descsắp xếp trong mọi tình huống. Nếu không thì tôi tránh nó như bệnh dịch hạch

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.