12/08/2018, 15:26

Custom Rails Validator

Vừa rồi mình có gặp một yêu cầu là khi tạo name cho group thi không được có các từ bậy bạ, các từ thô tục trong đó. Nên mình đã tìm hiểu và viết một cái validation cho riêng trường hợp này. 1. Các class kế thừa khi tạo 1 class validator ActiveModel::Validator || ...

Vừa rồi mình có gặp một yêu cầu là khi tạo name cho group thi không được có các từ bậy bạ, các từ thô tục trong đó. Nên mình đã tìm hiểu và viết một cái validation cho riêng trường hợp này.

1. Các class kế thừa khi tạo 1 class validator

                        ActiveModel::Validator   ||   ActiveModel::EachValidator

ActiveModel::Validator thì sẽ phải hiện thực function validate(record) nhận param là 1 record, và mình thực hiện validate trên record này. Khi hiện thực xong thì mình gọi validate bằng hàm validates_with.

Ví dụ:

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << 'Need a name starting with X please!'
    end
  end
end
 
class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

_

ActiveModel::EachValidator thì phổ biến hơn, mình sẽ phải hiện thực hàm validate_each với 3 param (record, attribute, and value), đối với cách này thì mình có thể chỉ định attribute cần validate và biết được value của attribute đó. Và khi hiện thực xong mình có thể validate bằng method validates.

Ví dụ:

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
end
 
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

2. Custom Validators

Sau khi mình tìm hiểu 1 chút về các class Validate thì quay trờ lại với yêu cầu đầu bài: khi tạo name cho group thi không được có các từ bậy bạ, các từ thô tục trong đó. Yêu cầu ở đây có attribute là field name và value của nó không được chứa các từ bậy bạ, chắc mình sẽ dùng class ActiveModel::EachValidator

Mình sẽ tạo class ở trong folder app/validators, nếu chưa có thì mình tạo folder validators

# app/validators/blacklist_validator.rb

# Validate list of words that can not be use in name field
class BlacklistValidator < ActiveModel::EachValidator
  def validate_each record, attribute, value
    converted_value = value.unicode_normalize(:nfkc).downcase
    plain_text = Nokogiri::HTML(converted_value).text
    BlackWord.pluck(:word).each do |word|
      if plain_text.include? word
        message = options[:message] || :words_in_blacklist
        record.errors.add attribute, message, word: plain_text	
        break
      end	
    end
  end
end

Ở trên các "black words" mình được lưu trong bảng BlackWord của database, Nếu bạn muốn lưu các "black words" này vào file và đọc lên để validate thì bạn có thể tham khảo cách làm của trang sau đây: http://womanonrails.com/custom-rails-validators

Mình sẽ nói 1 chút về 2 hàng này

1. converted_value = value.unicode_normalize(:nfkc).downcase
2. plain_text = Nokogiri::HTML(converted_value).text

Hàng thứ nhấtđể mình tránh các dùng các kí tự unicode để qua mặt compare thì mình dùng hàm unicode_normalize với Unicode normalization là NFKC để convert các kí tự tiếng Nhật 1 byte (Hankaku - 半角) thành 2 bytes (Zenkaku - 全角). Hình như nó còn tác dụng khác mà mình không biết             </div>
            
            <div class=

0