一段Ruby元编程代码

2017-03-14 21:50

首发于轻境界:轻装前往更高境界

可以用irb(Ruby的REPL程序)实际运行:

class A
  [:scope, :show_snippets, :search_results, :search_objects].each do |name|
    define_method name do
      search
      instance_variable_get "@#{name}"
    end
  end

  def search
    return if @searched
    @scope, @show_snippets, @search_results, @search_objects = 1,2,3,4
    @searched = true
  end

  def call(*attrs)
    attrs.map { |a| send a}
  end
end

a = A.new
[a.scope, a.show_snippets, a.search_results, a.search_objects] # [1, 2, 3, 4]
a.(:scope, :show_snippets, :search_results, :search_objects) # [1, 2, 3, 4]

解释一下:

  • 对给定的4个属性名定义相应的getter,并且在获得属性前要先执行幂等的search方法来生成属性值。define_method接受方法名和代码块来定义方法,instance_variable_get根据名字获取对象的成员变量。

  • 定义一个call方法来支持a.(:attr1, :attr2)这种批调用语法。对*attrs每个属性名a调用send a,相当于把方法名发送给对象(send是任何对象都拥有的方法),让它执行。

  • 等价的普通Ruby代码如下:

    class A
      def scope
        search
        @scope
      end
    
      def show_snippets
        search
        @show_snippets
      end
    
      def search_results
        search
        @search_results
      end
    
      def search_objects
        search
        @search_objects
      end
    
      def search
        return if @searched
        @scope, @show_snippets, @search_results, @search_objects = 1,2,3,4
        @searched = true
      end
    end
    
    a = A.new
    [a.scope, a.show_snippets, a.search_results, a.search_objects]