<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>zhaowenchina</title>
    <link>https://ruby-china.org/zhaowenchina</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>How to write a template engine in less than 30 lines of code</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;庆祝「翻译」节点诞生，发一篇译文。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;原文： &lt;a href="http://bits.citrusbyte.com/how-to-write-a-template-library/" rel="nofollow" target="_blank" title=""&gt;How to write a template engine in less than 30 lines of code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;声明：本文基于模板引擎库 &lt;a href="https://github.com/soveran/mote" rel="nofollow" target="_blank" title=""&gt;mote&lt;/a&gt;。其简洁的代码赋予了我灵感，如果你之前没有探索过模板引擎的内部实现，相信这会是一个绝佳的学习范例。&lt;/p&gt;
&lt;h2 id="前言： 什么是模板？"&gt;前言：什么是模板？&lt;/h2&gt;
&lt;p&gt;模板引擎是一种使用模板来生成文本（字符串）的工具，它还有助于将呈现逻辑与应用逻辑分离。&lt;/p&gt;

&lt;p&gt;除非你正挣扎于某些历史悠久的软件代码中（或者你开发的是没有用户界面的软件），否则你极有可能已经接触过模板引擎了。&lt;/p&gt;

&lt;p&gt;但是你有没有思考过模板引擎的工作原理呢？你尝试过自己构建一个吗？如果我们瞥一眼主流的模板引擎的代码库，你会看到有&lt;a href="https://github.com/ruby/ruby/blob/trunk/lib/erb.rb" rel="nofollow" target="_blank" title=""&gt;几百行 (erb)&lt;/a&gt;的代码，甚至&lt;a href="https://github.com/genki/erubis/" rel="nofollow" target="_blank" title=""&gt;几千行 (erubis)&lt;/a&gt;。即使是名叫 &lt;a href="https://github.com/slim-template/slim" rel="nofollow" target="_blank" title=""&gt;slim&lt;/a&gt; 的家伙也并没有那么苗条。&lt;/p&gt;

&lt;p&gt;所以你或许会觉得处理模板是一个相当复杂的问题，然而下面我会一步步地向你展示其实你可以只用少量代码就构建出一个模板引擎。&lt;/p&gt;

&lt;p&gt;那让我们开始吧。&lt;/p&gt;
&lt;h2 id="定义特性"&gt;定义特性&lt;/h2&gt;
&lt;p&gt;本文我们将要实现的模板引擎只有两条规则：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;以 % 开头的行会被解释为 Ruby 代码。&lt;/li&gt;
&lt;li&gt;可以把 Ruby 代码插入到任何行中 {{ ... }} 符号的中间。比如我们可以使用 &lt;code&gt;{{ article.title }}&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;完了？就两条规则？没错，记住，第一条规则让我们可以访问 Ruby 的一切，也就是说所有常见的模板特性（比如循环、调用上层函数、嵌入 partial 等）都可以靠这条规则来实现。如此简单的特性甚至还带来了一个额外的好处：你不需要去学一个新的模板语言或者 DSL，因为你已经会 Ruby 了。&lt;/p&gt;

