我如何在 Ruby 中实现我自己的 Rails 风格的 validates() 方法?

How would I implement my own Rails-style validates() method in Ruby?

我正在尝试了解一些 Ruby 元编程概念。

我想我理解 classes、对象和元classes。不幸的是,我非常不清楚包含的模块在它们的实例/'class'变量方面到底发生了什么.

这是一个人为的问题,其解决方案将回答我的问题:

假设我正在编写自己的蹩脚 Rails "validates" 方法,但我希望它来自混合模块,而不是基准 class:

module MyMixin
  # Somehow validates_wordiness_of() is defined/injected here.

  def valid?
    # Run through all of the fields enumerated in a class that uses
    # "validate_wordiness_of" and make sure they .match(/\A\w+\z/)
  end
end

class MyClass
  include MyMixin

  # Now I can call this method in my class definition and it will
  # validate the word-ness of my string fields.
  validate_wordiness_of :string_field1, :string_field2, :string_field3

  # Insert rest of class here...
end

# This should work.
MyMixin.new.valid?

好的,那么您将如何存储来自 validate_wordiness_of 调用(在 MyClass 中)的字段列表,以便它可以在 valid 中使用?方法(来自 MyMixin)?

或者我是不是完全错了?任何信息将不胜感激!

所以这里有两种替代方法:

有"direct"访问

module MyMixin

  def self.included(base)
    base.extend(ClassMethods)
  end

  def wordy?(value)
    value.length > 2
  end
  module ClassMethods
    def validates_wordiness_of(*attrs)
      define_method(:valid?) do
        attrs.all? do |attr|
          wordy?(send(attr))
        end
      end
    end
  end
end

class MyClass
  include MyMixin

  validates_wordiness_of :foo, :bar

  def foo
    "a"
  end

  def bar
    "asrtioenarst"
  end
end

puts MyClass.new.valid?

此方法的缺点 是多次连续调用 validates_wordiness_of 会相互覆盖。

所以你不能这样做:

validates_wordiness_of :foo
validates_wordiness_of :bar

在 class

中保存经过验证的属性名称

您也可以这样做:

require 'set'
module MyMixin
  def self.included(base)
    base.extend(ClassMethods)
  end

  module Validation
    def valid?
      self.class.wordy_attributes.all? do |attr|
        wordy?(self.send(attr))
      end
    end

    def wordy?(value)
      value.length > 2
    end
  end

  module ClassMethods
    def wordy_attributes
      @wordy_attributes ||= Set.new
    end

    def validates_wordiness_of(*attrs)
      include(Validation) unless validation_included?
      wordy_attributes.merge(attrs)
    end

    def validation_included?
      ancestors.include?(Validation)
    end
  end
end

class MyClass
  include MyMixin

  validates_wordiness_of :foo, :bar

  def foo
    "aastrarst"
  end

  def bar
    "asrtioenarst"
  end
end

MyClass.new.valid?
# => true

我选择让 valid? 方法在您实际添加验证之前不可用。这可能是不明智的。如果没有验证,您可能只是 return true。

如果您引入其他类型的验证,此解决方案将很快变得笨拙。在那种情况下,我将开始在验证器对象中包装验证。