Active Record Validation: Từ căn bản đến nâng cao
Bài đăng này đã không được cập nhật trong 5 năm
I. Mở đầu
Chắc hẳn khi initialize một model, hay start 1 business flow thì bạn cần phải có rất nhiều validate tương ứng để có thểm đảm bảo được logic và business của dự án. Từ những điều cơ bản như not null
hay maximize
đến những validate phức tạp tuỳ theo yêu cầu của business. Và trong bài viết này mình sẽ giới thiệu về nhưng validation từ cơ bản đến nâng cao, giúp các bạn có thế có một cái nhìn tổng quan về nó
II. Let's go
1. Validating length của một attribute
Thường chúng ta sẽ có 2 option cơ bản nhất của việc validate length đó là
:minimum - Attribute không thể nhỏ hơn giá trị length cụ thể
:maximum - Attribute không thể lớn hơn giá trị length cụ thể
:in (or :within) - Giá trị length nằm trong một khoảng cụ thể
:is - Giá trị length phải chính xác
2.Validates format của một attribute
Validate giá trị phải match với một regular expression
có sử dụng format
và with
option
class User < ApplicationRecord
validates :name, format: { with: /\A\w{6,10}\z/ }
end
Bạn cũng có thể define a constant và set value là regular expression
và đẩy vào with: option
. Điều này có thể thuận tiện hơn nếu bạn sử dụng những regex phức tạp
PHONE_REGEX = /\A\(\d{3}\)\d{3}-\d{4}\z/
validates :phone, format: { with: PHONE_REGEX }
Default error message là is invalid
. Tuy nhiên, bạn có thể thay đổi với :message
option.
validates :bio, format: { with: /\A\D+\z/, message: "Numbers are not allowed" }
Ngược lại thì tương tự như vậy, bạn có thể định những nhưng value không thoả mãn regular expression
với without:
option
3: Validating presence của một attribute
Validate này sẽ specified attribute không rỗng
class Person < ApplicationRecord
validates :name, presence: true
end
Person.create(name: "John").valid? # => true
Person.create(name: nil).valid? # => false
Ngược lại bạn có thể sử dụng absence
. Nó sử dụng present?
method để check for nil?
hoặc empty value
class Person < ApplicationRecord
validates :name, :login, :email, absence: true
end
Trong trường hợp attribute là boolean
, bạn không thể kiểm tra sự tồn tại được (attribute sẽ không valid với false
value). Bạn có thể kiểm tra chúng bằng cách sử dụng inclusion
validation
validates :attribute, inclusion: [true, false]
4: Custom validations
Bạn có thể thêm những validations do chính bạn viết ra để đảm bảỏ được nhu cầu, và logic của business, bằng cách thêm một class mới inheriting từ ActiveModel::Validator
hoặc từ ActiveModel::EachValidator
. Cả hai methods đều rất giống nhau, nhưng chúng chỉ khác cách hoạt động một chút
ActiveModel::Validator và validates_with
Implement validate
method vào nơi mà record như là một argument và chạy validation ở nó. Sau đó sử dụng validates_ưith
với class trong model
# app/validators/starts_with_a_validator.rb
class StartsWithAValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'A'
record.errors[:name] << 'Need a name starting with A please!'
end
end
end
class Person < ApplicationRecord
validates_with StartsWithAValidator
end
ActiveModel::EachValidator and validate
Nếu bạn muốn sử dụng new validatior và sử dụng common validate method ở trong một single param, tạo một class inheriting từ ActiveModel::EachValidator
và implement validate_each
cho method vào nơi mà bạn mong muốn
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || 'is not an email')
end
end
class Person < ApplicationRecord
validates :email, presence: true, email: true
end
5: Validates inclusion trong một attribute
Bạn có thể kiểm tra nếu một value bao gồm một array sử dụng inclusion:
helper. :in
option và alias của nó, :within
cho thấy được tập giá trị sẽ chấp nhận được các giá trị tương ứng
class Country < ApplicationRecord
validates :continent, inclusion: { in: %w(Africa Antartica Asia Australia
Europe North America South America) }
end
để kiểm tra xem value không nằm trong array, ta sử dụng exclusion:
helper
class User < ApplicationRecord
validates :name, exclusion: { in: %w(admin administrator owner) }
end
6: Grouping validation
Đôi khi rất thuận tiện khi sử dụng multiple validations trong một điều kiện, Nó có thể được sử dụng with_options
class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
end
end
Toàn bộ validations bên trong with_options
block sẽ được tự động pass nếu như điều kiện if: :is_admin?
thoả mãn
7: Validating numericality của một attribute
Validation này sẽ giới hạn việc thêm chỉ numeric value
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
Bên cạnh :only_integer
, helper này cũng accepts các options khác như sau also accepts the following options to add constraints to acceptable values:
:greater_than
- Giá trị phải lớn hơn gía trị hiện tại. Vàdefault error message
cho option này là"must be greater than %{count}"
.:greater_than_or_equal_to
-Giá trị phải lớn hơn hoặc bằng gía trị hiện tại. Vàdefault error message
cho option này là"must be greater than or equal to %{count}"
.:equal_to
- Giá trị phải bằng gía trị hiện tại. Vàdefault error message
cho option này là"must be equal to %{count}".
:less_than
- Giá trị phải nhỏ hơn gía trị hiện tại. Vàdefault error message
cho option này là"must be less than %{count}".
:less_than_or_equal_to
- Giá trị phải nhỏ hơn hoặc bằng gía trị hiện tại. Vàdefault error message
cho option này là"must be less than or equal to %{count}".
:other_than
- Giá trị phải khác gía trị hiện tại. Vàdefault error message
cho option này là"must be other than %{count}".
:odd
- Giá trị nhận vào phải là số lẻ nếu set là true.default error message
cho option này là"must be odd".
:even
- Giá trị nhận vào phải là số chẵn nếu set là true.default error message
cho option này là"must be even".
Mặc định, giá trị sống sẽ không nhận giá trị
nil
. Bạn có thểallow_nil: true
để chấp nhận nó.
8: Validate uniqueness của một attribute
Validation helper này sẽ check xem value của attribute có unique hay không
class Account < ApplicationRecord
validates :email, uniqueness: true
end
:scope
option mà bạn có thể sử dụng để chỉ định một hoặc nhiều thuộc tính được sử dụng để giới hạn kiểm tra tính duy nhất.
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year, message: "should happen once per year" }
end
:case_sensitive
option dùng để dèine rằng liệu cái giá trị unique kia có case sensitive hay không. Mặc định là true
class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end
9: Skipping Validations
Sử dụng method này nếu bạn muốn bỏ qua validations. Những methods này lưu object vào database kể cả nó invalid.
decrement!
decrement_counter
increment!
increment_counter
toggle!
touch
update_all
update_attribute
update_column
update_columns
update_counters
Bạn cũng có thể skip validation trong khi save bằng việc passing validate
như là một argument khi save
User.save(validate: false)
10: Confirmation of attribute
Ta sử dụng cái này khi mà ta có 2 text fields và nhận chính xác nội dung giống nhau. Ví dụ, bạn muốn confirm trường email address hoặc trường password. Validation này sẽ tạo ra một virtual attribute
với nên giống với tên trường ở trên và thêm _confirmation
được nối ở sau
class Person < ApplicationRecord
validates :email, confirmation: true
end
Note Check này sẽ hoạt đônngj nếu trường email_confirmation
không nil
Để require confirmation, đảm bảo rằng việc thêm presence
check trong trường confirmation
class Person < ApplicationRecord
validates :email, confirmation: true validates :email_confirmation, presence: true
end
11: Using :on option
:on
option chỉ định khi nào thì validation sẽ hoạt động. Mặc định thì tất cả built-in validation sẽ chạy on save (cả 2 trường hợp khi create lẫn update record)
class Person < ApplicationRecord
# it will be possible to update email with a duplicated value
validates :email, uniqueness: true, on: :create
# it will be possible to create the record with a non-numerical age
validates :age, numericality: true, on: :update
# the default (validates on both create and update)
validates :name, presence: true
end
12: Conditional validation
Đôi khi bạn cần validate record chỉ khi rơi vào một số trường hợp nhất định
class User < ApplicationRecord
validates :name, presence: true, if: :admin?
def admin?
conditional here that returns boolean value
end
end
Nếu điều kiện của bạn nhỏ thì có thể sử dụng proc
class User < ApplicationRecord
validates :first_name, presence: true, if: Proc.new { |user| user.last_name.blank? }
end
để sử dụng với negative conditional
, bạn sử dụng unless
class User < ApplicationRecord
validates :first_name, presence: true, unless: Proc.new { |user| user.last_name.present? }
end
Bạn cũng có thể pass một string, cái mà sẽ được executed thông qua instance_eval
class User < ApplicationRecord
validates :first_name, presence: true, if: 'last_name.blank?'
end
III. Kết thúc
Vậy là thông qua bài viết này, các bạn đã gần như nắm được toàn bộ các cách viết validation từ cơ bản đến nâng cao, và theo mình sẽ có thể sử dụng rất nhiều trong dự án hiện tại. Thực ra vẫn còn 1 số cách khác những để express được condition, nhưng bản thân mình thấy với lượng kiến thức trên là vừa đủ và các bạn có thể ứng dụng với phần đa các business requirement rồi. Tuy nhiên, nếu các bạn muốn tìm hiểu thêm hay có góp ý thì hãy comment để giúp cho bài viết này thêm hữu ích nhé. Thanks
All rights reserved