&lt;p&gt;你可以这样来调用其他模板：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% render("path/to/template.template", {})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以写注释：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% # this is a comment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者执行 blocks：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% 3.times do |i|
{{i}}
% end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据上面的特性，我们可以来编写一个示例模板：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
% if access == 0
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt; no access :( &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
% else
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  % data.each do |i|
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;{{i}}&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  % end
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
% end
% # comments are just normal ruby comments
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面我们称这个模板为 &lt;code&gt;index.template&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;下面我们要做的就是怎样编写一个方法来解析这个模板并输出正确的字符串。在此之前，我们先来看一个中间步骤：如何使用纯粹的 Ruby 代码来输出 HTML。&lt;/p&gt;
&lt;h2 id="与 index.template 行为相同的 render 函数"&gt;与 index.template 行为相同的 render 函数&lt;/h2&gt;
&lt;p&gt;在模板引擎出现以前，你同样可以使用纯 Ruby 来达到与 &lt;code&gt;index.template&lt;/code&gt; 一样的效果，就像下面的代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c1"&gt;# 一个新的字符串，用来存放输出的内容&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;html&amp;gt;"&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;body&amp;gt;"&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;div&amp;gt; no access :( &amp;lt;/div&amp;gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;ul&amp;gt;"&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/ul&amp;gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/body&amp;gt;"&lt;/span&gt;
  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/html&amp;gt;"&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把它粘贴到 IRB 中试试：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&amp;gt;&amp;gt; render_index(0,["foo", "bar"])
=&amp;gt; "&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;div&amp;gt;&lt;/span&gt; no access :( &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;"
&amp;gt;&amp;gt; render_index(1,["foo", "bar"])
=&amp;gt; "&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;ul&amp;gt;&amp;lt;li&amp;gt;&lt;/span&gt;foo&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&amp;lt;li&amp;gt;&lt;/span&gt;bar&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假如你的应用程序涉及的范围非常有限，或许你就根本用不着模板引擎，这样就大功告成了！你可以根据需要自己编写 &lt;code&gt;render_index()&lt;/code&gt;、&lt;code&gt;render_header()&lt;/code&gt;、&lt;code&gt;render_footer()&lt;/code&gt; 方法。PHP 本身其实就是一个模板引擎，这也解释了为什么 PHP 社区的人们会经常这么做。&lt;/p&gt;

&lt;p&gt;然而我们来看 &lt;code&gt;render_index()&lt;/code&gt; 的目的是为了探寻如何将 &lt;code&gt;index.template&lt;/code&gt; 转换成 &lt;code&gt;render_index()&lt;/code&gt; ，并将转换的方法共通化，这样我们的模板引擎就出来了。我们并不想真的去编写 &lt;code&gt;render_index()&lt;/code&gt;、&lt;code&gt;render_header()&lt;/code&gt;、&lt;code&gt;render_footer()&lt;/code&gt; 方法，也不想用代码生成器来实现。我们想要的是能够动态生成一个行为类似 &lt;code&gt;render_index()&lt;/code&gt; 的方法，而不是亲手来写 &lt;code&gt;render_index()&lt;/code&gt; 方法的代码。&lt;/p&gt;

&lt;p&gt;在看具体的实现方式之前，我们再来看一个中间步骤：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;define_render_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c1"&gt;# 空字符串，用来存储构建方法的字符串&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"def render_index(access, data) &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"output = &lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"if access == 0&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"  output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;div&amp;gt; no access :( &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"else&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"   output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"      data.each do |i|&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"        output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;li&amp;gt; &lt;/span&gt;&lt;span class="se"&gt;\#&lt;/span&gt;&lt;span class="s2"&gt;{ i } &amp;lt;/li&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"      end &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"   output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"end&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"  output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"  output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"  return output &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"end&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把它粘贴到 IRB 中并调用：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&amp;gt;&amp;gt; define_render_index() 
=&amp;gt; nil
&amp;gt;&amp;gt; render_index(1, ["foo", "bar"])
=&amp;gt; "&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;ul&amp;gt;&amp;lt;li&amp;gt;&lt;/span&gt;foo&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&amp;lt;li&amp;gt;&lt;/span&gt;bar&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们就得到一个更为完整的蓝图了：我们可以将原始的模板逐行转换为一系列字符串，然后修改这些字符串使其能够被 Ruby 解释。生成了这个方法以后，在调用它时就会执行我们所期望的模板的行为。&lt;/p&gt;

&lt;p&gt;上面这个逐行转换的过程就是我们的 &lt;code&gt;parse&lt;/code&gt; 方法的根基，下面就让我们具体来看。&lt;/p&gt;
&lt;h2 id="Parse 方法"&gt;Parse 方法&lt;/h2&gt;&lt;h3 id="1. 使用 Proc"&gt;1. 使用 Proc&lt;/h3&gt;
&lt;p&gt;我们不想让 &lt;code&gt;func&lt;/code&gt; 字符串像 &lt;code&gt;define_render_index()&lt;/code&gt; 一样以一个具名函数开头，所以我们用 Proc 并将其保存至一个变量，有需要时就 &lt;code&gt;.call&lt;/code&gt; 它。&lt;/p&gt;
&lt;h3 id="2. 设定 Proc 的变量"&gt;2. 设定 Proc 的变量&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;define_render_index()&lt;/code&gt; 还写死了其中的变量：access 和 data。但我们需要将变量名传给 &lt;code&gt;parse&lt;/code&gt; 函数，这样才能构建定义 Proc 的字符串。这里我们把变量名直接以字符串形式传递给 parse 方法，就像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"access, data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 将模板逐行转译成函数字符串"&gt;3. 将模板逐行转译成函数字符串&lt;/h3&gt;
&lt;p&gt;上面的 &lt;code&gt;define_render_index()&lt;/code&gt; 已经告诉我们逐行处理模板来创建一个新的 Ruby 方法时所需遵循的规则了。这些规则就是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;所有的双引号必须被转义，每一行的内容用双引号围起来，并在每一行的最后添加换行符 "\n"。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如果行的第一个字符（不包括空格）是 &lt;code&gt;%&lt;/code&gt;，就把 &lt;code&gt;%&lt;/code&gt; 删除。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;变换前:
  % if data.empty? 
变换后:
  "if data.empty?\n"
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;其他的所有行都在前面加上 &lt;code&gt;"output &amp;lt;&amp;lt;"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;变换前:
  &lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
变换后:
  "output &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt; &lt;span class="err"&gt;\"&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;\" \n"
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. {{ ... }} 会被变换为 #{ ... }"&gt;4. {{ ... }} 会被变换为 #{ ... }&lt;/h3&gt;
&lt;p&gt;我们会使用正则表达式来实现这一点&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;变换前:
  &amp;lt;li&amp;gt;{{ i }}&amp;lt;/li&amp;gt; 
变换后:
  "output &amp;lt;&amp;lt; \"&amp;lt;li&amp;gt; \#{ i } &amp;lt;/li&amp;gt;\" \n"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要运用上述规则，我们首先用 &lt;code&gt;.split("\n")&lt;/code&gt; 将原始模板文件分割成数组，这样数组中的每个元素就是模板文件的每一行了。然后循环这个数组来构建 &lt;code&gt;func&lt;/code&gt; 字符串，最后将其 &lt;code&gt;eval&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我们最终得到的 parse 函数如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Proc.new do |&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;| &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; output = &lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;

  &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/^\s*(%)(.*?)$/&lt;/span&gt;
     &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^\s*%(.*?)$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'\1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;
     &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;" output &amp;lt;&amp;lt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\{\{([^\r\n\{]*)\}\}/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'#{\1}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;" output; end &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;

  &lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以在 IRB 中亲自尝试一番：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&amp;gt;&amp;gt; index = parse("index.template", "access, data")
=&amp;gt; nil
&amp;gt;&amp;gt; index.call(1,["Foo"]) 
=&amp;gt; " &lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;\n &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;\n&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;\n&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;foo&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt; \n&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;\n &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;\n &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;\n"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大功告成了！就这么几行代码，你就实现出一个颇为强劲的模板引擎了。由于没有涉及太多多余的功能，我们将复杂度降到了最低，也提高了模板语言的清晰程度。复杂度的降低能够让应用程序条例更清晰，更不容易出错，还能加快开发特性的速度。&lt;/p&gt;
&lt;h2 id="它能扩展（Scale）吗？"&gt;它能扩展（Scale）吗？&lt;/h2&gt;
&lt;p&gt;上面的代码恐怕不行！但是 &lt;a href="https://github.com/soveran/mote" rel="nofollow" target="_blank" title=""&gt;mote&lt;/a&gt;，也就是本文的灵感来源，当然可以。mote 附带了一些 helper 方法和缓存功能，我们已经成功将其运用在了各种大型的 Web 应用中。更不用提 &lt;a href="https://github.com/soveran/mote/blob/master/benchmarks/result.txt" rel="nofollow" target="_blank" title=""&gt;mote 的速度相当快&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;同时我还想就其简洁这个显著的特点说两句——虽然 mote 的体积极其小，但对于其要解决的问题给出了一个专注而又完整的解决之道。&lt;/p&gt;

&lt;p&gt;希望这篇文章会对从未探寻过模板引擎的人或者想要做一个模板引擎的人有所帮助。欢迎留下你的评论或者反馈。&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Fri, 24 Apr 2015 10:07:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/25289</link>
      <guid>https://ruby-china.org/topics/25289</guid>
    </item>
    <item>
      <title>Matz 最新作《关于 mruby 的一切》电子书发布了</title>
      <description>&lt;p&gt;购买地址： &lt;a href="http://www.ituring.com.cn/book/1339" rel="nofollow" target="_blank"&gt;http://www.ituring.com.cn/book/1339&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本书是 Matz 在《日经 Linux》杂志上关于 mruby 专栏文章的合集。
mruby 已于数月前发布了 1.0 版本。如果你对 mruby 感兴趣，这本小书是你入门的不二之选。&lt;/p&gt;

&lt;p&gt;我是本书的译者。说实话对 mruby 的理解并不深入，因此难免有所疏漏。欢迎大家指教：）&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Tue, 06 May 2014 17:44:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/19053</link>
      <guid>https://ruby-china.org/topics/19053</guid>
    </item>
    <item>
      <title>Rails 4.1 的新特性 (译)</title>
      <description>&lt;p&gt;首发： &lt;a href="http://zhaowen.me/blog/2014/04/09/whats-new-in-rails-4-dot-1/" rel="nofollow" target="_blank"&gt;http://zhaowen.me/blog/2014/04/09/whats-new-in-rails-4-dot-1/&lt;/a&gt;
原文： &lt;a href="http://brewhouse.io/blog/2013/12/17/whats-new-in-rails-4-1.html" rel="nofollow" target="_blank" title=""&gt;What's new in Rails 4.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你还不知道的话，Rails 4.1 已经&lt;a href="http://weblog.rubyonrails.org/" rel="nofollow" target="_blank" title=""&gt;于今天正式发布了&lt;/a&gt;！虽然只是版本的小更新，但还是新引入了诸多激动人心的新特性。本文列出了其中几个最让我欣喜的新特性，并介绍了为什么我觉得它们很有用。&lt;/p&gt;
&lt;h2 id="Action Mailer 预览"&gt;Action Mailer 预览&lt;/h2&gt;
&lt;p&gt;测试 Rails 的邮件模板一直以来都很不方便。目前我的测试流程为：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;改动邮件模板&lt;/li&gt;
&lt;li&gt;通过 rails console 发送邮件&lt;/li&gt;
&lt;li&gt;在浏览器中查看输出内容&lt;/li&gt;
&lt;li&gt;重复上述步骤&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;虽然 &lt;a href="https://github.com/ryanb/letter_opener" rel="nofollow" target="_blank" title=""&gt;Letter Opener&lt;/a&gt; gem 提供了一些便利，但还远远不够理想。幸运的是，&lt;a href="https://github.com/pixeltrix" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;&lt;a href="/pixeltrix" class="user-mention" title="@pixeltrix"&gt;&lt;i&gt;@&lt;/i&gt;pixeltrix&lt;/a&gt; 通过一番努力将 &lt;a href="https://github.com/37signals/mail_view" rel="nofollow" target="_blank" title=""&gt;MailView&lt;/a&gt; gem 集成进了 Rails 4.1。现在你可以轻易地为 mailer 创建 preview 并在浏览器中进行预览了 &lt;code&gt;http://localhost:3000/rails/mailers&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In /test/mailers/previews/notifier_preview.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotifierPreview&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Preview&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;welcome&lt;/span&gt;
    &lt;span class="c1"&gt;# Mock up some data for the preview&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FactoryGirl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Return a Mail::Message here (but don't deliver it!)&lt;/span&gt;
    &lt;span class="no"&gt;Notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是，虽然默认情况下 preview 文件位于 test 目录下（可以通过 &lt;code&gt;config.action_mailer.preview_path&lt;/code&gt; 修改），但其实它运行于 development 环境。因此，如果你要用 &lt;code&gt;FactoryGirl&lt;/code&gt; 等 gem 来生成数据，需要确保 Gemfile 中这些 gem 也在 development 的 group 下面。&lt;/p&gt;

&lt;p&gt;如果你的 app 没有 &lt;code&gt;test&lt;/code&gt; 目录（比如 &lt;code&gt;rspec&lt;/code&gt; 用户），你可能会将默认的 &lt;code&gt;config.action_mailer.preview_path&lt;/code&gt; 修改成类似 &lt;code&gt;/app/mailer/previews&lt;/code&gt; 的路径。但请注意，&lt;code&gt;/app&lt;/code&gt; 目录在 production 环境下是即时加载（eager-loaded）的，所以或许这并不是 preview 文件的最佳存放之处。&lt;/p&gt;

&lt;p&gt;该特性的延伸阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#action-mailer-previews" rel="nofollow" target="_blank" title=""&gt;Release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://edgeapi.rubyonrails.org/classes/ActionMailer/Base.html" rel="nofollow" target="_blank" title=""&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/rails/rails/pull/13332" rel="nofollow" target="_blank" title=""&gt;#13332&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Active Record Enums"&gt;Active Record Enums&lt;/h2&gt;
&lt;p&gt;你曾经在 model 中使用多个 &lt;code&gt;boolean&lt;/code&gt; 字段来表示一个复杂的状态吗？我绝对这么干过，并且代码很快就变得无法控制了。&lt;/p&gt;

&lt;p&gt;救星 Enums 来了！&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bug&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# Relevant schema change looks like this:&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# create_table :bugs do |t|&lt;/span&gt;
  &lt;span class="c1"&gt;#   t.column :status, :integer, default: 0 # defaults to the first value (i.e. :new)&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;

  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:assigned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:in_progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:resolved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reopened&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:assignee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Developer'&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assignee&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;developer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new?&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:assigned&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolved&lt;/span&gt;           &lt;span class="c1"&gt;# =&amp;gt; a scope to find all resolved bugs&lt;/span&gt;

&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolved?&lt;/span&gt;          &lt;span class="c1"&gt;# =&amp;gt; check if bug has the status :resolved&lt;/span&gt;

&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolved!&lt;/span&gt;          &lt;span class="c1"&gt;# =&amp;gt; update! the bug with status set to :resolved&lt;/span&gt;

&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;             &lt;span class="c1"&gt;# =&amp;gt; a symbol describing the bug's status&lt;/span&gt;

&lt;span class="n"&gt;bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:resolved&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; set the bug's status to :resolved&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在内部，与这些状态在数据库中相对应的是整数值，以节省空间。同样值得一提的是，&lt;code&gt;enum&lt;/code&gt; 宏所添加的方法是通过 mix-in 一个 module 来实现的。这意味着你可以方便地在 model 中重写它们并使用 &lt;code&gt;super&lt;/code&gt; 来调用原来的实现。&lt;/p&gt;

&lt;p&gt;使用该特性时还有如下一些注意事项：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I.&lt;/strong&gt; 不要被其名字所迷惑，在一些数据库中并不使用 &lt;code&gt;ENUM&lt;/code&gt; 类型来实现该特性。状态和其对应的整数值的匹配是通过 model 文件来维护的。所以一旦定义完 &lt;code&gt;enum&lt;/code&gt; 的 symbol 后，你就不应该再去改动其顺序了。可以明确地指定 mapping 来删除不再使用的状态：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bug&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;new: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;in_progress: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;resolved: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;rejected: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;reopened: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;II.&lt;/strong&gt; 避免在一个类的不同 enum 中使用相同的名字！这样会使 Active Record 很迷茫！&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bug&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;code_review_status: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# WARNING: Don't do this!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;III.&lt;/strong&gt; 如果你要使用自定义的 scope 来查询 enum 字段，需要传入对应的整数而不是 symbol。你可以使用宏所添加的常量来访问 enum-integer 的匹配：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bug&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status &amp;lt;&amp;gt; ? OR status &amp;lt;&amp;gt; ?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:resolved&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:rejected&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;del&gt;&lt;strong&gt;IV.&lt;/strong&gt; 目前，dirty tracking 方法（比如 &lt;code&gt;status_was?&lt;/code&gt;）还无法用于 enum（目前返回的是整数值而不是 symbol 值）。此问题应该在正式版发布前会修复（进度参见 &lt;a href="https://github.com/rails/rails/pull/13267" rel="nofollow" target="_blank" title=""&gt;#13267&lt;/a&gt;）&lt;/del&gt;（译者注：原文写于 Beta 版发布时，现在正式版已修复。）&lt;/p&gt;

&lt;p&gt;该特性的延伸阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#active-record-enums" rel="nofollow" target="_blank" title=""&gt;Release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html" rel="nofollow" target="_blank" title=""&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Original commit: &lt;a href="https://github.com/rails/rails/commit/db41eb8a6ea88b854bf5cd11070ea4245e1639c5" rel="nofollow" target="_blank" title=""&gt;db41eb8a&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Action Pack Variants"&gt;Action Pack Variants&lt;/h2&gt;
&lt;p&gt;作为 Web 开发人员，应该已经意识到我们已经全面过渡到了&lt;a href="http://zh.wikipedia.org/wiki/%E5%BE%8C%E5%80%8B%E4%BA%BA%E9%9B%BB%E8%85%A6%E6%99%82%E4%BB%A3" rel="nofollow" target="_blank" title=""&gt;后 PC 时代&lt;/a&gt;。虽然我钟爱于响应式设计，但它并不是解决跨设备网页显示问题的银弹。多数情况下，更合适的方法是为特定的设备种类定制 view 来显示最恰当的内容。&lt;/p&gt;

&lt;p&gt;有了 &lt;strong&gt;Action Pack Variants&lt;/strong&gt; 以后，在 Rails 4.1 中实现起来就容易多了：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:detect_device_variant&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_device_variant&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/iPad/i&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:tablet&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/iPhone/i&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;               &lt;span class="c1"&gt;# /app/views/posts/show.html.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;phone&lt;/span&gt;         &lt;span class="c1"&gt;# /app/views/posts/show.html+phone.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablet&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="vi"&gt;@show_edit_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的示例用了 &lt;code&gt;before_action&lt;/code&gt; 使 HTTP 的 &lt;code&gt;User-Agent&lt;/code&gt; 消息头匹配给定的关键字，并相应地赋值给 &lt;code&gt;request.variant&lt;/code&gt;。在 &lt;code&gt;respond_to&lt;/code&gt; 的 block 中通过指定所支持的 variant，Rails 会根据特定的 format 和 variant 的组合来 render 相应的模板。你还可以传一个 block 来指定某个 variant 的情况下要执行的代码。&lt;/p&gt;

&lt;p&gt;实际上你甚至可以将声明省略——只要在 &lt;code&gt;views&lt;/code&gt; 目录下有对应的模板文件，Rails 就会自动匹配上。而如果某个 variant 没有专门的模板，Rails 会退一步去加载该 format 默认的模板（比如 &lt;code&gt;show.html.erb&lt;/code&gt;）。因此你可以在两个 variant 之间共享模板。在上面的示例中，如果不存在 &lt;code&gt;/app/views/posts/show.html+tablet.erb&lt;/code&gt;， &lt;code&gt;tablet&lt;/code&gt; variant 就会重用默认模板。&lt;/p&gt;

&lt;p&gt;虽然在介绍该特性时，大多数示例都会使用 &lt;code&gt;User-Agent&lt;/code&gt; 消息头，但值得注意的是 Rails 中的实现完全是不可预知的。在渲染模板之前，&lt;code&gt;request.variant&lt;/code&gt; 可能是基于任何条件被赋值的，例如基于请求的域名（或子域名）、HTTP 消息头、session 数据、甚至是抛硬币的结果。&lt;/p&gt;

&lt;p&gt;这就使得该特性具有相当的灵活性，可以用于很多用途，例如 API 版本控制、A/B 测试、甚至可以用来做特性演示！&lt;/p&gt;

&lt;p&gt;该特性的延伸阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#variants" rel="nofollow" target="_blank" title=""&gt;Release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://edgeapi.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to" rel="nofollow" target="_blank" title=""&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/rails/rails/pull/12977" rel="nofollow" target="_blank" title=""&gt;#12977&lt;/a&gt;、&lt;a href="https://github.com/rails/rails/pull/13290" rel="nofollow" target="_blank" title=""&gt;#13290&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Application Message Verifier"&gt;Application Message Verifier&lt;/h2&gt;
&lt;p&gt;Rails 4.1 还引入了一个内置的 helper 来生成基于 &lt;a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code" rel="nofollow" target="_blank" title=""&gt;HMAC&lt;/a&gt; 的加密消息。消息验证以前被用于加密 Cookies 等高端操作，然而现在已经可以方便地将其用于其他一些用途了。&lt;/p&gt;

&lt;p&gt;例如，你可以实现一个无状态的「重置密码」功能而无须在数据库中存储 token：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verifier_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@verifiers&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="vi"&gt;@verifiers.fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="vi"&gt;@verifiers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message_verifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_password_token&lt;/span&gt;
    &lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifier_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reset-password'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Unique for each type of messages&lt;/span&gt;
    &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_password!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_password_confirmation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# This raises an exception if the message is modified&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifier_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reset-password'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_password&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_confirmation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_password_confirmation&lt;/span&gt;
      &lt;span class="n"&gt;save!&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# Token 过期&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Notifier&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="vi"&gt;@reset_password_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password_reset_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="vi"&gt;@user.reset_password_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Your have requested to reset your password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样一来，重置密码所需的所有信息都包含在链接中了，没必要在数据库中存储信息。这也能用于 &lt;code&gt;OAuth&lt;/code&gt; 等（&lt;code&gt;state&lt;/code&gt; 参数）。&lt;/p&gt;

&lt;p&gt;使用该特性时，需要重点防范&lt;a href="http://en.wikipedia.org/wiki/Replay_attack" rel="nofollow" target="_blank" title=""&gt;重放攻击&lt;/a&gt;。以上面的代码为例，如果我们没有使用 timestamp 来检验是否过期，万一邮件落到了居心不良的人手中，这个 URL 就可以随时用来重置用户的密码！&lt;/p&gt;

&lt;p&gt;另外，用来加密消息的 key 基于应用程序的 &lt;code&gt;secret_key_base&lt;/code&gt; 以及你加入的「盐值（salt）」（本例中为 &lt;code&gt;User-reset-password&lt;/code&gt;）。改变其中一个，就会使得之前的加密消息全部无效。&lt;/p&gt;
&lt;h2 id="Spring"&gt;Spring&lt;/h2&gt;
&lt;p&gt;根据你使用的 gems，启动一个 Rails app 平均需要 5 秒钟时间。意味着每次运行测试都会浪费 5 秒钟，哪怕你只是运行一个单独的测试用例！如果你遵循 TDD，你每天大约会运行 50 次测试。这么算的话，过去 5 年中就浪费了&lt;a href="http://xkcd.com/1205/" rel="nofollow" target="_blank" title=""&gt;整整 5 天时间&lt;/a&gt;！&lt;/p&gt;

&lt;p&gt;我们终于时来运转，由 Rails 4.1 生成的新应用在内部集成了预加载器 &lt;a href="https://github.com/jonleighton/spring" rel="nofollow" target="_blank" title=""&gt;Spring&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Spring 使你的应用程序运行于后台，所以你就不必每次运行测试、rake task 或 migration 时都要启动应用程序了。如果你熟悉 &lt;a href="https://github.com/burke/zeus" rel="nofollow" target="_blank" title=""&gt;Zeus&lt;/a&gt; 或 &lt;a href="https://github.com/sporkrb/spork" rel="nofollow" target="_blank" title=""&gt;Spork&lt;/a&gt; 等 gem，就不会对此感到陌生。然而 spring 使用 binstub 包装了常用的 Rails 命令（默认为 &lt;code&gt;rake&lt;/code&gt; 和 &lt;code&gt;rails&lt;/code&gt;），因此如果 &lt;code&gt;./bin&lt;/code&gt; 在你的 &lt;code&gt;PATH&lt;/code&gt; 中，那么什么都不用做就能提速不少了！&lt;/p&gt;

&lt;p&gt;我在自己的项目中试了一下，使用了 spring 后每次运行测试都能节省近 5 秒钟。我能因此得到 5 天假期吗？:)&lt;/p&gt;

