需要进行定义和执行的类

#示例代码
class Person
  include CheckedAttributes

  attr_checked :age do |v|
    v >= 18
  end
end

me = Person.new
me.age = 39 #success
me.age = 12 #error

step1:使用eval方法编写一个名为add checked attribute的内核属性,用来为类添加一个最为简单的见过校验的属性

def add_checked_attribute(klass, attribute)
  eval "
    class #{klass}
      def #{attribute}=(value)
        raise(NameError, 'value is wrong') unless value
        @#{attribute} = value
      end

      def #{attribute}
        @#{attribute}
      end
    end
  "
end

add_checked_attribute(String, :age)
obj = String.new
obj.age = 10
p obj.age #10

obj.age = nil
p obj.age #value is wrong

#注意点
1.add_checked_attribute中的参数不能为常量,就是不能 def add_checked_attribute(Klass, attribute)
2.add_checked_attribute中的第一个个参数必须为系统中已经定义过的常量,比如String这个核心类,如果使用Person则为错
3.add_checked_attribute中的第二个参数为方法名,必须使用":"的形式,如果使用age则会报查找不到这个变量的错误

step2:使用instance_eval替代eval

def add_checked_attribute(klass, attribute)
  klass.class_eval{
    define_method("#{attribute}=") do |value|
      raise(NameError, 'value is wrong') unless value
      instance_variable_set("@#{attribute}", value)
    end

    define_method("#{attribute}") do
      instance_variable_get("@#{attribute}")
    end
  }
end

add_checked_attribute(String, :age)
obj = String.new
obj.age = 10
p obj.age #10

obj.age = nil
p obj.age #value is wrong

#注意点
1.instance_variable_set和instance_variable_get中的参数有两种形式,分别是
instance_variable_set(:@name, "jayzen")
instance_variable_set("@name", "jayzen")

step3:使用块检验属性,step1-2中的代码中只能满足false和nil的情况,step扩展到可以通过块来进行校验

def add_checked_attribute(klass, attribute, &validation)
  klass.class_eval{
    define_method("#{attribute}=") do |value|
      raise(NameError, 'value is wrong') unless validation.call(value)
      instance_variable_set("@#{attribute}", value)
    end

    define_method("#{attribute}") do
      instance_variable_get("@#{attribute}")
    end
  }
end

add_checked_attribute(String, :age, &proc{|value| value >= 18})
obj = String.new
obj.age = 20
p obj.age #20

obj.age = 10
p obj.age #value is wrong

step4:使用类宏的形式来定义方法

class Class
  def attr_checked(attribute, &validation)
    define_method("#{attribute}=") do |value|
      raise(NameError, 'value is wrong') unless validation.call(value)
      instance_variable_set("@#{attribute}", value)
    end

    define_method("#{attribute}") do
      instance_variable_get("@#{attribute}")
    end
  end
end

class Person
  attr_checked :age do |v|
    v >= 18
  end
end

obj = Person.new

obj.age = 20
p obj.age #20

obj.age = 10
p obj.age #value is wrong

step5:使用hook方法来实现最开始的功能

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

  module ClassMethods
    def attr_checked(attribute, &validation)
      define_method("#{attribute}=") do |value|
        raise(NameError, 'value is wrong') unless validation.call(value)
        instance_variable_set("@#{attribute}", value)
      end

      define_method("#{attribute}") do
        instance_variable_get("@#{attribute}")
      end
    end
  end
end

class Person
  include CheckedAttributes

  attr_checked :age do |v|
    v >= 18
  end
end

obj = Person.new

obj.age = 20
p obj.age

obj.age = 10
p obj.age

results matching ""

    No results matching ""