12/08/2018, 13:56

Custom Validators in Ruby on Rails

Validate dữ liệu là 1 điều không thể thiếu khi làm project vì nó giúp dữ liệu được lưu vào cơ sở dữ liệu 1 cách chuẩn xác nhất. Ngoài những validate được hỗ trợ sẵn thì chúng ta có thể tự tạo ra các method validate theo ý muốn. Bài viết giới thiệu các custom method hỗ trợ để validate dữ liệu. ...

Validate dữ liệu là 1 điều không thể thiếu khi làm project vì nó giúp dữ liệu được lưu vào cơ sở dữ liệu 1 cách chuẩn xác nhất. Ngoài những validate được hỗ trợ sẵn thì chúng ta có thể tự tạo ra các method validate theo ý muốn. Bài viết giới thiệu các custom method hỗ trợ để validate dữ liệu.

Tại sao lại sử dụng validate?

Validation (Kiểm chứng) được sử dụng để đảm bảo rằng chỉ có dữ liệu hợp lệ được lưu vào cơ sở dữ liệu của bạn. Ví dụ để đảm bảo rằng mỗi người sử dụng cung cấp một địa chỉ email và địa chỉ gửi thư hợp lệ. Có nhiều cách để xác nhận dữ liệu trước khi nó được lưu vào cơ sở dữ liệu như là client-side validations (xác thực ở client), controller-level validations (xác thực ở controller), and model-level validations (xác thực ở model).

  • Client-side validations: xác thực phía client có thể hữu ích nhưng không đáng tin cậy nếu được sử dụng một mình. Nếu chúng được triển khai sử dụng JavaScript, họ có thể được bỏ qua nếu JavaScript bị tắt trong trình duyệt của người dùng. Tuy nhiên, nếu kết hợp với các kỹ thuật khác, xác thực ở client có thể là một cách thuận tiện để cung cấp cho người dùng với thông tin phản hồi ngay lập tức khi họ sử dụng trang web của bạn.

  • Controller-level validations: có thể sử dụng nhưng thường trở nên cồng kềnh và khó kiểm tra và bảo trì.

  • Model-level validations: là cách tốt nhất để đảm bảo rằng chỉ có dữ liệu hợp lệ được lưu vào cơ sở dữ liệu của bạn, thuận tiện để kiểm tra và duy trì. Rails làm cho các validation của model trở nên dễ sử dụng, cung cấp các helper cho các nhu cầu thông thường, và cho phép bạn tạo ra các phương pháp xác thực của riêng bạn

Trước khi lưu 1 đói tượng, Rails sẽ chạy kiểm dữ liệu bạn tạo. Nếu những validate xảy ra bất kì lỗi nào thì Rails sẽ không lưu đối tượng, Chúng ta được biết các validation được hỗ trợ của rails. ví dụ đơn giản:

class Person < ApplicationRecord
  validates :name, presence: true
end

p = Person.new
# => #<Person id: nil, name: nil>
p.errors.messages
# => {}

p.valid?
# => false
p.errors.messages
# => {name:["can't be blank"]}

p = Person.create
# => #<Person id: nil, name: nil>
p.errors.messages
# => {name:["can't be blank"]}

p.save
# => false

p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
  • Các method kích hoạt method validate và chỉ lưu khi đối tượng hợp lệ:

create

create!

save

save!

update

update!

valid?

  • Những method sau sẽ bỏ qua validate và lưu đối tượng vào database:

decrement!

decrement_counter

increment!

increment_counter

toggle!

touch

update_all

update_attribute

update_column

update_columns

update_counters

save(validate: false)

Câu hỏi đặt ra là làm sao có thể viết được validate ngoài những validate được hỗ trợ sẵn??? Bài viết chia sẻ cách tạo 1 validate thông thường và tạo validate có thể dùng chung cho nhiều class model khác nhau

Tạo 1 custom methods cho 1 model

Bạn có thể tạo ra các method kiểm tra và thêm các message khi dữ liệu không hợp lệ.

class Invoice < ApplicationRecord
  validate :expiration_date_cannot_be_in_the_past,
    :discount_cannot_be_greater_than_total_value

  def expiration_date_cannot_be_in_the_past
    if expiration_date.present? && expiration_date < Date.today
      errors.add(:expiration_date, "can't be in the past")
    end
  end

  def discount_cannot_be_greater_than_total_value
    if discount > total_value
      errors.add(:discount, "can't be greater than total value")
    end
  end
end

Theo mặc định validate sẽ chạy khi bạn gọi valid? hoặc lưu đối tượng. Nhưng nó có thể kiểm soát bằng cách cho thêm tùy chọn vào phương thức validate: on: option. ví dụ: on: :create hoặc on: :update

class Invoice < ApplicationRecord
  validate :active_customer, on: :create

  def active_customer
    errors.add(:customer_id, "is not active") unless customer.active?
  end
end

Tạo 1 validate dùng chung cho nhiều model (Custom validator)

Giả sử chúng ta muốn tạo một validate:

# app/models/contact_ticket.rb
class ContactTicket < ActiveRecord::Base
  validates :name, :my_email_field, :content,  presence: true

  validates :my_email_field, email: true # <-- custom email validator

  # ...

end

Để sử dụng email vaidator trong Active Record chúng ta cần phải tạo 1 lớp EmailValidator. Một câu hỏi đặt ra là nơi để giữ cho các lớp validator? Nơi tốt để lưu trữ chúng là thư mục mà chúng ta cần phải tạo ra bằng tay. Để tạo thư mục này có thể nhìn thấy rails có thể load được, chúng ta cần phải thêm class đó vào application.rb

# config/application.rb

    # ...

    # add custom validators path
    config.autoload_paths += %W["#{config.root}/app/validators/"]

Giờ chúng ta có thể tạo class EmailValidator trong thư mục app/validator

# app/validators/email_validator.rb

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] || "wrong email address")
    end
  end
end

EmailValidator được kế thừa từ ActiveModel::EachValidator. Ta có thể sử dụng EmailValidator cho nhiều các thuộc tính và nhiều class model khác nhau, ví dụ:

validates :email_1, email: true
validates :email_2, email: true

Hi vọng bài viết sẽ giúp ích cho bạn, giúp bạn tạo và kiểm soát dữ liệu 1 cách dễ dàng

** Nguồn tham khảo:

http://www.rails-dev.com/custom-validators-in-ruby-on-rails-4/ http://guides.rubyonrails.org/v3.2.13/active_record_validations_callbacks.html http://guides.rubyonrails.org/active_record_validations.html

0