&lt;p&gt;你可以阅读 &lt;a href="https://github.com/jonleighton/spring#readme" rel="nofollow" target="_blank" title=""&gt;Spring README&lt;/a&gt; 来了解它的工作原理，以及如何在既有的项目中引入它。&lt;/p&gt;

&lt;p&gt;该特性的延伸阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#spring-application-preloader" rel="nofollow" target="_blank" title=""&gt;Release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#spring" rel="nofollow" target="_blank" title=""&gt;Upgrading guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jonleighton/spring#readme" rel="nofollow" target="_blank" title=""&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/rails/rails/pull/12958" rel="nofollow" target="_blank" title=""&gt;#12958&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="其他新特性"&gt;其他新特性&lt;/h2&gt;
&lt;p&gt;这些都只是本次更新内容的一部分。还有很多其他可能对你有用的新特性，比如 &lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#config-secrets-yml" rel="nofollow" target="_blank" title=""&gt;使用 secrets.yml 来记录敏感数据&lt;/a&gt;、&lt;a href="https://github.com/rails/rails/pull/12824" rel="nofollow" target="_blank" title=""&gt;在测试中控制时间&lt;/a&gt;、&lt;a href="https://github.com/rails/rails/pull/12183" rel="nofollow" target="_blank" title=""&gt;更好的 JSON 处理&lt;/a&gt;、&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#module-concerning" rel="nofollow" target="_blank" title=""&gt;Module#concerning&lt;/a&gt;、&lt;a href="https://github.com/rails/rails/pull/12891" rel="nofollow" target="_blank" title=""&gt;to_param 宏&lt;/a&gt;等等。我建议你通过&lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html" rel="nofollow" target="_blank" title=""&gt;更新日志&lt;/a&gt;来查看所有的变更点！&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Wed, 09 Apr 2014 21:06:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/18504</link>
      <guid>https://ruby-china.org/topics/18504</guid>
    </item>
    <item>
      <title>Ruby 实现的简易推荐系统 (译)</title>
      <description>&lt;p&gt;首发：&lt;a href="http://zhaowen.me/blog/2014/03/25/ruby-recommendation-system/" rel="nofollow" target="_blank"&gt;http://zhaowen.me/blog/2014/03/25/ruby-recommendation-system/&lt;/a&gt;
