在Ruby中使用DATA和__END__将代码和数据混合

Tags: ruby

之前一直不理解      __END__的用法,现在看了这篇文章后才算是了解了,于是便翻译之。

《Mixing code and data in Ruby with DATA and __END__》:      http://blog.honeybadger.io/data-and-end-in-ruby/

你知道 Ruby 提供了一种方法在你的脚本中可以将源文件作为数据源来使用吗?当你在写一些一次性的脚本用于验证概念时这个小技巧会为你节约一些时间。让我们来看看吧。

DATA 和 __END__

在下面这个例子中,我使用了一个有趣的关键字    __END__。所有在    __END__下面的内容将会被 Ruby 解释器所忽略。但是有趣的是 ruby 为你提供了一个称为    DATA的 IO 对象,就像你可以读取其他任何文件一样,它能让你读取到    __END__以下的所有内容。

下面这个例子中,我们遍历每一行并进行输出。

DATA.each_line do |line|  puts lineend__END__DoomQuakeDiablo

 

关于这个技术我最喜欢的实例是使用    DATA来包含一个 ERB 模板。它同样也可用于 YAML、CSV等等。

require 'erb'time = Time.nowrenderer = ERB.new(DATA.read)puts renderer.result()__END__The current time is <%= time %>.

 

实际上你也可以使用    DATA来读取    __END__关键字以上的内容。那是因为    DATA实际上是一个指向了整个源文件,并定位到    __END__关键字的位置。你可以试试看在输出之前将 IO 对象反转。下面这个例子将会输出整个源文件。

DATA.rewindputs DATA.read # prints the entire source file__END__meh

 

多文件问题

这个技术最大的缺点是它只能用于单个文件的脚本,直接运行该文件,不能在其他文件进行导入。

下面这个例子,我们有两个文件,并且每个都有它们自己的    __END__部分。然而却只有一个全局    DATA对象。因此第二个文件的    __END__部分刚访问不到了。

# first.rbrequire "./second"puts "First file\n----------------------"puts DATA.readprint_second_data()__END__First end clause

 

# second.rbdef print_second_data  puts "Second file\n----------------------"  puts DATA.read # Won't output anything, since first.rb read the entire fileend__END__ Second end clause

 

snhorne ~/tmp $ ruby first.rbFirst file----------------------First end clause Second file----------------------

 

对于多文件的一个解决方案

在 Sinatra 中有一个很酷的特性是它允许你在你应用的    __END__部分添加多个内联模板。它看起来像下面这样:

# This code is from the Sinatra docs at http://www.sinatrarb.com/intro.htmlrequire 'sinatra'get '/' do  haml :indexend__END__ @@ layout%html   = yield @@ index%div.title Hello world.

 

sinatra 是如何实现的呢?毕竟你的应用可能是运行在 rack 上。在生产环境中你不能再通过    ruby myapp.rb来运行!他们必须有一种在多文件中使用    DATA的解决方案。

因此如果你稍微深入一下 Sinatra 的源代码,你会发现它们并没有使用    DATA。而是使用了跟下面这段代码类似的方案。

# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284app, data = File.read(__FILE__).split(/^__END__$/, 2)

 

实际上它比这个要更复杂一些,因为它们不想读取    __FILE__,它只是    sinatra/base.rb文件。它们其实是需要获取调用了该方法的文件的内容。它们通过解析    caller的结果来获取。

caller方法将会告诉你当前运行的方法是从哪调用的。这里是个简单的例子:

def some_method  puts callerendsome_method # => caller.rb:5:in `<main>'

 

现在可以简单地获取到文件名了,然后从该文件中再提取出与    DATA等价的内容。

def get_caller_data  puts File.read(caller.first.split(":").first).split("__END__", 2).lastend

 

请善用它,不要作恶

希望对于这些技巧你不要经常使用。它们不会使得代码干净、可维护。

然后,你偶尔需要一些又快又脏的实现一个一次性的脚本或者验证一些概念。此时    DATA和    __END__就非常有用了。

   

本文链接:http://www.4byte.cn/learning/119720/zai-ruby-zhong-shi-yong-data-he-end-jiang-dai-ma-he-shu-ju-hun-he.html