Mastering Rails Validations: Contexts
Bạn đã bao giờ nghĩ tới việc tùy chỉnh validate trong mỗi phân quyền trong Rails chưa? Đó là người sử dụng có quyền cao hơn được cấp quy tắc xác nhận ít nghiêm ngặt hơn. Bắt đầu nào class User < ActiveRecord::Base validates_length_of :slug, minimum: 3 end Nếu chúng ta muốn thêm ...
Bạn đã bao giờ nghĩ tới việc tùy chỉnh validate trong mỗi phân quyền trong Rails chưa?
Đó là người sử dụng có quyền cao hơn được cấp quy tắc xác nhận ít nghiêm ngặt hơn.
Bắt đầu nào
class User < ActiveRecord::Base validates_length_of :slug, minimum: 3 end
Nếu chúng ta muốn thêm xác nhận validate đó sẽ khác nhau cho các quản trị viên và khác nhau cho người sử dụng thì làm thế nào.
Phương Pháp đầu tiên mà bạn sẽ nghĩ đến là gì ?
Ta sẽ tạo thêm một attr_accessor rồi tiến hành validate như thế này:
class User < ActiveRecord::Base attr_accessor: :edited_by_admin validates_length_of :slug, minimum: 3, unless: Proc.new{|u| u.edited_by_admin? } validates_length_of :slug, minimum: 1, if: Proc.new{|u| u.edited_by_admin? } end class Admin::UsersController def edit @user = User.find(params[:id]) @user.edited_by_admin = true if @user.save redirect # ... else render # ... end end end
Và cách này sẽ hoạt động, tuy nhiên nó không phải là đoạn mã mà chúng ta có thể tự hào.
Bạn đã biết một cách để validate chỉ kích hoạt khi thực hiện các hành động khác nhau. Bạn có nhớ không?
class Meeting < ActiveRecord::Base validate :starts_in_future, on: :create end
Validate này chỉ hoặt động khi chúng ta thực hiện create
Vậy liệu chúng ta có thể sử dụng nó ...
Và đây là cách mà chúng ta mong muốn
class User < ActiveRecord::Base validates_length_of :slug, minimum: 3, on: :user validates_length_of :slug, minimum: 1, on: :admin end class Admin::UsersController def edit @user = User.find(params[:id]) if @user.save(context: :admin) redirect # ... else render # ... end end end
Wow, bây giờ nhìn vào đó. Nó thật là dễ thương phải không?
Và nếu bạn chỉ muốn kiểm tra xác nhận mà không lưu các đối tượng bạn có thể sử dụng:
u = User.new u.valid?(:admin)
u.valid?(:user)
Bây giờ là một thời điểm tốt để nhắc nhở mình về một API tốt đẹp mà có thể làm cho nó ít dư thừa trong trường hợp nhiều quy tắc:
class User < ActiveRecord::Base with_options({on: :user}) do |for_user| for_user.validates_length_of :slug, minimum: 3 for_user.validates_acceptance_of :terms_of_service end with_options({on: :admin}) do |for_admin| for_admin.validates_length_of :slug, minimum: 1 end end
Vấn đề với cách tiếp cận này là bạn không thể cung cấp nhiều ngữ cảnh.
Nếu bạn muốn có một validate on: :admin và thêm một số on: :create thì sao
Ví dụ bạn validate như thế này:
class User < ActiveRecord::Base validates_length_of :slug, minimum: 3, on: :user validates_length_of :slug, minimum: 1, on: :admin validate :something, on: :create end
Khi bạn chạy user.valid?(:admin) hoặc user.save(context: admin), thì validate :something sẽ không hoặt động bởi vì chúng ta đã thay thế :create context thành :admin context
Vậy phải xử lý nó thế nào?
Chúng ta có thể kiểm tra lại cho cả hai context như sau:
class Admin::UsersController def edit User.transaction do @user = User.find(params[:id]) if @user.valid?(:admin) && @user.valid?(:create) @user.save!(validate: false) redirect # ... else render # ... end end end end
Và đây là 1 ví dụ có thể hữu ích cho bạn
class User < ActiveRecord::Base has_many :invoices validate :does_not_have_any_invoice, on: :destroy def destroy transaction do valid?(:destroy) or raise RecordInvalid.new(self) super() end end private def does_not_have_any_invoice errors.add(:invoices, :present) if invoices.exists? end end
Ý tưởng là, nó không thể xóa người dùng đã tồn tại trong bảng invoices