原文：&lt;a href="http://otobrglez.opalab.com/ruby/2014/03/23/simple-ruby-recommendation-system.html" rel="nofollow" target="_blank" title=""&gt;Simple recommendation system written in Ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我在求职，于是昨天我回顾了一下以前做过的 Rails 项目，试图能给我的简历添上几笔。我找到了一个有意思的老项目，我在其中实现了推荐系统。但也没有很出彩，只是基于博客文章的标签做了推荐。我决定拿出其中的一些代码来写一篇文章。&lt;/p&gt;

&lt;p&gt;推荐系统的算法基于 &lt;a href="http://en.wikipedia.org/wiki/Jaccard_index" rel="nofollow" target="_blank" title=""&gt;Jaccard 系数&lt;/a&gt;，也被称为 Jaccard 相似度系数。Jaccard 系数是由植物学家 &lt;a href="http://en.wikipedia.org/wiki/Paul_Jaccard" rel="nofollow" target="_blank" title=""&gt;Paul Jaccard&lt;/a&gt; 提出的，是用来体现样本相似性和差异性的数值。&lt;/p&gt;
&lt;h2 id="运作原理"&gt;运作原理&lt;/h2&gt;
&lt;p&gt;取出当前的 item（比如博客文章）和能够很好地描述它的属性（标签、分类或单词）。然后对其它每个 item 计算出它们交集和并集的商。数学过程可以表述为以下公式。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://upload.wikimedia.org/math/1/8/6/186c7f4e83da32e889d606140fae25a0.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;该方程的计算结果在 0 到 1 之间。你也能够方便地用以下公式来计算项目间的差异性。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://upload.wikimedia.org/math/0/2/9/02906c47e0a08707ad6e35a6c34a43b4.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="Ruby 的实现示例"&gt;Ruby 的实现示例&lt;/h2&gt;
&lt;p&gt;下面我将使用书名中的单词来推荐书籍。使用简单的 &lt;code&gt;Book&lt;/code&gt; 类即可。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# 长度大于2的不重复的单词的数组&lt;/span&gt;
  &lt;span class="c1"&gt;# 也可以是「标签」或「分类」的数组&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;words&lt;/span&gt;
    &lt;span class="vi"&gt;@words&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[a-zA-Z]{3,}/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BookRecommender&lt;/code&gt; 类使用当前的书籍和书籍数组进行初始化。&lt;code&gt;recommendations&lt;/code&gt; 方法会循环数组并给每一个元素设定 &lt;code&gt;jaccard_index&lt;/code&gt; 值，最后将书籍进行排序。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookRecommender&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
    &lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recommendations&lt;/span&gt;

    &lt;span class="c1"&gt;# 计算每个元素的 jaccard_index 值并排序&lt;/span&gt;
    &lt;span class="vi"&gt;@books.map&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

      &lt;span class="c1"&gt;# 运行中定义 jaccard_index 的取值 singleton 方法&lt;/span&gt;
      &lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jaccard_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="vi"&gt;@jaccard_index&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 还有赋值方法&lt;/span&gt;
      &lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"jaccard_index="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="vi"&gt;@jaccard_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 计算样本的交集&lt;/span&gt;
      &lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@book.words&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;words&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
      &lt;span class="c1"&gt;# ... 和并集&lt;/span&gt;
      &lt;span class="n"&gt;union&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@book.words&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;words&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;

      &lt;span class="c1"&gt;# 将除法运算的结果赋值，如无法计算则捕捉异常并赋值为0&lt;/span&gt;
      &lt;span class="n"&gt;this_book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jaccard_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;union&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;

      &lt;span class="n"&gt;this_book&lt;/span&gt;

      &lt;span class="c1"&gt;# 排序&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jaccard_index&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;演示推荐过程：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# 读取数据并定义书籍数组&lt;/span&gt;
