Learning Ruby Meta-programming

元编程的意思是:对程序进行编程。
由于 Ruby 是一门动态编程语言,因此,在 Ruby 中可以很方便的实现元编程。
而在编程中灵活的使用元编程,将会非常有用和高效。

下面列举几个实用的、常见的例子。PS:使用 ruby-1.9.3-p194,Rails 3.0.11

1. Ruby 元编程中典型的例子:attr_accessor,attr_reader,attr_writer。
以 attr_accessor 为例,attr_accessor 是一个 Ruby 类中内嵌的方法,可以用来动态的生成访问实例变量的(set/get)方法。

    class User

      attr_accessor :name

    end

    puts User.instance_methods
  

输出结果中将包含:name 和 name= 。因此下面的程序将输出:'admin'

  admin = User.new

  admin.name = 'admin'

  puts admin.name
  # admin
  

2. Rails 中典型的例子:belongs_to,has_one,has_many 等。
以 belongs_to 为例:

  class Book < ActiveRecord::Base

    belongs_to :user

  end

  book = Book.new
  

由于有 belongs_to :user 这句声明,book 这个实例,将拥有下面四个实例方法,实践中会经常用到。

  user

  user=

  build_user

  create_user
  

3. 使用 send 方法,对例1的程序稍作改动,如下:

  class User

    attr_accessor :name

    def initialize(attrs = {})

      attrs.each do |k,v|

        self.send("#{k}=", v)

      end

    end

  end


  admin = User.new(:name => 'admin')

  puts admin.name
  # admin
  

输出结果同样为'admin'。
好处在于新建 user 时,可以传入一个 Hash。这一优点在 user 属性多的时候,比较明显。

4. 使用 class_eval 和 define_method 方法。
下面尝试初始一个类,并且在运行时,动态的为其增加一个方法。输出结果为注释的内容。

  class User

  end

  u = User.new

  puts u.respond_to?(:website_url)
  # false

  User.class_eval do

    define_method :website_url do
      "www.boohee.com"
    end

  end

  puts u.respond_to?(:website_url)
  # true

  puts u.website_url
  # www.boohee.com
  

5. 使用 method_missing 方法:Ruby 中非常强大的特性,灵活的运用可以简化代码。
Rails 源代码中有非常多的应用。例如,ActiveRecord::Base 类中,有下面这样一个方法定义:

  def method_missing(method_id, *arguments, &block)

    if match = DynamicFinderMatch.match(method_id)

      attribute_names = match.attribute_names

      super unless all_attributes_exists?(attribute_names)

      if match.finder?

        ...

        relation.send :find_by_attributes, match, attribute_names, *arguments

      elsif match.instantiator?

        scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block

      end

    elsif match = DynamicScopeMatch.match(method_id)

      ...

    else

      super

    end

  end
  

该方法即实现了所有 find_by_* 的方法。当我们调用 User.find_by_name('admin') 的时候,
实现原理是:User 类中没有该方法,Base中也没有,但是 Base 类中定义了 method_missing 方法。
因此执行该方法。大概浏览一下 DynamicFinderMatch 的 match 方法,他会使用正则解析,判断查找的方式是 find_all 还是 find_last,并且解析将要查找的条件,最后,构造一个 find 语句。

还有很多可以实现动态编程的方法。例如:eval, instance_eval, remove_method, undef_method。
理解 Ruby 元编程,需要清楚 Ruby 的 Classes 是一个对象(Object)。
而且,任意一个 Classes 都不是关闭的,在运行时可以被打开,重新定义程序信息。
在运用元编程实现动态编程的时候,需要清楚的知道它的 self 以及作用域,
否则,会遇到一些奇怪的问题,但是,思考一下(self 和 scope)就可以找到问题了。

2012-12-02

rocket-wing