ruby中的动态方法定义

时间:2012-07-10 11:55:47

标签: ruby metaprogramming

$1.to_sym => args[0]($1.to_sym,*args,&block)在以下代码行中做了什么?

class Table
  def method_missing(id,*args,&block)
    return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
    return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
    super
  end
  # ...
end

上下文
Ruport是一个Ruby报告库。您可以使用Ruport :: Data :: Table类创建表格数据并将其转换为不同的格式文本,例如:

require 'ruport'  
table = Ruport::Data::Table.new :column_names => ["country" , "wine" ], :data => [["France" , "Bordeaux" ], ["Italy" , "Chianti" ], ["France" , "Chablis" ]]  
puts table.to_text

⇒    
+--------------------+
| country |   wine   |
+--------------------+
| France | Bordeaux |
| Italy  | Chianti |
| France | Chablis |
+--------------------+

假设您只选择法国葡萄酒并将其转换为以逗号分隔的值:

found = table.rows_with_country("France" )
found.each do |row|
  puts row.to_csv
end

⇒
France, Bordeaux
France, Chablis

你刚刚在Ruport :: Data :: Table上调用了一个名为rows_with_country()的方法。但是这个班级的作者怎么会知道你有一个名为country的专栏呢?事实是,作者不知道这一点。如果您查看Ruport内部,您会看到rows_with_country()和to_csv()都是Ghost方法。 Ruport :: Data :: Table类有点如上所定义。

对rows_with_country()的调用将成为对更传统外观的调用 方法,rows_with(:country),它将列名作为参数 - 换货。此外,对to_csv()的调用将成为对(:csv)的调用。如果方法 名称不是以这两个前缀中的任何一个开头,Ruport会退回 到Kernel#method_missing(),它抛出一个NoMethodError。 (那是什么 super关键字适用于。)

1 个答案:

答案 0 :(得分:2)

让我们看一下method_missing

def method_missing(id,*args,&block)
  return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
  return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
  super
end

必需的背景:在未明确定义请求的方法时,在对象上调用method_missingid将成为所谓方法的符号; args将是一个参数数组,block如果有一个块,则为Proc,或nil

  return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/

执行真的从最后开始,在if:它检查条件

id.to_s =~ /to_(.*)/

这基本上与被调用方法的字符串上的正则表达式匹配。 x =~ y返回x匹配的y的整数偏移量(如果有的话),否则为零。 e.g:

> "abc" =~ /a/
 => 0
> "abc" =~ /.$/
 => 2
> "abc" =~ /\d/
 => nil

请记住,0在布尔条件中被视为真值,因此如果被调用函数的名称以if开头,则to_将被视为为真。方法名称的其余部分由(.*)捕获。

现在,我们没有明确保存捕获组,但Ruby从Perl借用,因为第一个捕获组的内容将保存在$1中,第二个保存在$2中,等等: / p>

> "abc" =~ /a(.)(.)/
 => 0
> $1
 => "b"
> $2
 => "c"

现在,回到有问题的一行:

  return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/

因此,如果被调用方法的名称是to_XYZ形式,则它调用as()方法,第一个参数设置为:XYZ,其余参数来自调用附加,并通过块(如果有的话)。

继续:

  return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/

这基本相同:如果方法名称与rows_with_ABC类似,则它使用散列rows_with()调用{:ABC => args[0]},其中args[0]是给定的第一个参数缺少方法调用。