&lt;span class="no"&gt;BOOKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DATA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 定义当前书籍&lt;/span&gt;
&lt;span class="n"&gt;current_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ruby programming language"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 进行推荐...&lt;/span&gt;
&lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BookRecommender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BOOKS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;recommendations&lt;/span&gt;

&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="s1"&gt;'%.2f'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jaccard_index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="cp"&gt;__END__
Finding the best language for the job
Could Ruby save the day
Python will rock your world
Is Ruby better than Python
Programming in Ruby is fun
Python to the moon
Programming languages of the future
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是名为「Ruby programming language」的书籍的输出结果，右边的数值就是 Jaccard 指数。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Programming in Ruby is fun (0.50)
Programming languages of the future (0.17)
Is Ruby better than Python (0.17)
Could Ruby save the day (0.14)
Finding the best language for the job (0.12)
Python to the moon (0.00)
Python will rock your world (0.00)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;以上就是纯粹基于 Ruby 的解决方案，&lt;a href="https://gist.github.com/otobrglez/9738998" rel="nofollow" target="_blank" title=""&gt;源代码在我的 gist 上&lt;/a&gt;。我还写了使用标签的 &lt;a href="https://gist.github.com/otobrglez/1078953" rel="nofollow" target="_blank" title=""&gt;PostgresSQL 版本&lt;/a&gt;。但要注意的是，当样本变得很大以后代码的执行速度也会变慢，因此不要每次操作 item 时都运行推荐，更好的方法是定义后台服务在后台进行推荐的计算。&lt;/p&gt;

&lt;p&gt;希望你能够在自己的项目中用到这个方法，也请给我反馈。谢谢！：）&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Tue, 25 Mar 2014 21:29:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/18165</link>
      <guid>https://ruby-china.org/topics/18165</guid>
    </item>
    <item>
      <title>反面模式：用迭代循环来构建集合 (译自 thoughtbot)</title>
      <description>&lt;p&gt;首发：&lt;a href="http://zhaowen.me/blog/2014/03/22/iteration-as-an-anti-pattern/" rel="nofollow" target="_blank"&gt;http://zhaowen.me/blog/2014/03/22/iteration-as-an-anti-pattern/&lt;/a&gt;
原文：&lt;a href="http://robots.thoughtbot.com/iteration-as-an-anti-pattern" rel="nofollow" target="_blank" title=""&gt;Anti-Pattern: Iteratively Building a Collection&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ruby 自带了很多出色的 Enumerable 方法，然而其中最有用的两个方法是传承自 Smalltalk 和 LISP 的 &lt;code&gt;#map&lt;/code&gt; 和 &lt;code&gt;#inject&lt;/code&gt;。一些冗长的方法定义通过使用这两个方法进行改写以后，可以变得既简洁又更加清晰。&lt;/p&gt;
&lt;h2 id="构建一个数组"&gt;构建一个数组&lt;/h2&gt;
&lt;p&gt;需求：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个用户都有一个 PGP 公钥，我想要得到所有用户的公钥以便能够快速地将它们导入密钥服务器。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;第一版代码有些冗长且过于死板。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_key_ids&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但只需简单地使用 &lt;code&gt;#map&lt;/code&gt; ，就能更清晰地表明该方法的用途。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_key_ids&lt;/span&gt;
  &lt;span class="n"&gt;rsigners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="由多个数组构建一个数组"&gt;由多个数组构建一个数组&lt;/h2&gt;
