<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>redemption (hym)</title>
    <link>https://ruby-china.org/redemption</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>一次写 API wrap 的尝试</title>
      <description>&lt;p&gt;最近想学习写 API wrap，所以就对照 Github API 尝试写了起来。在扫过 Github API 文档之后，看到每一类 API 几乎都有 Create, Update, Delete, Get 这四种操作。所以当时脑中就想写出类似 rails model 中常使用的&lt;code&gt;User.create&lt;/code&gt;、&lt;code&gt;User.find&lt;/code&gt;、&lt;code&gt;@user.update&lt;/code&gt;、&lt;code&gt;@user.delete&lt;/code&gt; 的这些方法。经过一些尝试之后最终失败了。现在看来失败最主要的原因其实是抽象不正确，我这里想实现的东西其实并不是 API wrap，其本质是一个映射系统。&lt;/p&gt;

&lt;p&gt;在写这个文章的过程中，看到论坛之前帖子讨论到 Active Resource 其实实现的就是我这里要做的映射系统，所以我研究了相关的源码。所以下面我会将自己在写代码过程中遇到的问题与使用 Active Resource 结合起来。&lt;/p&gt;
&lt;h2 id="Active Resource 相关介绍"&gt;Active Resource 相关介绍&lt;/h2&gt;&lt;h5 id="接口"&gt;接口&lt;/h5&gt;
&lt;p&gt;为了方便后面叙述，这里需要先对 Active Resource 的部分内容作一个简要介绍。
首先，Active Resource 是为 REST web services 提供映射的，那么 API 的 URL 和 HTTP Verb 要遵守 REST 的约定。简单例子来说就是如下的形式：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET         /resources/:id
POST        /resources
PATCH(PUT)  /resources/:id 
DELETE      /resources/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过现在 Active Resource 并不支持 PATCH，所以在 Active Resource 眼中，每一个资源都有如下的 API 接口：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET      /resources/:id
POST     /resources
PUT      /resources/:id 
DELETE   /resources/:id
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="URL 结构"&gt;URL 结构&lt;/h5&gt;
&lt;p&gt;我们首先来定义一个名为 &lt;code&gt;resource&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;Resource&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&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;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://www.example.com"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于上面的资源，我们进行查找操作&lt;code&gt;Resource.find(1, params: { query: 1 })&lt;/code&gt;会产生如下的 URL：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/eeefb8cd9eb7e48dfa5ff0e8ded7e7e9.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;各个颜色在 Active Resource 代码中代表的含义如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;橙色：称为 prefix。&lt;/li&gt;
&lt;li&gt;绿色：表示的是资源本身相关的信息，被表示为 element_path；如果没有相关 id 信息 (例如这个部分只有 /resources)，则称为 collection_path&lt;/li&gt;
&lt;li&gt;红色：资源的格式扩展名（目前支持 json 和 xml），被表示为 fomat_extension&lt;/li&gt;
&lt;li&gt;紫色：请求的额外信息，被表示为 query_string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;prefix 的部分主要由 &lt;code&gt;site&lt;/code&gt;、&lt;code&gt;prefix&lt;/code&gt;、&lt;code&gt;prefix=&lt;/code&gt;、&lt;code&gt;prefix_options&lt;/code&gt; 这些函数共同完成定义。prefix 除了前面例子中那么定义之外，我们还可以在 site 中添加相关的变量。例如：&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;Repo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&lt;/span&gt;
  &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.com/:user_id/:emoji"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过上面的定义，我们如果要查找某个 repo，除了正常要提供的信息，还需要提供 user_id、emoji，所以相关查找如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GET https://example.com/2/smile/1&lt;/span&gt;
 &lt;span class="no"&gt;Repo&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;user_id: &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;emoji: &lt;/span&gt;&lt;span class="s2"&gt;"smile"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;element_path 与 collection_path 主要是根据自身定义的 class 的名称来生成。element_path 中的 id 值，主要由定义的 primary_key 的值给出。例如&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;Resource&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&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;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;resource&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="s2"&gt;"name"&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "name"&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "name"&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;element_path&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "/resources/name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 resource 本身有 id 属性呢？比如上面的 resource 中有属性 id 的值为 1，那么我们如何设置和获取到这个值呢？在下面的讨论中，你会知道答案。&lt;/p&gt;
&lt;h5 id="属性"&gt;属性&lt;/h5&gt;
&lt;p&gt;我们定义的每一个 resource 类对应的是 server 上的一类资源，每个资源都会有它的属性和相应的值&lt;/p&gt;
&lt;h6 id="属性值的设置与获取"&gt;属性值的设置与获取&lt;/h6&gt;
&lt;p&gt;所有属性的名称和对应的值都存放在 resource 实例的 &lt;code&gt;@attributes&lt;/code&gt; 这个变量中。&lt;/p&gt;

&lt;p&gt;例如，我们根据如下 JSON 信息创建了一个 resource:&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1296269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello-World"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&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="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {"name"=&amp;gt;"Hello-World", "id"=&amp;gt;1296269}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而我们可以直接去读取和设置 name 和 id 的值，其实就是通过 &lt;code&gt;method_missing&lt;/code&gt; 的黑魔法来实现的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&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="s2"&gt;"haha"&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt;  "haha"&lt;/span&gt;
&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {"name"=&amp;gt;"haha", "id"=&amp;gt;1296269}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以回到上一节的那个问题，如果我们设置的 primary_key 并不是 id，那么我们要取得 id 的值就可以通过 &lt;code&gt;@attributes&lt;/code&gt;来获得。&lt;/p&gt;
&lt;h6 id="属性的定义"&gt;属性的定义&lt;/h6&gt;
&lt;p&gt;对于我们定义的每一个 resource，我们如何去确定它有哪些属性呢？Active Resource 中，相关属性可以通过 schema 去定义，也可以让代码通过获得的 JSON 信息去分析。&lt;/p&gt;

&lt;p&gt;下面我举例来说明一下如何根据获得的 JSON 来定义属性的。假如我们获得 Repo 如下：&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;Github::Repo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&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;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://example.com'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而我们获得的 json 信息如下：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1296269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"forks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1296269&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于上面的 JSON，键值对中的值如果不是 Object 或者 Array，那么对应的键就成为 resource 的属性，而相应的值就是属性的值。所以通过上面的 JSON 信息，我们的 repo 信息如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;    &lt;span class="c1"&gt;# =&amp;gt; 1296269&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; "Hello-World"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON 中的值如果是 object 的话，那么 Active Resource 就会从现在的 namespace 开始到最顶层的 namespace 中去寻找我们是否定义了相关的类（例如上面的例子中，就会从 Github::Repo 这个 namespace 到 Github 再到最顶层的 namespace 中去查找我们是否定义了 Owner 这个类），如果没有定义相应的类，那么就在最底层的 namespace 中去定义一个这样的类（也就是会去定义一个 Github::Repo::Owner），而这个类的属性就重复一样的规则通过 object 的信息得到。也就是我们去查询 owner 时：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; Github::Repo::Owner&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "octocat"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON 中的值如果是 Array，Active Resource 会先检查这个属性是不是 association。在上面的例子中也就是会去看是否有下面的关系：&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;Github::Repo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;ActiveResource&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:forks&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果定义了这种关系，就利用我们创建的 Fork 类去实例化 array 中的每一个 object。如果没有定义这种关系，同样的将名字变成单数后重复上面 object 的查找过程，然后就用查找到或者新创建的类去实例化 array 中的每个 object。&lt;/p&gt;

&lt;p&gt;上面经过处理的信息，最终都保存在对象的 &lt;code&gt;@attributes&lt;/code&gt; 属性中。&lt;/p&gt;

&lt;p&gt;我这里讲述得可能不太清楚，更详细的了解大家可以去看源码中 &lt;a href="https://github.com/rails/activeresource/blob/master/lib/active_resource/base.rb#L1387" rel="nofollow" target="_blank" title=""&gt;load&lt;/a&gt; 这个函数。&lt;/p&gt;

&lt;p&gt;在了解以上关于 Active Resource 的相关内容之后，我们进入相关正题。&lt;/p&gt;
&lt;h2 id="需解决的问题汇总"&gt;需解决的问题汇总&lt;/h2&gt;
&lt;p&gt;这里先列出要实现该映射需要克服的困难&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API 不规律

