Tái sử dụng validation khi sử dụng form object trong rails

Nếu bạn đang sử dụng pattern "Form Objects" và các validations không đặt ở ActiveRecord model, có thể bạn sẽ gặp khó khăn khi muốn sử dụng lại những validations tùy chỉnh (custom) ở những class khác nhau.

Tôi sẽ đưa cho các bạn ví dụ sau: Tưởng tượng bạn phải viết 1 đoạn mã validate cho một tiến trình thanh toán được cũng cấp bởi nhiều dịch vụ thanh toán khác nhau. Vì vậy bạn phải tách riêng từng class để xử lý cho mỗi dịch vụ thanh toán khác nhau.

class StripePurchase
  include ActiveModel::Model

  attr_accessor :token

  validates :token, presence: true

  def call
    return false if invalid?
    #Process Stripe payment
  end
end
class PayPalPurchase
  include ActiveModel::Model

  attr_accessor :success_url, :failure_url

  validates :success_url, :failure_url, presence: true

  def call
    return false if invalid?
    #Process PayPal payment
  end
end

User is purchasing the product, so we need to check if it exists. Khi mà người dùng muốn thanh toán cho một sản phẩm. Trước hết bạn phải kiểm tra xem sản phẩm đó có tồn tại hay không? Việc kiểm tra này sẽ thực hiện cho mọi hình thức thanh toán.

class ValidateProduct
  include ActiveModel::Model

  attr_accessor :product_id

  validate :product_presence
  validate :product_availability

  private

  def product_presence
    errors.add(:product_id, :invalid) unless Product.where(id: product_id).exists?
  end

  def product_availability
    errors.add(:product_id, :not_available) unless Stock.where(product_id: product_id).exists?
  end
end

Gọi validation:

class StripePurchase
  include ActiveModel::Model

  attr_accessor :token, :product_id

  validates :token, presence: true

  def call
    validate_product = ValidateProduct.new(product_id: product_id)
    unless validate_product.valid?
      define_singleton_method(:errors) { validate_product.errors }
      return false
    end

    return false if invalid?
    #Process Stripe payment
  end
end

Trong khi thanh toán, người dùng có thể nhập voucher giảm giá, vì vậy chúng tao cần phải kiểm tra xem input đó có tồn tại và có được sử dụng với sản phẩm đang thanh toán hay không.

class ValidateVoucher
  include ActiveModel::Model

  attr_accessor :voucher, :product_id

  validate :voucher_presence

  private

  def voucher_presence
    errors.add(:voucher, :invalid) unless Voucher.where(code: voucher, product_id: product_id).exists?
  end
end

Giờ đây nếu validation đầu tiên không thỏa mãn thì tiến trình sẽ dừng lại luôn mà không cần check các validation tiếp theo. Cũng có nghĩa là nếu không có sản phẩm thì cũng không có giảm giá nào cho nó cả.

class StripePurchase
  include ActiveModel::Model

  attr_accessor :token, :product_id, :voucher

  validates :token, presence: true

  def call
    validate_product = ValidateProduct.new(product_id: product_id)
    unless validate_product.valid?
      define_singleton_method(:errors) { validate_product.errors }
      return false
    end

    validate_voucher = ValidateVoucher.new(voucher: voucher, product_id: product_id)
    unless validate_voucher.valid?
      define_singleton_method(:errors) { validate_voucher.errors }
      return false
    end

    return false if invalid?
    #Process Stripe payment
  end
end

Với hướng dẫn trên từ giờ bạn có thể tách các object thành các thành phần nhỏ riêng biệt và sử dụng trong những chỗ khác nhau


All Rights Reserved