&lt;p&gt;另一个需求：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个用户都有一个 PGP 公钥，我想要一份包括了所有用户的所有 UID 的清单，以便于我能看到他们的名字和地点。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们可以用 &lt;code&gt;#each&lt;/code&gt; 和 &lt;code&gt;#flatten&lt;/code&gt; 来结构化地实现。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_uids&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uids&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而 &lt;code&gt;#map&lt;/code&gt; 更加清晰。注意这里用到了 &lt;code&gt;Symbol#to_proc&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_uids&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:uids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;#inject&lt;/code&gt; 配合 &lt;code&gt;Array#+&lt;/code&gt; 就可以不用在末尾调用 &lt;code&gt;#flatten&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_uids&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uids&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实本例中，使用 &lt;code&gt;#inject&lt;/code&gt; 还不是最直接的方法，我们可以直接使用 &lt;code&gt;Enumerable#flat_map&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_uids&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:uids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="由一个数组构建一个 hash"&gt;由一个数组构建一个 hash&lt;/h2&gt;
&lt;p&gt;又收到了一个需求：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个用户都有一个 PGP 公钥，我想要得到一个散列表来通过每个用户的公钥 id 匹配他的全部 UID，以便于我来构建自己的密钥服务器。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们需要构建一个 hash，而且我们需要使用数组中的每个元素。至少，下面的代码诠释了这一点：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_keys_and_uids&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uids&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实还有另一种诠释的方法：先给定一个空的 hash，把用户数组中的每个元素的公钥 id 到 UID 的配对注入（&lt;code&gt;#inject&lt;/code&gt;）到 hash 中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signer_keys_and_uids&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="由一个数组构建一个 Boolean"&gt;由一个数组构建一个 Boolean&lt;/h2&gt;
&lt;p&gt;他们发誓这个最后一个需求：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个用户都有一个 PGP 公钥，我想要确认我的所有用户都是通过我来认证的，以便于我时常能够互相感觉良好。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面 hash 的例子中我们处理的是另一个 Enumerable。而这里是一个 Boolean，我们先来看一个绕远路的方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutually_signed?&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed_by?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后是下面这种方法，有些眼熟：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutually_signed?&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed_by?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而这个方法太不犀利了，我们可以把它当成一个所有元素必须全部为 true 的 Boolean 的数组：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutually_signed?&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:signed_by?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;身为 Rubyists，我们还应该知道 &lt;code&gt;Enumerable&lt;/code&gt; 有很多其他出色的抽象方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutually_signed?&lt;/span&gt;
  &lt;span class="n"&gt;signers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:signed_by?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="下一步是？"&gt;下一步是？&lt;/h2&gt;
&lt;p&gt;想要更好地培养对 &lt;code&gt;#map&lt;/code&gt;、&lt;code&gt;#inject&lt;/code&gt; 和其他 &lt;code&gt;Enumerble&lt;/code&gt; 方法的感觉，我建议暂时离开 Ruby 一会儿。看看下面这些关于函数式编程的优秀书籍：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://learnyouahaskell.com/" rel="nofollow" target="_blank" title=""&gt;Learn You a Haskell for Great Good&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ccs.neu.edu/home/matthias/BTLS/" rel="nofollow" target="_blank" title=""&gt;The Little Schemer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.amazon.com/Smalltalk-Best-Practice-Patterns-Kent-ebook/dp/B00BBDLIME/ref=tmm_kin_title_0" rel="nofollow" target="_blank" title=""&gt;Smalltalk Best Practice Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你想要阅读更多关于 Ruby 的 &lt;code&gt;#inject&lt;/code&gt; 的文章，可以参阅：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://robots.thoughtbot.com/come-correct-with-inject-and-collect" rel="nofollow" target="_blank" title=""&gt;Come Correct with Inject and Collect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://robots.thoughtbot.com/derive-inject-for-a-better-understanding" rel="nofollow" target="_blank" title=""&gt;Derive #inject for a Better Understanding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Tue, 25 Mar 2014 21:27:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/18164</link>
      <guid>https://ruby-china.org/topics/18164</guid>
    </item>
    <item>
      <title>Preload、 Eagerload、 Includes 和 Joins</title>
      <description>&lt;p&gt;首发：&lt;a href="http://zhaowen.me/blog/2014/03/13/preload-eagerload-includes-and-joins/" rel="nofollow" target="_blank"&gt;http://zhaowen.me/blog/2014/03/13/preload-eagerload-includes-and-joins/&lt;/a&gt;
原文：&lt;a href="http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html" rel="nofollow" target="_blank" title=""&gt;Preload, Eagerload, Includes and Joins&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rails 提供了 4 种方式来加载关联表的数据。在这篇文章中，我们来分别来看看这些方法。&lt;/p&gt;
&lt;h2 id="Preload"&gt;Preload&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; 使用一条附加的查询语句来加载关联数据。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也是 &lt;code&gt;includes&lt;/code&gt; 默认的加载数据的方式。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; 总是会生成两条 SQL 语句，所以我们不能在 where 条件中使用 &lt;code&gt;posts&lt;/code&gt; 表。比如下面的查询就会报错。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts.desc='ruby is awesome'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SQLite3&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SQLException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;such&lt;/span&gt; &lt;span class="ss"&gt;column: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ruby is awesome'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; 也可以指定 where 条件。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.name='Neeraj'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Neeraj'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;  &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Includes"&gt;Includes&lt;/h2&gt;
&lt;p&gt;和 &lt;code&gt;preload&lt;/code&gt; 一样，&lt;code&gt;includes&lt;/code&gt; 也使用一条附加的查询语句来加载关联数据。&lt;/p&gt;

&lt;p&gt;然而，它要比 &lt;code&gt;preload&lt;/code&gt; 更聪明一些。我们刚刚看到了使用 &lt;code&gt;preload&lt;/code&gt; 无法查询 &lt;code&gt;User.preload(:posts).where("posts.desc='ruby is awesome'")&lt;/code&gt;。让我们使用 &lt;code&gt;includes&lt;/code&gt; 来试试看。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts.desc = "ruby is awesome"'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"desc"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; 
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="no"&gt;LEFT&lt;/span&gt; &lt;span class="no"&gt;OUTER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; 
&lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ruby is awesome"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如你所见，&lt;code&gt;includes&lt;/code&gt; 不再使用两条查询语句，而是使用了单独一条 &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt; 语句来获取数据，而且也加载了 where 条件。&lt;/p&gt;

&lt;p&gt;所以，&lt;code&gt;includes&lt;/code&gt; 在某些场合会从两次查询变成一次查询。默认的简单情况下它会使用两次查询。如果出于某些原因，你想强制其使用单次查询，可以使用 &lt;code&gt;references&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"desc"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; 
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="no"&gt;LEFT&lt;/span&gt; &lt;span class="no"&gt;OUTER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的例子就只执行了一次查询。&lt;/p&gt;
&lt;h2 id="Eager load"&gt;Eager load&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;eager_load&lt;/code&gt; 使用 &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt; 进行单次查询，并加载所有的关联数据。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"desc"&lt;/span&gt; &lt;span class="no"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; 
&lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="no"&gt;LEFT&lt;/span&gt; &lt;span class="no"&gt;OUTER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这与 &lt;code&gt;includes&lt;/code&gt; 在 &lt;code&gt;where&lt;/code&gt; 或 &lt;code&gt;order&lt;/code&gt; 语句中指定了 &lt;code&gt;posts&lt;/code&gt; 表的属性的情况下的单次查询完全相同。&lt;/p&gt;
&lt;h2 id="Joins"&gt;Joins&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;joins&lt;/code&gt; 使用 &lt;code&gt;INNER JOIN&lt;/code&gt; 来加载关联数据。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="no"&gt;INNER&lt;/span&gt; &lt;span class="no"&gt;JOIN&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;ON&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的语句不会查询出 &lt;code&gt;posts&lt;/code&gt; 表的数据。这个查询还可能会得到重复的结果，我们先创建一些数据。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;
  &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;

  &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Neeraj'&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'ruby'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s1"&gt;'ruby is awesome'&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s1"&gt;'rails is awesome'&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'JavaScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s1"&gt;'JavaScript is awesome'&lt;/span&gt;

  &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Neil'&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'JavaScript'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s1"&gt;'Javascript is awesome'&lt;/span&gt;

  &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Trisha'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了上面的数据后，当我们执行 &lt;code&gt;User.joins(:posts)&lt;/code&gt; 后得到的结果为&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#&amp;lt;User id: 9, name: "Neeraj"&amp;gt;