&lt;ul&gt;
&lt;li&gt;URL 不规律&lt;/li&gt;
&lt;li&gt;HTTP Verb 不规律&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API 返回的数据与 API 能编辑的数据不同&lt;/li&gt;
&lt;li&gt;Associate 关系的表示&lt;/li&gt;
&lt;li&gt;支持不同的  Mime type &lt;/li&gt;
&lt;li&gt;查询的优化&lt;/li&gt;
&lt;li&gt;多 client 验证的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="分析问题及利用 Active Resource 解决的办法"&gt;分析问题及利用 Active Resource 解决的办法&lt;/h2&gt;
&lt;p&gt;在具体探讨相关问题之前，我们在此明确我们要达到的目的，我们想要达到的主要是建立一个资源的基类，这个基类的作用就像 ActiveRecord::Base 一样，其他资源类通过继承这个基类，我们就能够不用做配置或者进行极少配置后，就能够通过完成基本的 CURD 操作。我们这里并不真的要将所有 Github API 全部涵盖进来，只是讨论是否能涵盖所有资源的 CRUD 操作。&lt;/p&gt;

&lt;p&gt;明确完这一点后，我们开始讨论相关细节。&lt;/p&gt;
&lt;h3 id="API 不规律"&gt;API 不规律&lt;/h3&gt;
&lt;p&gt;我们来看 &lt;a href="https://developer.github.com/v3/repos/" rel="nofollow" target="_blank" title=""&gt;Repo&lt;/a&gt; 和 &lt;a href="https://developer.github.com/v3/repos/contents" rel="nofollow" target="_blank" title=""&gt;Content&lt;/a&gt; 的相关 CURD 操作的接口：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Repo 相关接口
POST /user/repos   # =&amp;gt; Create
GET /repos/:owner/:repo   # =&amp;gt; Get
PATCH /repos/:owner/:repo   # =&amp;gt; Edit
DELETE /repos/:owner/:repo   # =&amp;gt; Delete
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Content 相关接口
PUT /repos/:owner/:repo/contents/:path   # =&amp;gt; Create
GET /repos/:owner/:repo/contents/:path   # =&amp;gt; Get
PUT /repos/:owner/:repo/contents/:path   # =&amp;gt; Update
DELETE /repos/:owner/:repo/contents/:path   # =&amp;gt; Delete
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="URL 不规律"&gt;URL 不规律&lt;/h5&gt;
&lt;p&gt;通过 Repo 的 Create API 可以看到，它并不是我们所期望的 &lt;code&gt;/repos&lt;/code&gt;，而是前面有一些前缀。这个 URL 从它本身的意义来说，这样写是很正确的，因为 repo 是属于某个 user 的，而这个 user 也就是通过验证的用户，这个用户的身份信息也已经通过 token 进行了唯一定义，所以并不需要通过 &lt;code&gt;/users/:id/repos&lt;/code&gt; 这样的 URL 来体现所属关系。&lt;/p&gt;

&lt;p&gt;同样，在 Github 中每一个 Repo 是由 owner 与 repo 来定位，并不是用一个 id 来定位，所以在实现映射系统的时候需要允许 primary_key 由多个数据项来定义。&lt;/p&gt;

&lt;p&gt;虽然如上所说，但是这却给我们实现映射关系设置了一道坎。&lt;/p&gt;
&lt;h5 id="HTTP Verb 不规律"&gt;HTTP Verb 不规律&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt; Repo 和 Content 的 Create 方法使用的 Verb 是不同的&lt;/li&gt;
&lt;li&gt; Repo 和 Content 修改信息的 Verb 是不同的&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里所说的 Verb 不规律只是对我们实现映射关系来说不规律，但是 Github API 本身使用 HTTP Verb 本身是非常严谨的。&lt;/p&gt;
&lt;h6 id="POST 与 PUT 的差别："&gt;POST 与 PUT 的差别：&lt;/h6&gt;
&lt;p&gt;根据 &lt;a href="http://www.rfcreader.com/#rfc2616_line2527" rel="nofollow" target="_blank" title=""&gt;HTTP 标准&lt;/a&gt;中的说明，POST 与 PUT 方法最主要的区别就是在于对请求中 URI 的涵义解释的不同。对于 POST 方法来说，请求中 URI 指定的 resource 会去处理请求中所包含的这些 entity。但是 PUT 方法中 URI 是 identify 了请求中的 entity。我们来简单举一个例子：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 名为 demo-user 的用户创建一个名为 demo 的 repo
POST /user/repos  # { "name": "demo" }

# 然后服务器为我们创建了 demo 这个 repo它的位置为  /repos/demo-user/demo
# 我们要获取这个资源要访问的是 
GET /repos/demo-user/demo
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 但是我们创建一个 content
PUT /repos/demo-user/demo/contents/demo

#当我们要访问这个资源的时候，我们用的还是同样的 URL
GET /repos/demo-user/demo/contents/demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以对于 POST 方法，URI 只是表明 URI 指向的 resource 会去处理这个请求中的 entity，但是并不表明这个 URI 指向的 resource 就是请求中的 entity。但是对于 PUT 方法来说，URI 指向的 resource 就是请求中 entity 会存放的位置，如果这个 entity 在其他位置，那么服务器就需要返回 301 进行重定向。（上面这些是我个人的理解，如有不正确的地方，请指正）&lt;/p&gt;
&lt;h6 id="Repo 和 Content 中修改信息时所用 Verb 的不同"&gt;Repo 和 Content 中修改信息时所用 Verb 的不同&lt;/h6&gt;
&lt;p&gt;细心的人肯定发现了，Repo 中修改信息的 API 叫做 Edit，Content 中求改信息的 API 叫做 Update。在 Github API 中可以看到文档区分了 Edit 与 Update 的含义。&lt;/p&gt;

&lt;p&gt;看到这里，我就去了解了一下 PUT 与 PATCH 的差别。我查到 PATCH 方法在 &lt;a href="http://www.rfcreader.com/#rfc5789" rel="nofollow" target="_blank" title=""&gt;rfc5789&lt;/a&gt; 中进行补充定义的。该文档中说明了，PUT 方法虽然可以进行更新，但是它的含义是用新的信息对整个资源进行替换。而 PATCH 方法定义的是对资源进行部分的修改。&lt;/p&gt;

&lt;p&gt;然后回到这里的 API，对于 Repo 来说，我们修改信息用 PATCH 是没有任何疑问的。而对于 Content 来说，为什么使用 PUT 呢？ 
我个人理解是，对于更新 content 来说，一次更新就会创建一个 commit，创建一个 commit 之后整个 repo 就更新了一个版本，也就可以理解为 content 的内容被新版本的内容完全替代了。&lt;/p&gt;

&lt;p&gt;上面说了这么多 Github API 写得棒，但是对于我们想实现的东西来说，这些都是阻碍。&lt;/p&gt;
&lt;h4 id="Active Resource 处理相关问题的方法"&gt;Active Resource 处理相关问题的方法&lt;/h4&gt;
&lt;p&gt;首先我们来看能否利用 Active Resource 提供的功能来解决 Repo API 相关的问题。
Repo API 主要有两个地方比较特殊：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create 方法的路径并不是要求的 &lt;code&gt;/repos&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Repo 的 primary_key 由 &lt;code&gt;owner&lt;/code&gt; 和 &lt;code&gt;repo&lt;/code&gt; 两个值构成&lt;/li&gt;
&lt;li&gt;Repo 的 Edit 要求的 Verb 是 PATCH，但是 Active Resource 是 PUT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于第一个问题和第三个问题，Active Resource 并没有相关办法去解决，因为这些代码是写死了的。
对于第二个问题，如果我们去看 Repo 的 API 文档，我们可以看到 API 返回的信息如下：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1296269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"site_admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat/Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们主要关注 full_name 这个属性的值，如果我们将 API 中 &lt;code&gt;:owner/:repo&lt;/code&gt; 整个看做一个值，那么 full_name 正是我们要设置的 primary_key。所以我们可以如下创建 Repo。&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;Repo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&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;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:full_name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Repo&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="s2"&gt;"rails/rails"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; GET /repos/rails/rails&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "rails/rails"&lt;/span&gt;
&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;DELETE&lt;/span&gt; &lt;span class="sr"&gt;/repos/&lt;/span&gt;&lt;span class="n"&gt;rails&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rails&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而对于 Content 的 API，除了无法实现 Create 方法之外，其他的操作通过定义下面的定义来实现：&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;Content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&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;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://api.github.com/repos/:owner/:repo"&lt;/span&gt;
  &lt;span class="n"&gt;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:path&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;# GET /repos/rails/rails/contents/readme&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Content&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="s2"&gt;"readme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;repo: &lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; PUT /repos/rails/rails/contents/readme&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; DELETE /repos/rails/rails/contents/readme&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="API 返回的数据与 API 能编辑的数据不同"&gt;API 返回的数据与 API 能编辑的数据不同&lt;/h3&gt;
