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

时间:2015-02-20 07:36:05

标签: ruby-on-rails ruby metaprogramming mixins

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

我想我理解类,对象和元类。不幸的是,我对 非常不清楚包含的模块在其实例/'类'变量 方面究竟会发生什么。

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

假设我正在编写自己糟糕的Rails“验证”方法,但我希望它来自混合模块,而不是基类 :< / p>

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中)中存储该字段列表,以便它可以在有效的情况下使用?方法(来自MyMixin)?

或者我是否会出错?任何信息都会非常感激!

1 个答案:

答案 0 :(得分:2)

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

通过“直接”访问

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

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

你也可以这样做:

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?方法不可用。这可能是不明智的。如果没有验证,你可能会让它返回true。

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