#&amp;lt;User id: 9, name: "Neeraj"&amp;gt;
#&amp;lt;User id: 9, name: "Neeraj"&amp;gt;
#&amp;lt;User id: 10, name: "Neil"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要去除重复数据，可以使用 &lt;code&gt;distinct&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'distinct users.*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外，如果我们想要得到 &lt;code&gt;posts&lt;/code&gt; 表中的属性值，就必须显式地 &lt;code&gt;select&lt;/code&gt; 它们。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'distinct users.*, posts.title as posts_title'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts_title&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得注意的是，使用 &lt;code&gt;joins&lt;/code&gt; 就意味着 &lt;code&gt;user.posts&lt;/code&gt; 会执行一次新的查询。&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Fri, 14 Mar 2014 08:05:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/17866</link>
      <guid>https://ruby-china.org/topics/17866</guid>
    </item>
    <item>
      <title>Ruby 中那些你绕不过的「坑」(译)</title>
      <description>&lt;p&gt;首发：&lt;a href="http://zhaowen.me/blog/2014/03/04/ruby-gotchas/" rel="nofollow" target="_blank"&gt;http://zhaowen.me/blog/2014/03/04/ruby-gotchas/&lt;/a&gt;
原文：&lt;a href="http://blog.elpassion.com/ruby-gotchas" rel="nofollow" target="_blank" title=""&gt;Ruby Gotchas that will come back to haunt you&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/2014/991de870fcd5d84329e9d96abc9774e1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;大多数 Ruby on Rails 的初学者们都会为这个出色的框架着迷，在缺乏 Ruby 语言知识的情况下就开始开发应用程序。这也无可厚非。至少，除非这些初学者们坚持了下来，然后摇身一变，成了没有 Ruby 基础知识的「senior」开发者。&lt;/p&gt;

&lt;p&gt;不论如何，不管初学者还是有经验的程序员，迟早都会遇到传说中的「Ruby 的坑」。这些平时埋伏很深的语言上的细微之处将会耗费我们数个小时的死命调试（ &lt;em&gt;puts "1"&lt;/em&gt; ），查明真相后我们会惊呼「怎么会这样？？！」，「好吧，我发誓这一次我会去看那本镐头封面的书！」，又或者，我们喊了声「操！」然后就去睡觉了。&lt;/p&gt;

&lt;p&gt;我在这篇文章中列举了开发者们需要警惕的 Ruby 中常见的坑。我在每个条目中都给出了示例代码，包括了让人迷惑的或容易出错的代码。&lt;/p&gt;

&lt;p&gt;另外，我也给出了最佳实践来简化你（和维护你代码的人）的生活。如果你对「最佳实践」不感冒，你也可以选择阅读详细的解释来了解为什么这个坑会引发 bug（多数情况下是因为它和你所想的不一样）。&lt;/p&gt;
&lt;h2 id="and/or 不同于 &amp;amp;&amp;amp;/||"&gt;
&lt;code&gt;and/or&lt;/code&gt; 不同于 &lt;code&gt;&amp;amp;&amp;amp;/||&lt;/code&gt;
&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;surprise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; surprise 的值为 true&lt;/span&gt;
&lt;span class="n"&gt;surprise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; surprise 的值为 false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;只使用 &lt;code&gt;&amp;amp;&amp;amp; / ||&lt;/code&gt; 运算符。&lt;/p&gt;
&lt;h3 id="详情"&gt;详情&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;and / or&lt;/code&gt; 运算符的优先级比  &lt;code&gt;&amp;amp;&amp;amp; / ||&lt;/code&gt; 低&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;and / or&lt;/code&gt; 的优先级比 &lt;code&gt;=&lt;/code&gt; 低，而 &lt;code&gt;&amp;amp;&amp;amp; / ||&lt;/code&gt; 的优先级比 &lt;code&gt;=&lt;/code&gt; 高&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;and&lt;/code&gt; 和 &lt;code&gt;or&lt;/code&gt; 的优先级相同，而 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 的优先级比 &lt;code&gt;||&lt;/code&gt; 高&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们来给上述示例代码加上括号，这样就可以明显地看出 &lt;code&gt;and&lt;/code&gt; 和 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 在用法上的不同之处了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surprise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; surprise is true&lt;/span&gt;
&lt;span class="n"&gt;surprise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; surprise is false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也有这样的说法：&lt;code&gt;and / or&lt;/code&gt; 用于流程控制，而 &lt;code&gt;&amp;amp;&amp;amp; / ||&lt;/code&gt; 用于布尔型运算。但我认为：不要使用这些运算符的关键字版本（&lt;code&gt;and / or / not&lt;/code&gt;），而使用更为清晰的 &lt;code&gt;if&lt;/code&gt; 和 &lt;code&gt;unless&lt;/code&gt; 等。更加明确，更少困惑，更少 bugs。&lt;/p&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/2083112/difference-between-or-and-in-ruby" rel="nofollow" target="_blank" title=""&gt;Difference between “or” and || in Ruby?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="eql? 不同于 ==（也不同于 equal? 或 ===）"&gt;
&lt;code&gt;eql?&lt;/code&gt; 不同于 &lt;code&gt;==&lt;/code&gt;（也不同于 &lt;code&gt;equal?&lt;/code&gt; 或 &lt;code&gt;===&lt;/code&gt;）&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eql?&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;只使用 &lt;code&gt;==&lt;/code&gt; 运算符。&lt;/p&gt;
&lt;h3 id="详情"&gt;详情&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;==&lt;/code&gt;、&lt;code&gt;===&lt;/code&gt;、&lt;code&gt;eql?&lt;/code&gt; 和 &lt;code&gt;equal?&lt;/code&gt; 都是互不相同的运算符，各自有不同的用法，分别用于不同的场合。当你要进行比较时，总是使用 &lt;code&gt;==&lt;/code&gt;，除非你有特殊的需求（比如你 &lt;strong&gt;真的&lt;/strong&gt; 需要区分 &lt;code&gt;1.0&lt;/code&gt; 和 &lt;code&gt;1&lt;/code&gt;）或者出于某些原因重写（override）了某个运算符。&lt;/p&gt;

&lt;p&gt;没错，&lt;code&gt;eql?&lt;/code&gt; 可能看起来要比平凡的 &lt;code&gt;==&lt;/code&gt; 更为 &lt;strong&gt;聪明&lt;/strong&gt; ，但是你真的需要这样吗，去 &lt;strong&gt;区分两个相同的东西&lt;/strong&gt; ？&lt;/p&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and" rel="nofollow" target="_blank" title=""&gt;What’s the difference between equal?, eql?, ===, and ==?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="super 不同于 super()"&gt;
&lt;code&gt;super&lt;/code&gt; 不同于 &lt;code&gt;super()&lt;/code&gt;
&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Foo#show'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ArgumentError: wrong number of arguments (1 for 0)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;在这里，省略括号可不仅仅是品味（或约定）的问题，而是确实会影响代码的逻辑。&lt;/p&gt;
&lt;h3 id="详情"&gt;详情&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;super&lt;/code&gt;（没有括号）调用父类方法时，会将传给这个方法的参数原封不动地传给父类方法（因此在 &lt;code&gt;Bar#show&lt;/code&gt; 里面的 &lt;code&gt;super&lt;/code&gt; 会变成 &lt;code&gt;super('test')&lt;/code&gt;，引发了错误，因为父类的方法不接收参数）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;super()&lt;/code&gt;（带括号）在调用父类方法时不带任何参数，正如我们期待的那样。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/4632224/super-keyword-in-ruby" rel="nofollow" target="_blank" title=""&gt;Super keyword in Ruby&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="自定义异常不能继承 Exception"&gt;自定义异常不能继承 &lt;code&gt;Exception&lt;/code&gt;
&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyException&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;MyException&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Caught it!'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# MyException: MyException&lt;/span&gt;
&lt;span class="c1"&gt;#       from (irb):17&lt;/span&gt;
&lt;span class="c1"&gt;#       from /Users/karol/.rbenv/versions/2.1.0/bin/irb:11:in `&amp;lt;main&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（上述代码不会捕捉到 &lt;code&gt;MyException&lt;/code&gt;，也不会显示 &lt;code&gt;'Caught it!'&lt;/code&gt; 的消息。）&lt;/p&gt;
&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;自定义异常类时，继承 &lt;code&gt;StandardError&lt;/code&gt; 或任何其后代子类（越精确越好）。永远不要直接继承 &lt;code&gt;Exception&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;永远不要 &lt;code&gt;rescue Exception&lt;/code&gt;。如果你想要大范围捕捉异常，直接使用空的 &lt;code&gt;rescue&lt;/code&gt; 语句（或者使用 &lt;code&gt;rescue =&amp;gt; e&lt;/code&gt; 来访问错误对象）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="详情"&gt;详情&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当你使用空的 &lt;code&gt;rescue&lt;/code&gt; 语句时，它会捕捉所有继承自 &lt;code&gt;StandardError&lt;/code&gt; 的异常，而不是 &lt;code&gt;Exception&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果你使用了 &lt;code&gt;rescue Exception&lt;/code&gt;（当然你不应该这样），你会捕捉到你无法恢复的错误（比如内存溢出错误）。而且，你会捕捉到 SIGTERM 这样的系统信号，导致你无法使用 CTRL+C 来中止你的脚本。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby" rel="nofollow" target="_blank" title=""&gt;Why is it bad style to `rescue Exception =&amp;gt; e` in Ruby?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="class Foo::Bar 不同于 module Foo; class Bar"&gt;
&lt;code&gt;class Foo::Bar&lt;/code&gt; 不同于 &lt;code&gt;module Foo; class Bar&lt;/code&gt;
&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;MY_SCOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Global'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Foo&lt;/span&gt;
  &lt;span class="no"&gt;MY_SCOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Foo Module'&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scope1&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;MY_SCOPE&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo::Bar&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scope2&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;MY_SCOPE&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scope1&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "Foo Module"&lt;/span&gt;
