Những chú ý khi sử dụng Uniqueness Validations

Ứng dụng Rails của bạn có thể sử dụng validations uniqueness ở một số nơi chính. Việc validation hợp lệ này cung cấp một trải nghiệm cho người dùng khi nhân bản bản ghi được phát hiện nhưng sau đó một chút, không đủ để đảm bảo tính toàn vẹn của dữ liệu.

Điều có thể dễ bị sai

Chúng ta hãy nhìn vào một ví dụ ở class User:

class User
  validates :email, presence: true, uniqueness: true
end

Trong một dữ liệu người dùng, Rails sẽ validate model của bạn bằng cách chạy một truy vấn SELECT để xem có bất kỳ người dùng với email đã tồn tại hay không. Giả sử bản ghi là hợp lệ, Rails sẽ chạy câu lệnh INSERT để thêm người dùng.

Nhưng bạn không chỉ chạy đơn lẻ một WEBrick, đúng không? Không, để tối đa hóa các yêu cầu mỗi phút, bạn đang sử dụng Unicorn trên nhiều Herons dynos, mỗi trang có nhiều quy trình web. Chúng ta hãy nhìn vào những gì sẽ xảy ra nếu chỉ có hai trong số những quy trình này đang cố gắng tạo ra một người dùng có cùng địa chỉ email cùng thời điểm:

Oh. Bây giờ chúng ta đã có một vấn đề. Chúng ta muốn validation tính duy nhất để giữ dữ liệu phù hợp với ý định của chúng ta nhưng nó đã không thành công. Tại sao? Bởi vì chúng ta không bao giờ nói với cơ sở dữ liệu về ý định của chúng ta.

Unique Indexes chính là giải pháp

Hãy đảm bảo rằng cơ sở dữ liệu nằm trong kế hoạch bằng cách nói với nó để tạo ra một ràng buộc duy nhất đối với users.email. Chúng tôi làm điều này với unique index.

class AddEmailIndexToUser
  def change
    # If you already have non-unique index on email, you will need
    # to remove it before you're able to add the unique index.
    add_index :users, :email, unique: true
  end
end

Ngoài ra, bạn có thể tạo unique index khi khởi tạo migration hoặc model bằng:

rails generate model user email:string:uniq

Với unique index, ví dụ trên diễn ra như thế nào?

Bây giờ chúng ta có cơ sở dữ liệu hoạt động như dòng cuối cùng trong việc xử lý dữ liệu không nhất quán. Thao tác lưu lần thứ hai sẽ khởi tạo một exception ActiveRecord :: RecordNotUnique. Trong hầu hết các trường hợp, điều này sẽ dẫn đến lỗi ứng dụng. Nếu bạn cần cung cấp trải nghiệm tốt hơn, bạn có thể rescue và handle exception đó trong action của controller hoặc sử dụng rescue_from ở cấp class.

Khi nào cần dùng Unique Indexes

Ứng dụng Rails của bạn cũng có thể có nhiều quan hệ has_one. Chỉ định một quan hệ has_one chỉ đơn thuần là thiết lập các phương pháp mối quan hệ để đối phó với một đối tượng chứ không phải là một nhóm. has_one không có gì để đảm bảo tính toàn vẹn dữ liệu.

Ví dụ: chúng tq đã quyết định thêm class Profile vào ứng dụng. Users dùng sẽ có một bản ghi profile duy nhất. Các clas của chúng ta bây giờ trông như thế này:

class User
  has_one :profile
  validates :email, presence: true, uniqueness: true
end

class Profile
  belongs_to :user
end

y. Chúng tôi hy vọng rằng bảng profiles sẽ không chứa giá trị user_id trùng lặp, nhưng has_one không đưa ra bất kỳ lời hứa nào về điều đó. Chúng ta sẽ phải thêm unique index vào hồ sơ (trên user_id) để tránh sự không thống nhất về dữ liệu.

Làm thế nào tôi có thể tìm vấn đề trong ứng dụng

Bạn có thể tìm trong dự án của bạn với validates_uniqueness_of, uniqueness: và has_one và sau đó tham khảo chéo đó với một danh sách indexes lấy từ cơ sở dữ liệu của bạn, hoặc bạn có thể để gem làm điều đó cho bạn. Consistency Fail là một gem tìm unique indexes cho bạn. Cài đặt và chạy nó như chi tiết trong README.

Một vấn đề tôi đã nhìn thấy với consistency_fail là các tìm kiếm has_one không nhận ra các mối quan hệ đa hình. Nó sẽ đề nghị một unique index là cột id khi những gì bạn thực sự cần là index trên cột type và id.

Kết luận

Rails thực hiện rất nhiều việc, nhưng validations dữ liệu không phải là một trong số đó. Cơ sở dữ liệu quan hệ của bạn được thiết kế để thực hiện toàn vẹn dữ liệu.

Tài liệu dịch: https://robots.thoughtbot.com/the-perils-of-uniqueness-validations