Mastering Rails Validations: Contexts
Bài đăng này đã không được cập nhật trong 3 nă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)
or
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
All rights reserved