&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scope2&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "Global"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;总是使用长的，更清晰的，module 把 class 包围的写法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Foo&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="详情"&gt;详情&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;module&lt;/code&gt; 关键字（&lt;code&gt;class&lt;/code&gt; 和 &lt;code&gt;def&lt;/code&gt; 也一样）会对其包围的区域创建新的词法作用域（lexical scope）。所以，上面的 &lt;code&gt;module Foo&lt;/code&gt; 创建了 &lt;code&gt;'Foo'&lt;/code&gt; 作用域，常量 &lt;code&gt;MY_SCOPE&lt;/code&gt; 和它的值 &lt;code&gt;'Foo Module'&lt;/code&gt; 就在其中。&lt;/li&gt;
&lt;li&gt;在这个 module 中，我们声明了 &lt;code&gt;class Bar&lt;/code&gt;，又会创建新的词法作用域（名为 &lt;code&gt;'Foo::Bar'&lt;/code&gt;），它能够访问父作用域（&lt;code&gt;'Foo'&lt;/code&gt;）和定义在其中的所有常量。&lt;/li&gt;
&lt;li&gt;然而，当你使用了这个 &lt;code&gt;::&lt;/code&gt; 「捷径」来声明 Foo::Bar 时，&lt;code&gt;class Foo::Bar&lt;/code&gt; 又创建了一个新的词法作用域，名字也叫 &lt;code&gt;'Foo::Bar'&lt;/code&gt;，但它没有父作用域，因此不能访问 &lt;code&gt;'Foo'&lt;/code&gt; 里面的东西。&lt;/li&gt;
&lt;li&gt;因此，在 &lt;code&gt;class Foo::Bar&lt;/code&gt; 中我们只能访问定义在脚本的开头的 &lt;code&gt;MY_SCOPE&lt;/code&gt; 常量（不在任何 module 中），其值为 &lt;code&gt;'Global'&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/15119724/ruby-lexical-scope-vs-inheritance" rel="nofollow" target="_blank" title=""&gt;Ruby – Lexical scope vs Inheritance&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="多数 bang! 方法如果什么都没做就会返回 nil"&gt;多数 &lt;code&gt;bang!&lt;/code&gt; 方法如果什么都没做就会返回 &lt;code&gt;nil&lt;/code&gt;
&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase!&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "FOO"&lt;/span&gt;
&lt;span class="s1"&gt;'FOO'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase!&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;永远不要依赖于内建的 &lt;code&gt;bang!&lt;/code&gt; 方法的返回值，比如在条件语句或流程控制中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@name.upcase&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码会造成一些无法预测的行为（或者更准备地说，我们可以预测到当 &lt;code&gt;@name&lt;/code&gt; 已经是全部大写的时候就会失败）。另外，这个示例也再一次说明了为什么你不应该使用 &lt;code&gt;and/or&lt;/code&gt; 来控制流程。敲两个回车吧，不会有树被砍的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@name.upcase&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="attribute=(value) 方法永远返回传给它的 value 而无视 return 值"&gt;
&lt;code&gt;attribute=(value)&lt;/code&gt; 方法永远返回传给它的 &lt;code&gt;value&lt;/code&gt; 而无视 &lt;code&gt;return&lt;/code&gt; 值&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'OK'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（注意这个赋值方法 &lt;code&gt;bar=&lt;/code&gt; 返回了 &lt;code&gt;3&lt;/code&gt;，尽管我们显式地在最后 &lt;code&gt;return 'OK'&lt;/code&gt;。）&lt;/p&gt;
&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;永远不要依赖赋值方法的返回值，比如下面的条件语句：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Assigned'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'OK'&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显然这个语句不会如你所想。&lt;/p&gt;

&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/6366834/ruby-define-operator-why-cant-control-return-value" rel="nofollow" target="_blank" title=""&gt;ruby, define []= operator, why can’t control return value?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="private 并不会让你的 self.method 成为私有方法"&gt;
&lt;code&gt;private&lt;/code&gt; 并不会让你的 &lt;code&gt;self.method&lt;/code&gt; 成为私有方法&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Not-so-private class method called'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "Not-so-private class method called"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（注意，如果这个方法真的是私有方法，那么 &lt;code&gt;Foo.bar&lt;/code&gt; 就会抛出 &lt;code&gt;NoMethodError&lt;/code&gt;。）&lt;/p&gt;
&lt;h3 id="最佳实践"&gt;最佳实践&lt;/h3&gt;
&lt;p&gt;要让你的类方法变得私有，你需要使用 &lt;code&gt;private_class_method :method_name&lt;/code&gt; 或者把你的私有类方法放到 &lt;code&gt;class &amp;lt;&amp;lt; self&lt;/code&gt; block 中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;    
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bar&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Class method called'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;    
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baz&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Another class method called'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;private_class_method&lt;/span&gt; &lt;span class="ss"&gt;:baz&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; NoMethodError: private method `bar' called for Foo:Class&lt;/span&gt;
&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baz&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; NoMethodError: private method `baz' called for Foo:Class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;延伸阅读：&lt;a href="http://stackoverflow.com/questions/4952980/creating-private-class-method" rel="nofollow" target="_blank" title=""&gt;creating private class method&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="我才不怕这些 Ruby 的坑！"&gt;我才不怕这些 Ruby 的坑！&lt;/h2&gt;
&lt;p&gt;上面列举的 Ruby 的坑可能看上去没什么大不了的，乍一看似乎只是属于代码风格和约定的范畴。&lt;/p&gt;

&lt;p&gt;但相信我，如果你不重视它们，终有一天你会在 Ruby on Rails 的开发过程中碰到诡异无比的问题。它会让你心碎。因为你已经累觉不爱。然后孤独终老。永远。&lt;/p&gt;</description>
      <author>zhaowenchina</author>
      <pubDate>Sat, 08 Mar 2014 15:51:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/17742</link>
      <guid>https://ruby-china.org/topics/17742</guid>
    </item>
  </channel>
</rss>