&lt;p&gt;从 Github API 文档中我们可以看到，我们从 JSON 中或许的信息非常多，但是这些信息中只有部分是我们能修改的，例如 Repo 给我们返回的信息：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1296269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"followers_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/octocat/followers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"following_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/octocat/following{/other_user}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gists_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/octocat/gists{/gist_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"starred_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/octocat/starred{/owner}{/repo}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subscriptions_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/octocat/subscriptions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"site_admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat/Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This your first repo!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fork"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/repos/octocat/Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/octocat/Hello-World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"archive_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contributors_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://api.github.com/repos/octocat/Hello-World/contributors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deployments_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://api.github.com/repos/octocat/Hello-World/deployments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downloads_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://api.github.com/repos/octocat/Hello-World/downloads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是 Repo 给出的 Edit API 接口中，我们能修改的信息只是获得的信息的一个子集。这与我们平时操作 Rails 中的 Model 就不一样了，对于 Rails 中的 Model，我们从数据库获得的所有模型的数据都是可以进行编辑的。&lt;/p&gt;

&lt;p&gt;所以在我们实现映射功能的时候，我们有三个选择：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;无视 API 的限制，所有获得的数据都可以进行修改，并将所有修改过和没修改过的数据交给服务器，让服务器本身去判断哪些数据可修改，哪些不可修改。例如，对于上面获得的信息:
&lt;code&gt;ruby
# 我们可以对本身不能修改的数据进行修改
repo.private = true
# 然后将以上的所有数据交给服务器
repo.save  # { id: "id": 1296269 ....   fork: false, private: true ...}
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;我们同样对获得的数据都可以进行修改，但是我们对服务器只提交 API 允许修改的值
&lt;code&gt;ruby
# 我们可以对本身不能修改的数据进行修改
repo.private = true
repo.save  # { name: "name", descption: "desc"}
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; 我们对能修改的数据进行控制
&lt;code&gt;ruby
repo.private = true # =&amp;gt; Error!
&lt;/code&gt;
#### Active Resource 处理相关问题的方法
利用 Active Resource 我们能轻松的实现上面的 1、2 解决方法。如果要实现 3 这种解决方法，需要额外打一些补丁。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于 1 方法来说，Active Resource 本身代码就是这样实现的，所以不需要额外工作。&lt;/p&gt;

&lt;p&gt;对于 2 方法来说，我们需要在我们定义的每一个 resource 中定义如下的方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="ss"&gt;:old_encode&lt;/span&gt; &lt;span class="ss"&gt;:encode&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;old_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;only: &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="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;only 后面的 array 中就是定义的我们所允许传给服务器的相关数据。&lt;/p&gt;
&lt;h3 id="支持不同的  MIME type"&gt;支持不同的  MIME type&lt;/h3&gt;
&lt;p&gt;Github API 除了支持 JSON 外，还可以支持一些其他的格式。&lt;/p&gt;

&lt;p&gt;Active Resource 目前只支持 JSON 和 XML，但是由于 Active Resource 的代码组织的非常好，我们只需要在 ActiveResource::Formats 的 namespace 中定义想要的 MIME type，并给 ActiveResource::Formats 模块打上补丁，用于载入新定义的格式。相关实现细节，可以参考 Active Resourse 中已实现的功能。&lt;/p&gt;

&lt;p&gt;当然说到 MIME type 这里，大家可能注意到 Active Resource 发出的请求中，会在 URL 中加入 MIME typed 的格式扩展名。但是 Github API 并不支持扩展名。我们可以通过在 Resource 类中进行如下设置来去掉这个扩展名：&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;Resource&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&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;include_format_in_path&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;/code&gt;&lt;/pre&gt;&lt;h3 id="查询的优化"&gt;查询的优化&lt;/h3&gt;
&lt;p&gt;在使用 Rails Model 的时候，我们很熟悉对于 &lt;code&gt;where&lt;/code&gt;、&lt;code&gt;all&lt;/code&gt; 会延迟查询。对于一般&lt;code&gt;find&lt;/code&gt;查询操作，由于数据库执行很快，所以不用做相关优化。&lt;/p&gt;

&lt;p&gt;但是对于通过 Web API 实现的映射系统来说，由于网络传输的开销很大，所以对于每一个查询能做优化，能延迟查询都很重要的。例如：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Resource&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于目前来说，上面的代码会经历两个 HTTP 响应：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Resource&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; GET /resources/1&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; DELETE /resource/1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是从我们要达到的目的来说，其实只要执行最后一个 HTTP 请求就可以了。&lt;/p&gt;

&lt;p&gt;当然优化也存在一些困难需要克服，例如：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"f"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于第二行的两个操作发起两个 HTTP 请求是不能简化的。从这个例子来看，貌似只要遇到非 GET 请求就不能优化了，但是如果把上面两个操作反过来呢？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"haha"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况可以进行优化，但是有的情况也不能优化。比如 update 不仅仅是更新 name 的信息，而是有其他的副作用的话，那么这两个操作就不能够优化。&lt;/p&gt;

&lt;p&gt;Active Resource 目前没有做这些方面的工作，只有遇到需要发出请求的操作，Active Resource 就会立即发出请求。&lt;/p&gt;
&lt;h3 id="多用户验证问题"&gt;多用户验证问题&lt;/h3&gt;
&lt;p&gt;假设我们客服了上面的所有问题。我们得到什么东西呢？我们得到的是 Repo、Branch 等等一堆类，其它人要去使用的时候，那么就需要先了解有哪些类，然后根据需要调用相关的类去完成功能。假设大家能够接受这样的东西，那么我们使用这些类的时候还有一个问题就是，GIthub 有些 API 是需要验证才能使用的。不过这个问题也好解决，Active Resource 提供了 Basic authentication，可以用于验证：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt;
&lt;span class="no"&gt;Repo&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="s2"&gt;"password"&lt;/span&gt;
&lt;span class="no"&gt;Repo&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt;  请求会携带用户名和密码&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是如果我们要在代码中要同时访问两个 user 的 repo 呢？那这样就需要不停的更换 Repo 类的 user 和 password，这明显很不方便，而且转换次数多了还容易引起混乱。所以这也最终决定了这种形式不能成为一个 API wrap。&lt;/p&gt;
&lt;h2 id="这篇文章对大家的价值"&gt;这篇文章对大家的价值&lt;/h2&gt;
&lt;p&gt;这个文章我回头看一下，好像对大家的价值并没有多少，可能最大的价值就是帮助大家了解一下 Active Resource  吧。&lt;/p&gt;</description>
      <author>redemption</author>
      <pubDate>Sun, 25 Dec 2016 20:42:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/32001</link>
      <guid>https://ruby-china.org/topics/32001</guid>
    </item>
    <item>
      <title>Rack Middleware 新的理解</title>
      <description>&lt;h2 id="疑惑"&gt;疑惑&lt;/h2&gt;
&lt;p&gt;最近用到一个 gem 叫做 rest-client-component，它的主要作用就是让 restclient 能够添加中间件，从而使得在发出 http 请求之前或者在收到 http 应答之后对请求和应答做相关处理。但是在使用的时候，我发现它所添加的中间件是 rack 的中间件。这就引起了我的好奇。&lt;/p&gt;
&lt;h2 id="分析"&gt;分析&lt;/h2&gt;
&lt;p&gt;rack 本身是定义的 web server 与 web framework 之间的接口，那么在我印象中 rack 相关的中间件也一定是用于服务端的，这怎么可以用于客户端呢？于是就仔细研究了相关资料和源代码，才发现自己的之前的“认为“过于肤浅。&lt;/p&gt;

&lt;p&gt;我们首先来看一个图，这个图描述了一次对基于 rack 的 web app 的请求应答的过程图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/09dcbc1ad6e4e74ad1a0a89e25716cad.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从这个图中我们可以看到当一个 http 请求来临的时候，这个请求中的相关信息通过 server 和 rack handler 转换为了 env 中相关的内容，然后再经过 rack middleware 相关处理，最后经由 rack adapter 变为 web app 能够识别的信息。当不存在 rack middleware 的时候，那么传入 web app 的 env 的本身其实就是 client 发出的 request，他们之间的差别只是格式不同，但所表达的信息是一样的（当然这里说法并不准确，后面有解释）。&lt;/p&gt;

&lt;p&gt;同样，当 web app 处理请求完成后，其处理的结果由 rack adapter 转换成了 [status, headers, [body]] 的形式，然后经过 rack middleware 处理，最后通过 rack handler 和 server 的处理，将这些信息变成一个 response 返回给客户端。那么当不存在 middleware 的时候，web app 返回的 [status, headers, [body]] 本质上与最后返回给客户端的 response 是一样的。&lt;/p&gt;

&lt;p&gt;所以我们就可以将 env 和 [status, headers, [body]] 与 request 和 response 等同起来。那么从这里我们就可以看出来，rack middle 本身的作用其实就是处理 request 和 response。rack middleware 的作用就像&lt;a href="http://rubylearning.com/blog/2013/04/02/whats-rack/" rel="nofollow" target="_blank" title=""&gt;这篇文章&lt;/a&gt;说的：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the server, and processing the HTTP response before returning it to the client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这句话改成这样或许更准确一点&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fundamental idea behind Rack middleware is – come between the calling client and the server, process the HTTP request before sending it to the &lt;del&gt;server&lt;/del&gt; &lt;code&gt;web application&lt;/code&gt;, and processing the HTTP response before returning it to the client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这句话说的是 rack middleware 本身出现的目的。但是我们可以将 rack middleware 理解得更加广一点。rack middleware 的作用是处理 request 和 response 的，它就像是一根电线，电线里面有火线和零线，而火线和零线分别对应这里的 request 的通道和 response 的通道，而每个完整的请求应答信息 (env 与 [status, header, [body]]) 就像电流。就如下图一样：(当然这个电线也可能会短路。。)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/9b71cf73911a9582a5217aedc61b2f98.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;电线可以加入进电流通路的任何地方。那么同样的 middleware 为什么不能够加入到 http 请求应答路径所经过的任何地方呢？ &lt;/p&gt;

&lt;p&gt;客户端本身就是每一个 http 请求应答所会经过的必经之路，那么当然 rack middleware 也就能够放在客户端使用了。当然由于 rack middleware 处理的 request 和 response 的格式并不一样，所以我们就要自己写格式相关的代码。当 rack middleware 放在客户端的时候，其结构就如下图 (以 restclient 为例)：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/6d5ed36c4cd78fb5ca0ef1bb755ba7e1.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;当然，由于没有了 rack 的支持，client 还要实现 middleware 栈的调用机制。&lt;/p&gt;
&lt;h2 id="补充"&gt;补充&lt;/h2&gt;
&lt;p&gt;上面说当不存在 middleware 的时候，request 内容等同于 env。其实现实情况不一定，因为服务器也可能会对 request 做一些处理（比如 webrick 可以调用一些 callback），当然这并不损 env 代表一个 request 的事实。&lt;/p&gt;
&lt;h2 id="其他想法"&gt;其他想法&lt;/h2&gt;
&lt;p&gt;从 rack middleware 这个问题，我突然意识到，任何东西我们都要尽量要去掌握它的本质，而不要只看到它的表面或者它出现的背景。不然我们很可能无法广泛灵活的去应用这些东西。突然感觉我们日常生活中其实处处存在这个道理。大到核能的应用，核能最早出现也是用于战争，但是理解了它的本质，它也能用于人类的生产生活。小到日常生活中，我们会用凳子当做梯子使用，用废弃的饮料瓶作为花瓶。所以既然在生活中能够将一种东西按照他本身的特性去使用它，而并不只是局限于它出现的直接目的去使用它。那么在技术中，我们或许也可以视情况用这种思维去使用它们，或许有不同的惊喜。&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;rack middleware 本身存在的意义就是处理应用逻辑之外针对 http 本身的相关处理，既然针对 http 本身的处理，那么只要 middleware 本身的功能合适，那么这个 middleware 就可以放在整个 http 请求应答路径上的任何地方，也就包括这里的客户端。&lt;/li&gt;
&lt;li&gt;任何东西我们都要尽量要去掌握它的本质，可以根据它的本质去灵活使用，而不只是根据它的表面或者它出现的直接目的去使用它。这样或许我们能看到另一篇天地。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还有就是我是菜鸟，请大家多多指教：）&lt;/p&gt;</description>
      <author>redemption</author>
      <pubDate>Tue, 29 Nov 2016 23:47:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/31737</link>
      <guid>https://ruby-china.org/topics/31737</guid>
    </item>
    <item>
      <title>如何避免代码的重复引入？</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;我遇到这么一个场景。比如我有两个 module（Searchable, Filterable）和一个使用这两个 module 的类 A。
定义如下：&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;Searchable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Filterable&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;A&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Filterable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在 Searchable、Filterable、A 中，他们里面包含的 method 很多都会用到一组公共的 methods，为了避免代码重复，我的一个想法是讲这些公共方法也作为 module 提取出来（例如叫做 Pub）。那么上面的代码就会变成下面这样：&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;Pub&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Searchable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pub&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Filterable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pub&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;A&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pub&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Filterable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面我之所以 3 个地方我都 include 了 Pub 是因为：
  1、Searchable 和 Filterable 两个 module  可能被其他地方重复使用，所以为了保证代码能够运行，所以需要包含。
  2、在 class A 中 include Pub 主要是设想一种情况，就是我并不知道 Searchable 这些 module 中已经 include Pub，但是我需要用到 Pub 的功能，所以我 include 了 Pub (当然，如果完全是自己写的代码肯定不会出现这种情况）&lt;/p&gt;

&lt;p&gt;问题：
  所以在上面的代码结构中，我们其实是在 class A 中多次 include 了 Pub 这个 module，虽然看到相关资料说 include 并不会实际去 copy 代码过来，只会产生一个指向 Pub module 的 reference，但是这样毕竟还是重复引用了。&lt;/p&gt;
&lt;h2 id="我的疑问"&gt;我的疑问&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;上面我的这种解决代码重复方法（就是将公用方法放入一个 module 中去）是否正确？如果不正确，更好的应该是什么方式&lt;/li&gt;
&lt;li&gt; 在不考虑我用这种方式解决代码复用是否正确的前提下。单纯考虑上面那种代码结构，对于这种重复引入应该怎么解决？&lt;/li&gt;
&lt;li&gt; 各位能否给提供些学习资料（我 google 了半天真没找到，可能关键字没用对，英文不是很好），主要就是代码相互引用这些方面的。除了这里的 module，我对于代码文件之间如何 require，才不会造成重复 require 同一个文件的问题也不是非常明白。 &lt;/li&gt;
&lt;/ol&gt;</description>
      <author>redemption</author>
      <pubDate>Sat, 16 Apr 2016 22:20:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/29744</link>
      <guid>https://ruby-china.org/topics/29744</guid>
    </item>
    <item>
      <title>Rails association 中的 foreign keys</title>
      <description>&lt;h2 id="问题背景"&gt;问题背景&lt;/h2&gt;
&lt;p&gt;以 belongs_to association 举例：&lt;/p&gt;

&lt;p&gt;在添加 belongs_to 关系的时候，需要在 table 中创建 foreign Keys。比如下面的例子：&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;Order&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立上面的关系的时候，order table 是如下方式建立的&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;CreateOrders&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;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;  &lt;span class="ss"&gt;:customer_id&lt;/span&gt;
      &lt;span class="err"&gt;······&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:customer_id&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrders&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;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="err"&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过观察 rails 中 belongs_to 方法的源码发现，这两个代码是等价的。&lt;/p&gt;

&lt;p&gt;通过观察发现，belongs_to 的 association 中在 table 上创建 foreign keys，也等同于在 table 中建立一个 存放 id 的列，并在该 column 上建立了一个 index。所以这里的 foreign keys 并不是 database 中的 foreign keys。&lt;/p&gt;

&lt;p&gt;其他的 association 的 foreign keys 也是同样的建立方法。&lt;/p&gt;
&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;理论上来说是否任何一个 integer 的 column 上建立一个 index 都能成为 rails association 中的 foreign keys 呢？&lt;/li&gt;
&lt;li&gt;这种 foreign keys 是否在功能上代替了 database 中 foreign keys 的作用了呢？在我个人看来好像是的，&lt;/li&gt;
&lt;li&gt;如果这里 foreign keys 在功能上代替了 database 中的 foreign keys 了话，那么在 database 中去建立 foreign keys 还有什么作用呢？如果无法在功能上代替 foreign keys 的话，那为什么上面的例子中不去在 database 中添加 foreign keys 呢？&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;功能上替代的意思：对于 database 中的 foreign key 的作用，（我认为）只是用于 join table。&lt;/p&gt;</description>
      <author>redemption</author>
      <pubDate>Tue, 05 Jan 2016 16:23:25 +0800</pubDate>
      <link>https://ruby-china.org/topics/28617</link>
      <guid>https://ruby-china.org/topics/28617</guid>
    </item>
    <item>
      <title>阅读 God 源码心得</title>
      <description>&lt;p&gt;大家好，我是 Ruby 新人，同时也是 Web 编程的新手。虽然我从去年开始断断续续接触 Ruby，但是真的进入 Ruby 的世界是从今年 3 月份在&lt;a href="http://sike.io/" rel="nofollow" target="_blank" title=""&gt;思客&lt;/a&gt;远程实习后正式开始的。在这半年多的时间中，我在社区学到了很多东西，同时也提了不少很“二”的问题。&lt;/p&gt;

&lt;p&gt;虽然作为一个新人，但我也不想老是向别人索取。我也想跟大家分享有趣有用的东西，也能融入这个地方。所以这篇文章就出现了。&lt;/p&gt;

&lt;p&gt;这篇文章的定位是：给想要阅读 God 的源码提供一个大概的框架。&lt;/p&gt;
&lt;h2 id="God 的整体结构"&gt;God 的整体结构&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;运行结构&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;God 程序从运行上看，其结构是 C/S 结构，Client 端用于从命令行中去控制 God 的行为，Server 端进行进程监控。God 的 C/S 是通过 DRB 实现的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码组织结构&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;从代码组织上看，God 的结构是分层结构，每一层代表一个层次的逻辑。低一层的逻辑层次为高一层的逻辑层次的实现提供支持。God 的源码粗略能够分为 3 层。如下图所示：
&lt;img src="https://l.ruby-china.com/photo/2015/62c0355b4e7bc0e8129ad81312688e18.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;最高层次的逻辑就是 God module 本身抽象出的逻辑。其表示了 God 所要完成的功能。&lt;/p&gt;

&lt;p&gt;次一层的逻辑就是由 Contact、Task（Watch）、EventHandler、Socket 这些类所表示的逻辑。每一个类抽象了 God 所具有的某一方面的功能。Contact 抽象了 God 的通知的功能，Task（Watch）抽象了 God 进程监视功能，EventHandler 抽象了 God 使用 Kqueue 和 Netlink 机制的功能，而 Socket 抽象了与命令行交互的接口。&lt;/p&gt;

&lt;p&gt;最低层的逻辑是由 System module、Contacts module、Conditions module、CLI module 和 Driver、Metric、Process、Condition、Behavior 等等构成的，其功能就是为实现上一层逻辑提供支持。&lt;/p&gt;
&lt;h2 id="God 源码中比较重要的抽象"&gt;God 源码中比较重要的抽象&lt;/h2&gt;&lt;h2 id="状态机的抽象"&gt;状态机的抽象&lt;/h2&gt;
&lt;p&gt;我们在做进程监控的时候，其目的就是为了在达到某些条件之后，让进程去进行某些我们需要的操作。这个功能抽象出来就是状态机。&lt;/p&gt;

&lt;p&gt;God 中的进程监控就是使用的状态机的模型。在 God 源码中，状态机被抽象成了两个层次，分别为一般意义上的状态机和专门进行进程监控行为的状态机。一般意义上的状态机（Task）定义了状态机的状态设置、状态机状态转换行为、状态机开启与关闭和状态机运行机制等通用行为。而专门进行进程监控行为的状态机（Watch）继承自 Task，加入和重写了部分与进程监控功能相关的操作。&lt;/p&gt;

&lt;p&gt;God 这样进行抽象我认为主要是为了让代码更加清晰易懂。首先，通过阅读源代码会发现，状态机 Task 的实现涉及了很多代码，包括 Driver、Metric、Condition 等，这些代码逻辑本身有一定的复杂度。而进程监控的状态机（Watch）除了需要状态机的相关操作之外，还要具有控制进程的相关操作。如果不做这种分层次的抽象，就会让进程监控的状态机的代码逻辑变得很庞大，不利于测试和维护。&lt;/p&gt;
&lt;h2 id="行为接口的抽象"&gt;行为接口的抽象&lt;/h2&gt;&lt;h3 id="有哪些"&gt;有哪些&lt;/h3&gt;
&lt;p&gt;God 中对行为接口的抽象有两个地方：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configurable module&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在 Configurable module 中，其定义了 prepare、reset、valid?、complain 等通用的用于配置方面的接口，这些方法在模块里并没有实现。要使用这些接口的地方（例如 Contact、Behavior）只需要引入模块即可。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Behavior&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Behavior 这个类主要定义了两个方面的操作。一是定义一个通用的产生实例的方法（self.generate），如果你要产生一个在 Behaviors module 里面的某个类的实例（例如 A 的实例），只需要调用 &lt;code&gt;Behavior.generate(:a)&lt;/code&gt; 就可以了。另一个方面 Behavior 定义了 Hook 接口（&lt;code&gt;before_*&lt;/code&gt;和 &lt;code&gt;after_*&lt;/code&gt;），所以所有继承自 Behavior 的类的实例都能够定制自己的 &lt;code&gt;before_*&lt;/code&gt; 和 &lt;code&gt;after_*&lt;/code&gt; 方法。&lt;/p&gt;
&lt;h3 id="有什么意义"&gt;有什么意义&lt;/h3&gt;
&lt;p&gt;说了他们具体抽象的内容之后，那么这两个抽象到底有什么意义呢？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对其他调用的代码提供统一的调用接口和步骤&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们要明确这两个抽象都是对一类行为操作的抽象，这些行为除了进行本身的操作外，有的行为在操作前需要进行其他的相关操作，这些相关操作在这类行为中并不是都需要的。这就让外部调用很不方便，因为既然是同一类的行为，那么在外部看来我对你的使用应该是用同一样的方法，但是实际上我还要根据实际情况判断是否还要进行相关额外的操作。同样使用 Hook 也是一样的道理，对于有的行为会定义 Hook，而有的行为却没有。所以外部代码需要进行额外的判断。而通过上面的抽象之后，对于所有引入 Configurable module 的地方，对外部代码提供了统一的调用步骤（在进行操作前需要调用 prepare）和统一的方法。同样对于 Hook，外部代码只需要遵循 &lt;code&gt;before_*  —&amp;gt; * —&amp;gt; after_*&lt;/code&gt; 的调用流程就可以了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;提供统一的创建接口，让调用者明白这些类具有同类型的功能。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Behavior 定义的 generate 方法是通过传入 Symbol 来产生 Behaviors module 空间中相应类的实例。而在该模块中，所有的类都具有同类型的功能。提供这种统一的创建接口，能够让调用者知道产生的实例具有同类型的功能。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;便于扩展&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;提供统一创建接口带来的另一个好处在于便于扩展。例如，Condition 类继承自 Behavior（Condition 类将 &lt;code&gt;self.generate&lt;/code&gt; 方法进行了重写，用于产生 Contacts module 空间中的类的实例），通过 Condition 的 &lt;code&gt;self.generate&lt;/code&gt; 方法能够很容易的创建各种条件的实例。为了更容易理解可扩展性，我们来看一下这个方法对 God 配置文件的影响。如下的 God 配置文件：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="no"&gt;God&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watch&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;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;w&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="s2"&gt;"simple"&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ruby /full/path/to/simple.rb"&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:clean_pid_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:init&lt;/span&gt;&lt;span class="p"&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;gt;&lt;/span&gt; &lt;span class="ss"&gt;:up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:start&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:process_running&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:start&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:process_exits&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;在这个配置文件中注意三个 Symbol（ &lt;code&gt;:clean_pid_file&lt;/code&gt;、 &lt;code&gt;:process_running&lt;/code&gt;、&lt;code&gt;:process_exits&lt;/code&gt;），这三个 Symbol 最后会在 God 内部生成 &lt;code&gt;CleanPidFile&lt;/code&gt;、&lt;code&gt;ProcessRunning&lt;/code&gt;、&lt;code&gt;ProcessExits&lt;/code&gt; 三个类的实例，这个转变就是通过 Condition 的 &lt;code&gt;self.generate&lt;/code&gt; 方法来完成的。如果我们新加入了一个 Condition（例如 NewCondition），并且想在上述的进程监视任务中使用，那么我们只需要在模块中加入新的类（例如 NewCondition），然后在配置文件中直接通过 :new_condition 去产生该条件即可，而不用去修改其它地方的代码。&lt;/p&gt;
&lt;h2 id="进程监控相关代码分析"&gt;进程监控相关代码分析&lt;/h2&gt;
&lt;p&gt;通过前面讲的 God 的代码组织结构，我们知道 God 一共有 4 个子功能，分别是进程监控（Task）、通知（Contact）、使用 Kqueue/Netlink 机制（EventHandler）和给命令行提供接口（Socket）。&lt;/p&gt;

&lt;p&gt;在这里我主要分析进程监控功能的运行机制（状态机的运行机制）以及 EventHandler 如何融入到进程监控中。而对于其他的功能和代码实现细节不会做太多的详述。&lt;/p&gt;

&lt;p&gt;进程监控相关的主要的类如下图所示：
&lt;img src="https://l.ruby-china.com/photo/2015/f52804009b11fe9c580fc95054038dd5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从图中我们可以看到，进程监控是主要由 Driver、Metric、Process、Behavior 几个类配合完成。&lt;/p&gt;

&lt;p&gt;Behavior 的作用在前面提到了，其主要是用户产生 Behaviors module 空间中的类的实例，这些实例定义了在某些操作之前或之后所要进行的操作。例如 CleanPidFile 定义了 &lt;code&gt;before_start&lt;/code&gt; 方法，其作用是在 start 操作之前要清理 pid 文件。&lt;/p&gt;

&lt;p&gt;Process 主要功能在于提供调用在配置文件中配置的相关进程控制命令（start、restart、stop）的接口，根据配置设置进程的权限，关闭进程，给进程发送信号。也就是 Process 提供了根据我们配置去操作进程的接口。需要注意的是 Process 并没有提供去获取系统中进程真实信息的方法。而这部分功能是通过 System::Process 来完成的。&lt;/p&gt;

&lt;p&gt;剩下的 Driver 和 Metric 是状态机实现的主要部分，也是下面会详细进行讲解的部分。&lt;/p&gt;
&lt;h2 id="状态机的实现"&gt;状态机的实现&lt;/h2&gt;
&lt;p&gt;首先我们先回顾一下状态机所需要的基本元素。我们以下图为例：
&lt;img src="https://l.ruby-china.com/photo/2015/1f15afa08b30fdf6115faf07d8fc68e5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从图中我们可以直观的看到状态机所需要的基本元素有：状态（例如图中的 A、B、C、D、E、F）、各个状态转换到其它状态的转换路线（例如图中的 A-a1-D, A-a2-B、C-c1-F、D-d1-C、D-d2-E）。&lt;/p&gt;

&lt;p&gt;除了图中所体现的元素，状态机还需要有表示现在所处状态的变量和条件检查机制。所以要实现状态机，就要实现下面 4 个方面的要素：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;拥有的状态&lt;/li&gt;
&lt;li&gt;现在状态机所处的状态&lt;/li&gt;
&lt;li&gt;从某一状态转换到下一状态的路线&lt;/li&gt;
&lt;li&gt;条件检查机制&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以我们对照上面 4 个要素去在 God 中找到对应的实现。在 Task 中，你可以从源代码中快速的找到：valid_states 用于存放状态机所有的状态；state 属性保存着状态机的当前状态。&lt;/p&gt;

&lt;p&gt;而上面的元素 3 和 4 就是分别由 Metric 和 Driver 来实现的。&lt;/p&gt;
&lt;h3 id="转换路线的表示"&gt;转换路线的表示&lt;/h3&gt;&lt;h4 id="实现方式"&gt;实现方式&lt;/h4&gt;
&lt;p&gt;要表示出“从某一状态转换到下一状态的路线”，有三个方面的元素需要表示出来：1. 起始状态；2. 转换条件；3. 目的状态。&lt;/p&gt;

&lt;p&gt;在 Metric 中表示出了转换条件（conditions）和目的状态（destination）。也就是下图中红色方框的部分，我们将 Metric 表示的这种关系用符号 &lt;code&gt;c — S&lt;/code&gt; 表示，c 表示转换的条件，S 表示目的状态。下图中红色方框就可以写为 &lt;code&gt;a1 — B&lt;/code&gt;
 &lt;img src="https://l.ruby-china.com/photo/2015/eb27b0d86f5468c51fdeed285572c7a0.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;将“起始状态”与 Metric 表示的两个元素联系起来是通过 Task 中的名为 metrics 的 hash 来完成的。下面是某一个状态机的 metrics 中的内容：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# 某个状态机中的 metrics 的内容
self.metrics = {nil =&amp;gt; [metric1, metric2], :unmonitored =&amp;gt; [metric3], 
                          :stop =&amp;gt; [metric4], :start =&amp;gt; [metric5]}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上面的事例中我们可以看出，metrics 中的 key 表示起始状态，而其 value 是一个保存 Metric 实例的数组，结合上面所讲的 Metric 的含义。那么我们可以知道 metrics 中一个“键—值”对表示了“从某一个起始状态转换到其他状态的所有路线”。我们还是举个例子，如图所示：
&lt;img src="https://l.ruby-china.com/photo/2015/61de0392dadaf46b111a54d19b619d27.png" title="" alt=""&gt;
在上图中我们用分别用 &lt;code&gt;:A&lt;/code&gt;、&lt;code&gt;:B&lt;/code&gt;、&lt;code&gt;:C&lt;/code&gt; 表示 A、B、C 三个状态，用&lt;code&gt;metric_a1&lt;/code&gt;、&lt;code&gt;metric_a2&lt;/code&gt; 表示 &lt;code&gt;a1— B&lt;/code&gt;、&lt;code&gt;a2 — C&lt;/code&gt; 。那么上图中的状态机的 metrics 的内容如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;self.metrics = {:A =&amp;gt; [metric_a1, metric_a2], :B =&amp;gt; [],  :C =&amp;gt; []}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过一个 hash 和 Metric，就实现了状态机的第 3 个要素。&lt;/p&gt;
&lt;h4 id="实现技巧"&gt;实现技巧&lt;/h4&gt;
&lt;p&gt;在 Metric 实现的时候，其使用了两个小的技巧。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;技巧一&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有时候状态机转换的时候会出现下图所示的这种情况：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/09bbffa54724d6466e72445957dfc228.png" title="" alt=""&gt;
当在 A 状态时，如果测试满足条件 a1 的时候，状态转换为 B。相反如果测试条件不满足 a1，也就是说满足与 a1 相反的条件 a1' 的时候，其状态转换为 C。对于这种情况，利用上面我们谈到的实现，就需要创建两个 Metric 的实例 metric_a1、metric_a1'，分别表示 &lt;code&gt;a1 — B&lt;/code&gt; 和 &lt;code&gt;a1‘ — C&lt;/code&gt; 的状态。&lt;/p&gt;

&lt;p&gt;但是 God 中对于这种情况有一个更巧妙的实现，Metric 中的 destination 属性是一个形式为 &lt;code&gt;{ true =&amp;gt; :state1,  false =&amp;gt; :state2}&lt;/code&gt;的 hash。其含义表示，当 Metric 实例中的条件有测试为 true 的时候，状态转换为 state1，而当测试条件不通过的时候，状态转换为 state2。所以对于上图中的情况，实际上只需要一个 Metric 实例，将 a1 作为条件添加到实例中的，然后将 destination 设置为 &lt;code&gt;{ true =&amp;gt; :B,  false =&amp;gt; :C}&lt;/code&gt; 就可以了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;技巧二&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所有从某一个起始状态到同一个目的状态的条件，既可以放在一个 Metric 实例中，也可以放在多个 Metric 实例中。&lt;/p&gt;

&lt;p&gt;Metric 的 conditions 属性是一个数组，可以用于存放多个条件。从同一个起始状态到同一个目的状态的路线放入同一个 Metric 实例中可以说是很自然的。但是由于每一个 Metric 实例都是独一无二的。所以从同一个起始状态到同一个目的状态的路线也可以放入不同的 Metric 实例中，然后将这些实例放入 Task 的 metrics 的属性当中。&lt;/p&gt;

&lt;p&gt;God 为什么这么实现呢？我觉得其好处主要在于能够增加用于写配置文件的自由程度。例如下面两个配置文件：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#配置文件 1&lt;/span&gt;
&lt;span class="no"&gt;God&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watch&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;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;w&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="s2"&gt;"simple"&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ruby /full/path/to/simple.rb"&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keepalive&lt;/span&gt;

   &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:start&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:up&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:process_running&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:restart&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:memory_usage&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;megabytes&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&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;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;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:cpu_usage&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percent&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&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;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;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#配置文件 2&lt;/span&gt;
&lt;span class="no"&gt;God&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watch&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;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;w&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="s2"&gt;"simple"&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ruby /full/path/to/simple.rb"&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keepalive&lt;/span&gt;

   &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:start&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:up&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:process_running&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:restart&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:memory_usage&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;megabytes&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&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;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;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:restart&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:cpu_usage&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;percent&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&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;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;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;上面两个配置文件所表达的意义是一样的，唯一不同的地方就在于从 up 状态转换到 restart 状态的两个条件被放到了不同的 Metric 实例中而已。用户也不必拘泥于只能写第一种形式的配置文件。&lt;/p&gt;

&lt;p&gt;（对于 God 这么做我只能分析出这么一个好处，但是就内心来讲，感觉其实并没有什么实质的用途。）&lt;/p&gt;
&lt;h3 id="条件检查机制实现"&gt;条件检查机制实现&lt;/h3&gt;
&lt;p&gt;状态机的前 3 个要素表示完之后，关于状态机的描述已经很清楚了，剩下的就是引入“检查机制”来让状态机运行起来。&lt;/p&gt;

&lt;p&gt;God 的检查机制思路很简单。就是设置一个队列，将所有以当前状态为状态转换起始的条件放入该队列；然后根据设置的时间间隔，循环取出队列头的条件进行测试；如果条件测试成功，那么就清除现有队列中所有的条件，然后转换到相应的目的状态，并将所有以新状态为状态转换起始的条件放入队列，重复上面过程。如果条件测试不成功，那么将取出的条件再加入队列的尾部，然后取出队列头的条件，重复上面的步骤。整个过程可以用下面的图进行表示：
&lt;img src="https://l.ruby-china.com/photo/2015/9eeaff83bcf7f57f62b529893e3d7adf.png" title="" alt=""&gt;
在了解实现的思路后，我们来看一下具体的代码实现。&lt;/p&gt;

&lt;p&gt;在 God 中，检查机制的队列是由 Driver 进行维护的。Driver 实例中保存着一个 DriverEventQueue 的一个队列，并且开启了一个线程来不断的从队列中取出队列头部的元素，取出元素之后 Driver 本身并不再做多余的工作。也就是说 Driver 的实例只是完成下面红色方框中的功能：
&lt;img src="https://l.ruby-china.com/photo/2015/6ea42b8f6e6bcd6f11ff2339d74681e3.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;而绿色方框的功能则交给了 Task 实例来完成，准确的来说是交给 Task 实例的 &lt;code&gt;handle_poll&lt;/code&gt; 来完成的。所以整个检查机制的实现是：Driver 不断从自己维护的队列中取出条件，然后调用 Task 实例的 &lt;code&gt;handle_poll&lt;/code&gt; 方法来完成测试和与测试结果有关的其他操作。&lt;/p&gt;

&lt;p&gt;至此，状态机的功能已经完整的实现了。&lt;/p&gt;
&lt;h2 id="EventHandler"&gt;EventHandler&lt;/h2&gt;
&lt;p&gt;通过上面实现的检查机制，所有状态转换条件都需要状态机自己主动测试，这样的实现最大的问题就是实时性不够。对于有的条件，我们想要在条件满足后，立即进行状态的改变（例如：进程退出事件。因为进程退出后，其他很多条件测试已经没有意义了）。所以需要利用事件通知机制，在某些事件发生后立即通知状态机。在 God 中，通过 EventHandler 来使用系统提供的 Kqueue (BSD 和 Darwin 中的机制 ) 或者 Netlink (Linux 中进程间通信的机制) 来解决这一问题。&lt;/p&gt;

&lt;p&gt;在继续详细讲述 EventHandler 的具体实现之前，我们再来回顾一下前面的分层结构，我们看到 EventHandler 是作为一个 God 独立的子功能来实现的，而并不是作为为了实现状态机（Task）的功能而增加的子功能。&lt;/p&gt;

&lt;p&gt;在重新回顾了 EventHandler 在 God 中的地位之后，我们来看一下 EventHandler 的实现细节。&lt;/p&gt;
&lt;h3 id="对外接口"&gt;对外接口&lt;/h3&gt;
&lt;p&gt;EventHandler 对外提供的接口主要有：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;控制自生生命周期的 &lt;code&gt;self.load&lt;/code&gt;、 &lt;code&gt;self.start&lt;/code&gt;和 &lt;code&gt;self.stop&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用于注册和注销事件的 &lt;code&gt;self.register&lt;/code&gt; 和  &lt;code&gt;self.deregister&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EventHandler 对外部使用者来说最主要的接口就是 &lt;code&gt;self.register&lt;/code&gt;，这个接口接受 3 个参数（pid, event, block）。pid 就是我们所要监控的进程的 pid 号，event 就是我们所要监视的事件，block 是一个代码块，这个代码块会在事件发生后被调用。&lt;/p&gt;

&lt;p&gt;所以从对外部提供的接口来看，EventHandler 只是简单的对系统的事件通知机制做了一个包装，让使用者能够注册事件，然后在注册的事件发生后调用使用者设置的相关操作。&lt;/p&gt;
&lt;h3 id="内部实现细节"&gt;内部实现细节&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;主要属性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;类变量 &lt;code&gt;@@handler&lt;/code&gt;：用于保存 KQueueHandler 或者 NetlinkHandler 类。这两个类负责具体实现使用系统事件机制。&lt;/p&gt;

&lt;p&gt;类变量 &lt;code&gt;@@actions&lt;/code&gt;：它是一个 hash，用于保存我们所注册与每一个 pid 对应的 event 和与 event 对应的 block.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;具体实现&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NetlinkHandler 和 KQueueHandler 作为使用系统事件通知机制的具体实现，这两个类主要提供了 &lt;code&gt;register_process&lt;/code&gt; 和 &lt;code&gt;handle_events&lt;/code&gt; 两个方法，它们分别用于注册事件和当事件触发时调用用户传入的 block。在 EventHandler 中，其开启一个线程，循环调用 &lt;code&gt;@@handler&lt;/code&gt; 的 &lt;code&gt;handle_events&lt;/code&gt; 方法，接受来自系统的事件通知。&lt;/p&gt;
&lt;h3 id="与状态机结合"&gt;与状态机结合&lt;/h3&gt;
&lt;p&gt;从上面 EventHandler 的实现中，我们可以看到，EventHandler 本身只是提供了使用系统事件通知机制的功能，并没有直接与状态机结合。要与状态机结合，唯一的方法就是通过在注册事件时传入的 block。但是如果直接在这里的 block 中进行状态改变那就肯定会出现问题，因为这样就会有两个线程同时具有改变状态机的权限，而这两个线程在使用状态机资源上却没有实现互斥。&lt;/p&gt;

&lt;p&gt;在 God 的实现中，状态机的改变都是交给 Driver 实例中所开启的线程。而实现这个目的最简单的方法就是将 EventHandler 中注册的事件触发后所需要做的操作加入到 Driver 队列的队列头部，然后 Driver 根据取出元素的类型去决定让状态机执行测试还是直接进行相关操作。所以在 Driver 的队列中存在两种类型的元素：一种是 DriverEvent，表示 condition；另一种是 DriverOperation，表示状态机所要做的相关的操作。而要添加 DriverOperation 事件，就需要调用 Driver 提供的 &lt;code&gt;message&lt;/code&gt; 方法。&lt;/p&gt;

&lt;p&gt;所以 EventHandler 与状态机结合的方法就是在注册事件时提供的 block 中调用 Driver 的 &lt;code&gt;message&lt;/code&gt; 方法，以此来通知状态机要做的相关操作。&lt;/p&gt;
&lt;h2 id="对 God 进行扩展"&gt;对 God 进行扩展&lt;/h2&gt;
&lt;p&gt;God 的代码结构非常清晰，同时也非常利于扩展。比如我自己就对 God 进行了扩展，实现了对文件的监视。我在这里简单讲述一下思路，在明白 God 源码之后，进行功能扩展是非常容易的。&lt;/p&gt;
&lt;h2 id="实现状态机"&gt;实现状态机&lt;/h2&gt;
&lt;p&gt;首先，由于对文件的监视本身也是一个状态机，所以继承 Task 获得状态机所具有的一般的操作。&lt;/p&gt;

&lt;p&gt;然后，确定状态机具有的状态。例如在我的实现中，定义了 :unmonitor，:move_to 和 :delete 三个状态。其中 :move_to 的状态表示将文件移动到某个我们所配置的位置，:delete 表示将文件删除。&lt;/p&gt;

&lt;p&gt;最后，在状态机中加入与操作 file 相关方法。这里可以借鉴 Watch 的实现，将 file 相关的操作，放入单独的类中去，然后将这个类的实例作为状态机的属性。&lt;/p&gt;
&lt;h2 id="加入条件"&gt;加入条件&lt;/h2&gt;
&lt;p&gt;我想要监视文件大小的变化。所以在 Conditions module 中加入 FileSize 的条件，这里没有用到 EventHandler 的机制，所以 FileSize 继承自 PollCondition，并实现相关的接口即可。&lt;/p&gt;
&lt;h2 id="在 God 中提供相关的接口"&gt;在 God 中提供相关的接口&lt;/h2&gt;
&lt;p&gt;为了不破坏原有的 God 的功能，你需要在 God module 中提供额外的控制信息和接口。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;需要在 God 中提供额外的变量用于保存自己的状态机。&lt;/li&gt;
&lt;li&gt;在 God 中提供控制状态机生命周期的代码。例如， &lt;code&gt;self.start&lt;/code&gt; 中提供代码启动自己的状态机；在 &lt;code&gt;terminate&lt;/code&gt; 方法中提供关闭状态机的代码。&lt;/li&gt;
&lt;li&gt;在 God 中提供接口使用于能够使用。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="我的扩展结果"&gt;我的扩展结果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;配置文件&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我的配置文件如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;God&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_watch&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;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;w&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="s2"&gt;"file"&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/temp/file"&lt;/span&gt;

  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/temp/c_success"&lt;/span&gt;

  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;

  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:move_to&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:file_size&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:move_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:delete&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:file_size&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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&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;这个配置文件的意思是：
监视路径为 "/path/to/temp/file" 的文件，当文件的大小大于 15 byte 的时候，将文件重命名为 "/path/to/temp/c_success" 。当文件大小大于 30 byte 的时候，将该文件删除。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;运行结果
&lt;img src="https://l.ruby-china.com/photo/2015/a37bba82d3a2c6f4da3e522c00005b6c.png" title="" alt=""&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/633827138254318b79c41897604bde59.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="我的收获"&gt;我的收获&lt;/h2&gt;
&lt;p&gt;God 作为第一个我读的 Ruby 的源码，我从它这里收获很多。&lt;/p&gt;

&lt;p&gt;God 代码量虽然不算大，但是它基本上涵盖了 Ruby 语言中的全部知识，从基本的 Ruby 语法到 Ruby 元编程技术再到 C 的扩展都有实用。而且，God 的代码逻辑非常清晰，可以学习不同模块之间如何在低耦合的情况下进行交互。还有 God 代码的组织上也一目了然，同类型功能的类都放在同样的模块下面。从阅读中还能学到写代码时各种各样的小技巧。&lt;/p&gt;

&lt;p&gt;作为一个新人，我强烈推荐其他的同伴也去阅读 God 源码。&lt;/p&gt;

&lt;p&gt;下面是我觉得阅读 God 源码所需要的知识：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Ruby 基本语法&lt;/li&gt;
&lt;li&gt;《Ruby 元编程（第二版）》前 5 章的内容&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;有了上面的知识基础，读下来肯定是没有问题的。
当然你可能还需要下面的资料：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="http://blog.x-aeon.com/2012/11/21/how-to-write-c-extensions-easily-in-ruby/" rel="nofollow" target="_blank" title=""&gt;Ruby 的 C 扩展的简单入门&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.baidu.com/link?url=1OVhxC2YWnABYsv2SE4uzJEFz8s47QRFKdOV5wORhck2wFOW1gQSzFfXxDRcyH32RxIibR42i7JS907VnI3SUq&amp;amp;wd=&amp;amp;eqid=e3eb430b00004581000000045624b321" rel="nofollow" target="_blank" title=""&gt;Kqueue 的讲解&lt;/a&gt; (其实这个我还是没有看懂)&lt;/li&gt;
&lt;li&gt;[DRB 相关知识] &lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="后记"&gt;后记&lt;/h2&gt;
&lt;p&gt;作为很少写东西来说的我，写这篇文章还是很费力。其实最主要的问题在于不知道什么该讲什么不用讲。最开始的时候，我其实想通过一个简单的配置文件来讲述 God 的运行流程和代码结构的，但是通过方式讲解，要涉及的细节太多，写起来也比较烦躁。到最后，我以“我最想跟大家分享“为立足点来写的。立足于这一点，我发现自己写起来也比较开心，文章的结构思路也相对较为清晰。&lt;/p&gt;

&lt;p&gt;不知道大家对于写技术文有哪些自己的观点呢？&lt;/p&gt;</description>
      <author>redemption</author>
      <pubDate>Mon, 19 Oct 2015 19:10:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/27736</link>
      <guid>https://ruby-china.org/topics/27736</guid>
    </item>
    <item>
      <title>has_many through 的 uniq 设定</title>
      <description>&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:bs&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:cs&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :bs&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;B&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:a&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:cs&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;C&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个 model 的关系如上所示，如果我现在想要保证 C 中的一个属性（比如 c.name）在所关联的 A 中是唯一的。这样的 uniq 怎么实现呢？是应该去用 before_create 去实现吗？
比如&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;#通过&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;#Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>redemption</author>
      <pubDate>Mon, 09 Mar 2015 11:47:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/24537</link>
      <guid>https://ruby-china.org/topics/24537</guid>
    </item>
    <item>
      <title>cucumber step 中获取返回页面信息的方法。</title>
      <description>&lt;p&gt;我刚使用 cucumber，现在我遇到的问题是：
在我提交一个表单之后，程序会跳转到一个页面去，我要用 cucumber 去检测返回页面的内容。
我看书或者网上查到的都是&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"XX"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我现在用的是最新版本的 cucumber 和 rspec。已经不支持 contain 这个方法了。好像是用 include 代替。于是我把代码改成&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"xx"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题是 response 从返回的结果中显示其值是 nil，不是返回的页面。求帮忙解惑。&lt;/p&gt;</description>
      <author>redemption</author>
      <pubDate>Sun, 12 Oct 2014 19:46:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/22000</link>
      <guid>https://ruby-china.org/topics/22000</guid>
    </item>
  </channel>
</rss>
