<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>cxh116 (曹小华)</title>
    <link>https://ruby-china.org/cxh116</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>用 Rails 5 + 纯 webpacker 做了一个小网站</title>
      <description>&lt;p&gt;高铁旅游路线推荐小工具 &lt;a href="https://mangege.com/" rel="nofollow" target="_blank"&gt;https://mangege.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;技术栈：rails 5 + webpacker + es6 + vue2&lt;/p&gt;

&lt;p&gt;rails 建项目时使用了 --skip-sprockets 选项，不安装 Sprockets gem 来管理静态资源，只使用 webpacker .&lt;/p&gt;

&lt;p&gt;webpacker 使后感：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; 感觉蛮方便的，配置 yarn 来管理前端库，还真心不错。&lt;/li&gt;
&lt;li&gt;不运行 ./bin/webpack-dev-server 时开发环境访问时有点慢。运行 ./bin/webpack-dev-server 当 js css 有改动时，会自动刷新页面。&lt;/li&gt;
&lt;li&gt;上手有点难度，定制一些功能时需要熟悉 webpack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;vue 使用比较简单，无前后端分离，只作为视图层工具使用。这样使用感觉也很良好，和 jquery 一样方便。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Fri, 23 Mar 2018 09:57:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/35306</link>
      <guid>https://ruby-china.org/topics/35306</guid>
    </item>
    <item>
      <title>为什么这个正则在某些情况下,直接阻塞不返回了?</title>
      <description>&lt;p&gt;一个 email 正则，在 ruby 2.2 , 2.4 测试都有问题？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 正常&lt;/span&gt;
&lt;span class="s2"&gt;"aaa@aaa.com"&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt;  &lt;span class="sr"&gt;/\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i&lt;/span&gt;


&lt;span class="c1"&gt;# 阻塞不返回&lt;/span&gt;
&lt;span class="s2"&gt;"ekathryn.gay@browardschools.comssssssaaaaaaaaq212qa1w4wa2333w"&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt;  &lt;span class="sr"&gt;/\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况要怎么调试？&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Fri, 08 Sep 2017 12:03:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/34065</link>
      <guid>https://ruby-china.org/topics/34065</guid>
    </item>
    <item>
      <title>解析邮件碰到的那些坑</title>
      <description>&lt;p&gt;本文主要讲解使用 &lt;a href="https://github.com/mikel/mail" rel="nofollow" target="_blank" title=""&gt;mail&lt;/a&gt; 库解析邮件所碰到的坑。&lt;/p&gt;

&lt;p&gt;邮件格式本身的解析由 mail 库。由于邮件格式标准&lt;a href="https://github.com/mikel/mail/tree/2-6-stable/reference" rel="nofollow" target="_blank" title=""&gt;过多且过于复杂&lt;/a&gt;,鉴于个人能力有限，所以就不讲解邮件相关的标准的。需要自己先阅读相关资料.比如 RFC 822, multipart 等方面的资料。&lt;/p&gt;

&lt;p&gt;坑主要分两大类：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;编码 (修炼的必经之路)&lt;/li&gt;
&lt;li&gt;邮件非常见格式解析 (主要是苹果设备发出来的邮件)

&lt;ul&gt;
&lt;li&gt;正文只有图片 (只包含附件 part, 无 text part 或 html part)&lt;/li&gt;
&lt;li&gt;正文有多个文本段 (multi text part)&lt;/li&gt;
&lt;li&gt;multipart 再包含 multipart&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="mail 基础技巧"&gt;mail 基础技巧&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;查看 &lt;a href="https://github.com/mikel/mail" rel="nofollow" target="_blank" title=""&gt;mail 官方文档&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Mail.new(str)&lt;/code&gt; 的 str 变量，需要为 RFC 822 标准格式&lt;/li&gt;
&lt;li&gt;gmail 邮件详情页的 "显示原始邮件" ,下载下来的 &lt;a href="https://productforums.google.com/d/msg/gmail/NLxxg-5jk_o/7Mg88jSz7vQJ" rel="nofollow" target="_blank" title=""&gt;original_msg.txt&lt;/a&gt; 文件，是 RFC 822 标准，调试时可以直接下载此文件来调试。&lt;/li&gt;
&lt;li&gt;iamp 抓取时，&lt;code&gt;imap.uid_fetch(uid, ['RFC822'])[0]&lt;/code&gt; 这样可以拿到 RFC 822 格式的内容。&lt;a href="https://github.com/mikel/mail/blob/2-6-stable/lib/mail/network/retriever_methods/imap.rb#L86" rel="nofollow" target="_blank" title=""&gt;参考来源&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="编码"&gt;编码&lt;/h3&gt;
&lt;p&gt;编码这个坑与编程语言无关，它是我们修炼必经的路。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;世界上有多种字符，比如英文，简体中文，繁体中文。&lt;/li&gt;
&lt;li&gt;一种字符有可能有多种编码，比如简体中文有 GB2312, GBK, GB18030 . &lt;a href="http://www.qqxiuzi.cn/bianma/zifuji.php" rel="nofollow" target="_blank" title=""&gt;参考来源&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;一种编码有可能有多种实现，比如 Unicode 编码有 UTF-8, UTF-16, UTF-32 多种实现。&lt;a href="https://zh.wikipedia.org/wiki/Unicode" rel="nofollow" target="_blank" title=""&gt;参考来源&lt;/a&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="c1"&gt;# "中" 字不同编码的十六进制值. http://blog.bigbinary.com/2011/07/20/ruby-pack-unpack.html&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# e4b8ad&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-32'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 0000feff00004e2d &lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# d6d0&lt;/span&gt;

&lt;span class="c1"&gt;# 字节数组&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [228, 184, 173]&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-32'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [0, 0, 254, 255, 0, 0, 78, 45]&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [214, 208]&lt;/span&gt;

&lt;span class="c1"&gt;# Base64&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"base64"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'中'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 1tA=&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'中'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 5Lit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上示例可以看出，同一个字符，用不同编码时，其二进制数据值有可能不一样。&lt;/p&gt;

&lt;p&gt;那么编码的主要问题是什么？请看代码示例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"base64"&lt;/span&gt;
&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1tA='&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 解码 "中" 的 GBK 编码的 base64 值.&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encoding&lt;/span&gt; &lt;span class="c1"&gt;# ASCII-8BIT, 相当于是一个字节数组(byte array, 1byte = 8bit)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [214, 208] , 等于上示例的 "中".encode('GBK').bytes.inspect .也就是说变量的在内存里的二进制值还是 GBK 编码.&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="c1"&gt;# 打印出乱码. 因为终端一般设置的编码为 UTF-8 ,如果想要此语句不显示成乱码,把终端编码改成 GBK 即可.记得改回 UTF-8.&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [214, 208], 字节还是 GBK 未变&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 中 force_encoding 只是改变变量的编码元信息. encode 把变量的字节从 GBK 变成 UTF-8 . 这样打印就不乱码了.&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BIG5'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# [214, 208], 字节还是 GBK 未变&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BIG5'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 笢 同样的字节数据,在繁体 BIG5 编码里有效且是另外一个字符.&lt;/span&gt;

&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 报错.因为不知道字节的编码信息,有可能默认编码转换映射是从 ASCII to UTF-8(待考证)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把一个字符转成对应编码的字节不难，把一个已知编码信息的字节转成对应字符也不难。&lt;/p&gt;

&lt;p&gt;难在把一个不知道编码信息的二进制数据，转成对应的字符。&lt;code&gt;[214, 208]&lt;/code&gt; 是 GBK 编码里的 "中" 字，同时也是 BIG5 编码里面的 "笢" 字。&lt;/p&gt;

&lt;p&gt;上示例就是演示了邮件里碰到的编码问题，当正文经过 base64 编码后。收到邮件后，base64 解码出来的二进制数据，到底是 GBK, 还是 BIG5 ,还是其它编码？&lt;/p&gt;

&lt;p&gt;所幸的是，大部分情况我们都不需要靠猜，一般邮件正文 part 都有这样一段 header 信息。其 Content-Type 的 charset 就告诉了我们这段 base64 解码后的二进制是什么编码。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码示例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mail'&lt;/span&gt; &lt;span class="c1"&gt;# 需要先安装 mail gem&lt;/span&gt;
&lt;span class="c1"&gt;# 构建 RFC822 标准的邮件字符串&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="s1"&gt;'mikel@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;to&lt;/span&gt;      &lt;span class="s1"&gt;'you@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="s1"&gt;'This is a test email'&lt;/span&gt;
  &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="s1"&gt;'text/plain; charset=GBK'&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;    &lt;span class="s2"&gt;"中"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&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;original_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="cm"&gt;=begin
Date: Sat, 19 Aug 2017 13:39:10 +0800
From: mikel@test.lindsaar.net
To: you@test.lindsaar.net
Message-ID: &amp;lt;5997cefe17bb6_5ded1e74693bc8972ac@hparch.mail&amp;gt;
Subject: This is a test email
Mime-Version: 1.0
Content-Type: text/plain;
 charset=GBK
Content-Transfer-Encoding: base64

1tA=
=end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;original_msg&lt;/span&gt;

&lt;span class="c1"&gt;# 开始解析&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&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;original_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decoded&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="c1"&gt;# 乱码&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encoding&lt;/span&gt; &lt;span class="c1"&gt;# ASCII-8BIT&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&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="nf"&gt;charset&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 中&lt;/span&gt;
&lt;span class="c1"&gt;# 需要检查 charset 是否存在,通过 Encoding.find 方法&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理想的世界，content_type 是带了 charset .但现实与理想总是存在差距。有些没有带 charset ,有些甚至连 content_type 整行都没有。&lt;/p&gt;

&lt;p&gt;这个时候编码就要靠猜了，&lt;a href="https://github.com/brianmario/charlock_holmes" rel="nofollow" target="_blank" title=""&gt;charlock_holmes&lt;/a&gt; 就是干这事的。&lt;/p&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="no"&gt;CharlockHolmes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EncodingDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'中文测试很长的文字'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ASCII-8BIT'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {:type=&amp;gt;:text, :encoding=&amp;gt;"UTF-16BE", :ruby_encoding=&amp;gt;"UTF-16BE", :confidence=&amp;gt;10}&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;CharlockHolmes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EncodingDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'遍身罗绮者 不是养蚕人'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ASCII-8BIT'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {:type=&amp;gt;:text, :encoding=&amp;gt;"ISO-8859-6", :ruby_encoding=&amp;gt;"ISO-8859-6", :confidence=&amp;gt;16, :language=&amp;gt;"ar"}&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;CharlockHolmes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EncodingDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'中文测试,工要在地一上是中国；'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GBK'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ASCII-8BIT'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {:type=&amp;gt;:text, :encoding=&amp;gt;"GB18030", :ruby_encoding=&amp;gt;"GB18030", :confidence=&amp;gt;100, :language=&amp;gt;"zh"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;所以，优先以 content_type 的 charset 去解码，charlock_holmes 只是最后方案。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;事实上 encode 方法有个坑，就是有可能 encode 碰到无效字节，会导致报错。推荐加上 invalid 和 undef 参数。replace 默认是替换成问号。还可以直接删除无效字节，推荐使用替换。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;invalid: :replace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;undef: :replace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="邮件非常见格式解析"&gt;邮件非常见格式解析&lt;/h3&gt;&lt;h4 id="1. 正文只有图片"&gt;1. 正文只有图片&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mail'&lt;/span&gt; &lt;span class="c1"&gt;# 需要先安装 mail gem&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'open-uri'&lt;/span&gt;
&lt;span class="c1"&gt;# 构建 RFC822 标准的邮件字符串&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="s1"&gt;'mikel@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;to&lt;/span&gt;      &lt;span class="s1"&gt;'you@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="s1"&gt;'This is a test email'&lt;/span&gt;
  &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="s1"&gt;'image/png; filename=One_black_Pixel.png'&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://upload.wikimedia.org/wikipedia/en/4/45/One_black_Pixel.png'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;original_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="cm"&gt;=begin
Date: Sat, 19 Aug 2017 14:25:48 +0800
From: mikel@test.lindsaar.net
To: you@test.lindsaar.net
Message-ID: &amp;lt;5997d9ec98315_695f2a4c229bd097632@hparch.mail&amp;gt;
Subject: This is a test email
Mime-Version: 1.0
Content-Type: image/png;
 filename=One_black_Pixel.png
Content-Transfer-Encoding: base64

iVBORwoaCgAAAApJSERSAAAAAQAAAAEIAgAAAJB3U94AAAABc1JHQgCuzhzp
AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAxJREFU
GFdjYGBgAAAABAABXM3/aQAAAABJRU5ErkJggg==
=end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;original_msg&lt;/span&gt;

&lt;span class="c1"&gt;# 开始解析&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&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;original_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="c1"&gt;# 正文是乱码&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachment?&lt;/span&gt; &lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于这种只有图片的邮件，我们先用 attachment? 方法判断是不是附件，是附件的话，按附件的逻辑处理，比如保存到本地。&lt;/p&gt;
&lt;h4 id="2. 正文有多个文本段"&gt;2. 正文有多个文本段&lt;/h4&gt;
&lt;p&gt;此非常见格式的邮件一般是苹果设备自带的邮件客户端发出来的。&lt;/p&gt;

&lt;p&gt;苹果邮件客户端这么做是为了实现在纯文本格式邮件插入图片的上下环绕效果。&lt;/p&gt;

&lt;p&gt;主流的做法纯文本不插入图片，图片只作为普通附件存在。要插入图片，使用 html 格式，通过 html img 标签来实现，img src 填图片附件的 cid .&lt;/p&gt;

&lt;p&gt;这样的格式，在 gmail 显示不出环绕效果，只作为普通附件显示。在苹果的客户端可以显示。&lt;/p&gt;

&lt;p&gt;像 &lt;a href="https://mailcatcher.me/" rel="nofollow" target="_blank" title=""&gt;MailCatcher&lt;/a&gt; 这个接收测试工具，和我开始一样，以为一个邮件只有一个 text part，所以导致这种邮件只会显示部分文本。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mail'&lt;/span&gt; &lt;span class="c1"&gt;# 需要先安装 mail gem&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'open-uri'&lt;/span&gt;
&lt;span class="c1"&gt;# 构建 RFC822 标准的邮件字符串&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="s1"&gt;'mikel@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;to&lt;/span&gt;      &lt;span class="s1"&gt;'you@test.lindsaar.net'&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="s1"&gt;'This is a test email'&lt;/span&gt;
  &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ss"&gt;:content_type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"multipart/alternative"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:content_disposition&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"inline"&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="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"abc"&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;part&lt;/span&gt; &lt;span class="ss"&gt;content_type: &lt;/span&gt;&lt;span class="s1"&gt;'image/png; filename=One_black_Pixel.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://upload.wikimedia.org/wikipedia/en/4/45/One_black_Pixel.png'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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;part&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"def"&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;original_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="cm"&gt;=begin
Date: Sat, 19 Aug 2017 14:58:30 +0800
From: mikel@test.lindsaar.net
To: you@test.lindsaar.net
Message-ID: &amp;lt;5997e19678f0f_70774d198d1bc8306db@hparch.mail&amp;gt;
Subject: This is a test email
Mime-Version: 1.0
Content-Type: multipart/mixed;
 boundary="--==_mimepart_5997e19675b41_70774d198d1bc8305cd";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_5997e19675b41_70774d198d1bc8305cd
Content-Type: multipart/alternative;
 boundary="--==_mimepart_5997e1959f3c7_70774d198d1bc830458";
 charset=UTF-8
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Content-ID: &amp;lt;5997e1967a6a3_70774d198d1bc830787@hparch.mail&amp;gt;


----==_mimepart_5997e1959f3c7_70774d198d1bc830458
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

abc
----==_mimepart_5997e1959f3c7_70774d198d1bc830458
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

def
----==_mimepart_5997e1959f3c7_70774d198d1bc830458
Content-Type: image/png;
 filename=One_black_Pixel.png
Content-Transfer-Encoding: base64

iVBORwoaCgAAAApJSERSAAAAAQAAAAEIAgAAAJB3U94AAAABc1JHQgCuzhzp
AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAxJREFU
GFdjYGBgAAAABAABXM3/aQAAAABJRU5ErkJggg==

----==_mimepart_5997e1959f3c7_70774d198d1bc830458--

----==_mimepart_5997e19675b41_70774d198d1bc8305cd--
=end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;original_msg&lt;/span&gt;

&lt;span class="c1"&gt;# 开始解析&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mail&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;original_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multipart?&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="c1"&gt;# 默认只取了第一个 text part&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="c1"&gt;# 通过 all_parts 拿到所有 part, 包含本身.&lt;/span&gt;
&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all_parts&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;part&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# 需要排除 multipart , attachment, 生产代码还需要区分 text 还是 html. text 和 text 加在一起, html 和 html 加在一起.&lt;/span&gt;
  &lt;span class="c1"&gt;# 这里还有一个大坑,就是多 text part 字符拼接时,一定要先把编码转成 utf-8 .因为苹果设备如果刚好那部分只有英文,那么编码为 ASCII, 如果有中文,编码为 GBK .&lt;/span&gt;
  &lt;span class="c1"&gt;# 有兴趣的朋友可以用苹果邮件客户端自己测试一下&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multipart?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachment?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="multipart 再包含 multipart"&gt;multipart 再包含 multipart&lt;/h4&gt;
&lt;p&gt;这种情景主要出现在苹果邮件客户端同时发送 text 和 html 格式的。html 是一个 sub multipart.&lt;/p&gt;

&lt;p&gt;处理方法同上，all_parts 会自动遍历 sub multipart . 我们只要排除 multipart? 和 attachment? 即可。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Sat, 19 Aug 2017 15:17:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/33882</link>
      <guid>https://ruby-china.org/topics/33882</guid>
    </item>
    <item>
      <title>使用 Meta Generator 打造你的 Rails Admin</title>
      <description>&lt;p&gt;按个人理解，Admin Interfaces 的主要作用是减少后台管理界面的 CRUD 开发的重复工作量，并提供登录注销等常见功能的实现。&lt;/p&gt;

&lt;p&gt;Admin Interfaces 实现主要分两大块。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;基于继承和配置.

&lt;ul&gt;
&lt;li&gt;代表：Django Admin(最著名的), ActiveAdmin.&lt;/li&gt;
&lt;li&gt;优点：代码量少。&lt;/li&gt;
&lt;li&gt;缺点：定制难度高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;基于代码生成.

&lt;ul&gt;
&lt;li&gt;代表：Rails Scaffold(自带的太简单了).&lt;/li&gt;
&lt;li&gt;优点：代码生成在项目里，定制只要直接修改代码即可，非常灵活。&lt;/li&gt;
&lt;li&gt;缺点：写自定义代码生成器有点难度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;同时使用代码生成和继承方式.

&lt;ul&gt;
&lt;li&gt;代表：ActiveScaffold&lt;/li&gt;
&lt;li&gt;优点：在减少代码的同时也保证了定制的灵活性。&lt;/li&gt;
&lt;li&gt;缺点：同上&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;作为有一个有追求的人，虽然已经 ActiveAdmin 和 ActiveScaffold 这样不错 Rails Admin Interfaces.但为了追求定制的灵活性的最大化，必须得自己造个轮子出来，哪怕是方的轮子。&lt;/p&gt;
&lt;h3 id="什么是 Meta Generator?"&gt;什么是 Meta Generator?&lt;/h3&gt;
&lt;p&gt;就是写个 Generator A, Generator A 生成一个 Generator B 到你的项目里，平常你主要运行 Generator B 生成 CRUD 相关代码。这里的 Generator A 就是 Meta Generator.&lt;/p&gt;
&lt;h3 id="Meta Generator 示例"&gt;Meta Generator 示例&lt;/h3&gt;
&lt;p&gt;首先，请认真读完 &lt;a href="http://guides.rubyonrails.org/generators.html" rel="nofollow" target="_blank" title=""&gt;Creating and Customizing Rails Generators &amp;amp; Templates&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;文中的 &lt;code&gt;bin/rails generate generator initializer&lt;/code&gt; 的 generator 即是一个 Meta Generator，利用 generator 产生的代码存放在你的 Rails 项目的 lib/generators ,这样我们就可以很方便的修改。我们只要参考 generator ,写一个类似的 Gem ,把代码生成到 lib/generators 目录即可。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your_workspace &lt;span class="c"&gt;# 修改成你自己的目录&lt;/span&gt;
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails plugin new meta_generator_demo &lt;span class="c"&gt;# 创建一个 Rails Engine 的 Gem 项目&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;meta_generator_demo
vi meta_generator_demo.gemspec &lt;span class="c"&gt;# 把里面的带 TODO 的都改成你准备填写的信息&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; lib/generators


&lt;span class="nb"&gt;cd &lt;/span&gt;your_rails_project
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g generator meta_generator_demo &lt;span class="c"&gt;# 创建一个生成器&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;lib/generators/meta_generator_demo your_workspace/meta_generator_demo/lib/generators/meta_generator_demo &lt;span class="c"&gt;# 修改成你自己的目录&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;your_workspace/meta_generator_demo &lt;span class="c"&gt;# 修改成你自己的目录&lt;/span&gt;
vi lib/generators/meta_generator_demo/meta_generator_demo_generator.rb &lt;span class="c"&gt;# 文件内容如下&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/generators/meta_generator_demo/meta_generator_demo_generator.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetaGeneratorDemoGenerator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Generators&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NamedBase&lt;/span&gt;
  &lt;span class="n"&gt;source_root&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;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../templates'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_demo_file&lt;/span&gt;
    &lt;span class="n"&gt;create_file&lt;/span&gt; &lt;span class="s1"&gt;'lib/generators/demo_generator.rb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOS&lt;/span&gt;&lt;span class="sh"&gt;
class DemoGenerator &amp;lt; Rails::Generators::Base
  desc "This generator creates an model file at app/models"
  def create_demo_file
    create_file "app/models/demo.rb", "class Demo; end"
  end
end
&lt;/span&gt;&lt;span class="no"&gt;    EOS&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;Meta Generator  已经编写完成，可以测试了。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your_rails_project
vi Gemfile &lt;span class="c"&gt;# 添加 "gem 'meta_generator_demo', path: 'your_workspace/meta_generator_demo'" 到 Gemfile 里面,不包含 " 符号.&lt;/span&gt;
bundle &lt;span class="nb"&gt;install
&lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g &lt;span class="c"&gt;# 是不是可以看到 meta_generator_demo 这个选项了?&lt;/span&gt;
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g meta_generator_demo Demo
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g &lt;span class="c"&gt;# 是不是可以看到 demo 这个选项了?&lt;/span&gt;
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails g demo &lt;span class="c"&gt;# 是不是可以看到 demo 这个选项了?&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;app/models/demo.rb &lt;span class="c"&gt;# 最终生成的文件, 如果需要改 demo.rb 生成的内容,只需要改 Rails 项目里的 lib/generators/demo_generator.rb 文件即可.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="总结"&gt;总结&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Gem 的 lib/generators/meta_generator_demo/meta_generator_demo_generator.rb 文件有个 create_demo_file 方法，此方法在你的 Rails 项目生成 lib/generators/demo_generator.rb 文件。&lt;/li&gt;
&lt;li&gt;生成的 lib/generators/demo_generator.rb 文件有个 create_demo_file 方法，此方法创建了 app/models.demo.rb 文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="Rails Admin Generator 示例"&gt;Rails Admin Generator 示例&lt;/h3&gt;
&lt;p&gt;示例项目：&lt;a href="https://github.com/adminonrails/aor" rel="nofollow" target="_blank" title=""&gt;https://github.com/adminonrails/aor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;此项目已经弃坑，作为示例提供参考。抛砖引玉，希望高手们能定制出更加适合自己的方案&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;挖了这个坑，后面因为做的项目都是前后端分离，就没有再填了。另外觉得这个项目也比较鸡肋，对新手不友好，上手和定制有点难度。对高手来说，有自己的一套方式，一般是自己挖个坑。&lt;/p&gt;

&lt;p&gt;大部分功能都有写单元测试，项目有在生产环境使用过。&lt;/p&gt;

&lt;p&gt;项目介绍：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bootstrap: bootstrap 的静态资源，无 sass 依赖。&lt;/li&gt;
&lt;li&gt;authentication: 提供登录验证的一些辅助方法，源码就一个文件。主要提供 logged_in?, current_user 等方法。参考老版 publify .&lt;/li&gt;
&lt;li&gt;authorization: 基于 cancancan 提供后台权限验证的一些辅助方法。主要基于 controller_name 和 action_name 来限制。参考 spree 的后台验证逻辑。&lt;/li&gt;
&lt;li&gt;theme: 代码生成器&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用请参考 test 和 dummy 目录测试代码。&lt;/p&gt;
&lt;h4 id="theme 详解:"&gt;theme 详解：&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/adminonrails/aor/blob/master/theme/Rakefile" rel="nofollow" target="_blank" title=""&gt;https://github.com/adminonrails/aor/blob/master/theme/Rakefile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上文件 DummyGenerator 部分，是一个新 rails 项目使用 aor 的主要流程，这里是用来每次运行 dummy rails 测试项目，先生成最新的 aor 代码。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;生成 kaminari 的 bootstrap3 模板。&lt;/li&gt;
&lt;li&gt;添加 cancan 的 AdmAbility 文件。&lt;/li&gt;
&lt;li&gt;运行 aor:theme&lt;/li&gt;
&lt;li&gt;生成 admin user model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/adminonrails/aor/blob/master/theme/lib/generators/aor/theme/theme_generator.rb" rel="nofollow" target="_blank" title=""&gt;https://github.com/adminonrails/aor/blob/master/theme/lib/generators/aor/theme/theme_generator.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上文件主要在安装了 aor-theme gem，运行 aor:theme 命令的代码。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;复制 admin js 和 css 文件。&lt;/li&gt;
&lt;li&gt;添加公共的头部，侧边，表单验证错误提示文件。&lt;/li&gt;
&lt;li&gt;添加 base admin controller 和 helper 文件。&lt;/li&gt;
&lt;li&gt;把 admin.js 和 admin.css 添加到 assets 里，这样编译 js 和 css 会单独生成 admin 文件。&lt;/li&gt;
&lt;li&gt;生成表单验证错误提示的 bootstrap 样式.
6  复制项目的 admin generator 到当前 rails 的 lib/generators 目录。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/adminonrails/aor/tree/master/theme/lib/generators/aor/theme/templates/generator" rel="nofollow" target="_blank" title=""&gt;https://github.com/adminonrails/aor/tree/master/theme/lib/generators/aor/theme/templates/generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;此目录的文件，主要增强 rails 自带的 scaffold, scaffold_controller . 我们不覆盖 rails scaffold，只是添加一个自己的 admin:scaffold . 使用时运行 rails g admin:scaffold .&lt;/p&gt;

&lt;p&gt;里面的 rb 文件逻辑，主要是修改 scaffold 的 source_paths 路径，优先使用我们的 controller 和 views 模板。&lt;/p&gt;

&lt;p&gt;子目录 erb 和 rails 即是模板。&lt;/p&gt;
&lt;h5 id="总结"&gt;总结&lt;/h5&gt;
&lt;p&gt;此项目混合两种方式，一种是通过代码继承，子类通过重写父类方法来实现自定义。一种是生成代码，再修改生成的生成器代码，来实现自定义。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Tue, 08 Aug 2017 22:35:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/33771</link>
      <guid>https://ruby-china.org/topics/33771</guid>
    </item>
    <item>
      <title>不使用 Docker 部署 Discourse (分享安装脚本)</title>
      <description>&lt;p&gt;脱离 Docker 依赖直接在系统上搭建 Discourse 基础环境。&lt;/p&gt;

&lt;p&gt;主要把 &lt;a href="https://github.com/discourse/discourse_docker" rel="nofollow" target="_blank" title=""&gt;discourse_docker&lt;/a&gt; 的 Dockerfile 转成 shell 脚本，用 shell 脚本直接在主机安装 Discourse 的 gifsicle pngcrush 等依赖。&lt;/p&gt;

&lt;p&gt;discourse_docker 使用的是 ubuntu 16.04 ,所以主机的系统必须得用 ubuntu 16.04 .&lt;/p&gt;

&lt;p&gt;discourse_docker 使用 runit 启动 web server, sidekiq 等。但普通的 ubuntu 默认是使用 systemd ,所以 web server 和 sidekiq 等进程的启动，你得像普通的 rails 应用，使用 god 或 monit 之类的进程管理软件来启动。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mangege/discourse_shell" rel="nofollow" target="_blank"&gt;https://github.com/mangege/discourse_shell&lt;/a&gt;&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 24 Aug 2016 15:18:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/30900</link>
      <guid>https://ruby-china.org/topics/30900</guid>
    </item>
    <item>
      <title>Ruby 包管理分析</title>
      <description>&lt;p&gt;本文简单的介绍 Ruby 包管理的相关原理，写的比较粗浅，欢迎补充。&lt;/p&gt;
&lt;h4 id="大纲"&gt;大纲&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Ruby 本身的包管理&lt;/li&gt;
&lt;li&gt;Rubygems&lt;/li&gt;
&lt;li&gt;Bundler&lt;/li&gt;
&lt;li&gt;RVM 与 rbenv&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="Ruby 本身的包管理"&gt;Ruby 本身的包管理&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;require method&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ruby 主要通过 require 函数来引入外部的库文件。函数原型如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# http://ruby-doc.org/core-1.8.7/Kernel.html#method-i-require&lt;/span&gt;
&lt;span class="c1"&gt;# http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-require&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数需要传一个 string , 文件名或文件路径。&lt;br&gt;
返回值为 boolean 值，true 为 require 成功。&lt;/p&gt;

&lt;p&gt;演示代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# shell
echo 'puts "a"' &amp;gt; /tmp/a.rb
cd /tmp
irb
require 'csv' # 文件名方式,在 $LOAD_PATH 全局变量定义的路径里搜索
require './a' # 相对路径方式,基于进程的工作目录, Dir.pwd 可以查看当前进程的工作路径
require '/tmp/a' # 绝对路径. 1.8.7 返回 true, 1.9 以后返回false. 1.9 以后同一文件,用不同的路径方式加载,也算同一文件,不会重复加载.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$LOAD_PATH&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本部分基于 ruby 1.8.7 的原因是因为 ruby 1.8.7 默认还是用 ruby 自身的 require 函数，1.8 以后，默认用的是 Rubygems 实现的 require 函数。&lt;/p&gt;

&lt;p&gt;大部分时候，我们使用 require 使用的是文件名，而不是相对路径或绝对路径的方式，所以 $LOAD_PATH 变量是个关键点。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby -e "puts $:" # shell, 用 ruby 命令的 -e 参数运行单行 ruby 代码. 以下为命令执行后的输出
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/x86_64-linux
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/vendor_ruby/1.8
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/vendor_ruby/1.8/x86_64-linux
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/vendor_ruby
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/1.8
/usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/1.8/x86_64-linux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;$LOAD_PATH 变量为一个数组，里面存放的路径字符串。&lt;/p&gt;

&lt;p&gt;打印出来的有三个重要的目录分类。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;site_ruby 默认优先级最高，安装本机相关库。摘自&amp;lt;&amp;gt; 254 页。
&lt;/li&gt;
&lt;li&gt;vendor_ruby 操作系统供应商进行定制用的，一般为空。&lt;/li&gt;
&lt;li&gt;1.8 ruby 标准库目录。比如 date, csv 库。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以进入对应的目录查看一下，目录下有什么文件。&lt;/p&gt;

&lt;p&gt;演示代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'puts "priority2"' &amp;gt; /usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/vendor_ruby/1.8/prioritydemo.rb # vendor_ruby
ruby -e "require 'prioritydemo'" # puts priority2
echo 'puts"priority1"'&amp;gt; /usr/local/rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/prioritydemo.rb # site_ruby
ruby -e "require 'prioritydemo'" # puts priority1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过代码演示可以看见，require 查找的顺序是基于 $LOAD_PATH 数组里面的路径的顺序来找的，找到了就不继续往下找。&lt;/p&gt;

&lt;p&gt;上测试代码如果要强制加载 vendor_ruby 目录下的 prioritydemo 文件，可以使用绝对路径。&lt;/p&gt;
&lt;h4 id="Rubygems"&gt;Rubygems&lt;/h4&gt;
&lt;p&gt;Rubygems 主要通过 ruby 的 monkey patch 特性，重写了 require 函数的实现。&lt;/p&gt;

&lt;p&gt;gem 一般安装到和 site_ruby 平级的 gems 目录下面，我们主要关心 gems(代码) 目录和 specifications(gemspec) 目录。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rubygems require 解析&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;此部分基于 2.3.4 的 ruby 源码分析。&lt;/p&gt;

&lt;p&gt;文件跳转有点晕，觉得麻烦的朋友，可以略过。结论是把 对应的 gem 的 gems 目录添加到 $LOAD_PATH 变量里面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;当加载 lib/rubygems.rb 时，会调用 Gem::Specification.load_defaults 代码 # 1.9 自动加载&lt;/li&gt;
&lt;li&gt;lib/rubygems/specification.rb#load_defaults 会把 specifications 目录下的所有 gemspec 文件的 files 描述的文件通过 lib/rubygems.rb#register_default_spec 方法注册到 &lt;a href="/path_to_default_spec" class="user-mention" title="@path_to_default_spec"&gt;&lt;i&gt;@&lt;/i&gt;path_to_default_spec&lt;/a&gt;_map 变量。key 文件名，value 为 spec 对象&lt;/li&gt;
&lt;li&gt;require 方法会调用 lib/rubygems.rb#find_unresolved_default_spec , find_unresolved_default_spec 拼上 .rb .so 在 &lt;a href="/path_to_default_spec" class="user-mention" title="@path_to_default_spec"&gt;&lt;i&gt;@&lt;/i&gt;path_to_default_spec&lt;/a&gt;_map 变量里查找，如果找到，则返回对应的 spec , 再调用 lib/rubygems.rb#remove_unresolved_default_spec 方法，从 &lt;a href="/path_to_default_spec" class="user-mention" title="@path_to_default_spec"&gt;&lt;i&gt;@&lt;/i&gt;path_to_default_spec&lt;/a&gt;_map 变量删除这个 spec 的相关值，防止重复加载。&lt;/li&gt;
&lt;li&gt;最后再调用  lib/rubygems/core_ext/kernel_gem.rb#gem, lib/rubygems/specification.rb#activate ,  lib/rubygems/specification.rb#add_self_to_load_path 再把这个 gems 添加到 $LOAD_PATH 变量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;演示代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;puts Gem.instance_eval("@path_to_default_spec_map.keys.any?{|k| k =~ /minitest/}") # true
puts $LOAD_PATH # 没有 minitest gems
puts require 'minitest' # true
puts Gem.instance_eval("@path_to_default_spec_map.keys.any?{|k| k =~ /minitest/}") # false
puts $LOAD_PATH # 有 minitest gems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到在 require 之前与之后的差别，多了 minitest gem 的 lib 路径 ( /home/outman/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/minitest-5.4.3/lib ) .&lt;/p&gt;

&lt;p&gt;最终结论是 rubygems 所做的一切，只是为了把 gem 的 lib 目录添加到 $LOAD_PATH 变量里，再用原生的 require 方法加载。&lt;/p&gt;
&lt;h4 id="Bundler"&gt;Bundler&lt;/h4&gt;
&lt;p&gt;个人现在的使用习惯是 rbenv + bundler .而不是使用 rvm 的 gemset . 项目第一次执行 bundle install 加 --path=vendor/bundle 参数，把 gem 安装到项目的 vendor/bundle 目录下。再在 git 忽略此目录。&lt;/p&gt;

&lt;p&gt;这样做就不会因为多个项目安装 gem 到系统目录，而导致系统里的 gem 冲突。&lt;/p&gt;

&lt;p&gt;Bundler 和 Rubygems 一样，最终还是为了把项目的 gem 的 lib 目录添加到 $LOAD_PATH 变量里。&lt;/p&gt;

&lt;p&gt;演示代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby -e 'puts $LOAD_PATH'
bundle exec ruby -e 'puts $LOAD_PATH' #可以看到 bundle 把项目 Gemfile 里定义的所有 gem 的 lib 目录都已经加到 $LOAD_PATH 变量里.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;源码简单解析：&lt;/p&gt;

&lt;p&gt;bundle exec 主要修改 PATH  RUBYOPT RUBYLIB 变量，再用 exec 函数替换当前进程，从而继承修改后的 PATH RUBYOPT RUBYLIB 环境变量。&lt;/p&gt;

&lt;p&gt;exec 后的新进程读取 RUBYOPT 环境变量的 -rbundler/setup 值，从而会先加载运行 bundler/setup 这个文件的代码。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lib/bundler/cli/exec.rb#run 方法&lt;/li&gt;
&lt;li&gt;SharedHelpers.set_bundle_environment 

&lt;ul&gt;
&lt;li&gt;把 bundle 的 bin 目录加到了 PATH 环境变量 &lt;/li&gt;
&lt;li&gt;bundle exec ruby -e 'puts ENV["PATH"]' &lt;/li&gt;
&lt;li&gt;再把 -rbundler/setup 添加到 RUBYOPT 变量。&lt;/li&gt;
&lt;li&gt;ruby -h, -rlibrary       require the library before executing your script&lt;/li&gt;
&lt;li&gt;echo "puts 123" &amp;gt; /tmp/s.rb; ruby -r '/tmp/s.rb' -e 'puts 456'&lt;/li&gt;
&lt;li&gt;把 bundle 的 lib 目录添加到 RUBYLIB 变量&lt;/li&gt;
&lt;li&gt;RUBYLIB=/tmp ruby -e 'puts $LOAD_PATH' # 把 /tmp 添加到 $LOAD_PATH 的第一位了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;再执行 Kernel.exec ,用 exec 参数后面的命令替换当前进程。新进程会在修改的 ENV 执行。&lt;/li&gt;
&lt;li&gt;lib/bundler/setup.rb -&amp;gt; Bundler.setup  -&amp;gt; lib/bundler.rb -&amp;gt; load.setup -&amp;gt; lib/bundler/runtime.rb &lt;/li&gt;
&lt;li&gt;Runtime 从 Bundler.definition 里拿到所有 specs ,再遍历 specs，调用 Bundler.rubygems.loaded_specs 方法把所有 gem 都加载到 $LOAD_PATH .

&lt;ul&gt;
&lt;li&gt;bundle exec ruby -e 'puts Bundler::Runtime.new(Bundler.root, Bundler.definition).requested_specs.first.inspect&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="rbenv"&gt;rbenv&lt;/h4&gt;
&lt;p&gt;rbenv 的原理和 bundler 差不多，主要是先修改环境变量，再调用 exec 替换当前进程。&lt;/p&gt;

&lt;p&gt;在 rbenv 环境我们调用 which ruby 命令可以看到，ruby 执行文件总是在 ~/.rbenv/shims 目录下面。shims 目录下的 ruby 脚本会根据 .ruby-version 文件，找到对应 ruby 的执行文件路径，修改好环境变量后，再执行 exec 命令。&lt;/p&gt;
&lt;h4 id="rvm"&gt;rvm&lt;/h4&gt;
&lt;p&gt;rvm 与 rbenv 不同，rbenv 实现类似于设计模式里的委托模式，所有的 ruby 执行都交给 ~/.rbenv/shims 目录下的执行文件。&lt;/p&gt;

&lt;p&gt;而 rvm 简单粗暴，直接把对应版本的 ruby 的 bin 目录添加到 PATH 环境变量里。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rvm gemset 解析&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;rvm 的 gemset 主要是通过修改环境变量 GEM_HOME 和 GEM_PATH 变量来实现的。此两变量 rubygems 根据其值在值定义的目录查找 gem .&lt;/p&gt;

&lt;p&gt;演示代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvm gemset use 1.8.7@testset --create
env | grep GEM
GEM_HOME=/usr/local/rvm/gems/ruby-1.8.7-head@testset
GEM_PATH=/usr/local/rvm/gems/ruby-1.8.7-head@testset:/usr/local/rvm/gems/ruby-1.8.7-head@global
gem install rack
cd /usr/local/rvm/gems/ruby-1.8.7-head@testset; ls
#bin  build_info  cachedoc  environment  gemsspecificationswrappers
ruby -e 'require "rubygems"; puts Gem.paths.path.inspect'
#["/usr/local/rvm/gems/ruby-1.8.7-head@testset", "/usr/local/rvm/gems/ruby-1.8.7-head@global"]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到把 gemset 的目录添加到 Gem.paths 变量里面去了。而且固定有 global 目录，这样当我们把 gem 安装到 global 的 gemset 里，当在我们自己的 gemset 里找不到时，会去 global 的 gemset 目录里面找。&lt;/p&gt;
&lt;h4 id="总结"&gt;总结&lt;/h4&gt;
&lt;p&gt;$LOAD_PATH 很强大，利用它好，可以实现不错的 hack 技巧，但注意别让自己掉到坑里去了。&lt;/p&gt;

&lt;p&gt;为自己的博客做外链： &lt;a href="https://blog.mangege.com/tech/2016/03/27/1.html" rel="nofollow" target="_blank"&gt;https://blog.mangege.com/tech/2016/03/27/1.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Sun, 27 Mar 2016 16:45:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/29475</link>
      <guid>https://ruby-china.org/topics/29475</guid>
    </item>
    <item>
      <title>Spree 扩展机制分析</title>
      <description>&lt;p&gt;原文地址：&lt;a href="http://blog.mangege.com/tech/2015/09/20/1.html" rel="nofollow" target="_blank" title=""&gt;http://blog.mangege.com/tech/2015/09/20/1.html&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="参考资料"&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://guides.spreecommerce.com/developer/extensions_tutorial.html" rel="nofollow" target="_blank" title=""&gt;https://guides.spreecommerce.com/developer/extensions_tutorial.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://guides.spreecommerce.com/developer/deface_overrides_tutorial.html" rel="nofollow" target="_blank" title=""&gt;https://guides.spreecommerce.com/developer/deface_overrides_tutorial.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="扩展的分类"&gt;扩展的分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;类的扩展，主要是对 Model 与 Controller 进行修改。其它像 Concern 与 Helper 都从属于 Model 与 Controller，一般直接改 Model 与 Controller 即可。&lt;/li&gt;
&lt;li&gt;视图的扩展，主要是对 Html 视图进行修改。JS 与 CSS 因为可以通过代码加载顺序来重写现有功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="类的扩展"&gt;类的扩展&lt;/h3&gt;
&lt;p&gt;类的扩展的实现主要是基于 Ruby 的 Open classes 特性实现。&lt;/p&gt;

&lt;p&gt;创建一个测试项目，请先参考 &lt;a href="https://github.com/spree/spree" rel="nofollow" target="_blank" title=""&gt;https://github.com/spree/spree&lt;/a&gt; 建立一个 Rails Project .
图省事，就不用 &lt;code&gt;spree extension&lt;/code&gt; 命令建立一个 Rails engine ,而直接在 Rails Project 写代码测试。&lt;/p&gt;
&lt;h4 id="示例一: 访问首页时在控制台打印文字"&gt;示例一：访问首页时在控制台打印文字&lt;/h4&gt;
&lt;p&gt;添加 app/controllers/spree/home_controller_decorator.rb 文件，文件内容如下：&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;Spree&lt;/span&gt;
  &lt;span class="no"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:old_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&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="s1"&gt;'#'&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; index test"&lt;/span&gt;
      &lt;span class="n"&gt;old_index&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;alias_method 是 Rails 的方法，用于重命名现有的方法并删除，方便重写方法时再调用老的方法。&lt;/p&gt;

&lt;p&gt;Open classes 除了可以用 class_eval 这样来实现，还可以直接用 &lt;code&gt;class A; end&lt;/code&gt; 这样的类定义语法来实现同样的功能。&lt;br&gt;
之所以用 class_eval ,有两个个人能想到的优点： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用 class_eval 这种形式，肯定会先把原来的 class 给加载，而用类定义语法就不一定了。&lt;/li&gt;
&lt;li&gt;类定义语法，再次打开类，还需要记得原来的 class 的父类，如果不同的话，到时会报 &lt;code&gt;superclass mismatch for class&lt;/code&gt; 错误。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;文件名结尾一定要以 decorator 结尾，这样才能保证在开发模式时，每次自动请求会自动重新加载此文件。&lt;/p&gt;
&lt;h5 id="decorator 分析"&gt;decorator 分析&lt;/h5&gt;
&lt;p&gt;查看 Spree 源码的 core/lib/spree/core/engine.rb 文件，可以看到这样一段代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Load application's model / class decorators&lt;/span&gt;
  &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&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;join&lt;/span&gt;&lt;span class="p"&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;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'../../../app/**/*_decorator*.rb'&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="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_classes&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&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;to_prepare 为 Rails 的方法，此处用来加载 decorator 文件.
glob 用来查找所有包含 &lt;code&gt;_decorator&lt;/code&gt; 的文件。&lt;br&gt;
Rails.configuration.cache_classes 判断是否开启类缓存，开启的话，用 require 加载文件，可以防止重复加载。否则用 load 方法，这样能保证每次请求，decorator 的代码都是最新的。 &lt;/p&gt;
&lt;h5 id="to_prepare 分析"&gt;to_prepare 分析&lt;/h5&gt;
&lt;p&gt;在项目里的 config/application.rb 文件增加以下内容：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&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="s1"&gt;'$'&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to_prepare test"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 rails server, 可以看到在启动后，就执行了添加的回调。但再次访问不会执行回调。随便修改一个 controller 文件，可以看到回调再次执行了.
基于 to_prepare 方法，这样就可以保证被修改的类不会被漏加载。&lt;/p&gt;
&lt;h3 id="视图的扩展"&gt;视图的扩展&lt;/h3&gt;
&lt;p&gt;视图的扩展有两种实现方法&lt;/p&gt;
&lt;h4 id="1. 基于 Rails view path的加载顺序实现"&gt;1. 基于 Rails view path 的加载顺序实现&lt;/h4&gt;
&lt;p&gt;添加 app/views/spree/home/index.html 文件，内容随便写点，比如 &lt;code&gt;hello&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;再次访问首页，可以看到首页的内容变成 hello 去了。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://guides.rubyonrails.org/action_view_overview.html#view-paths" rel="nofollow" target="_blank" title=""&gt;View Paths&lt;/a&gt; 这一章的文档刚好没有，所以个人简单的介绍一下。&lt;/p&gt;

&lt;p&gt;在 rails console 运行 &lt;code&gt;ActionController::Base.view_paths.each{|a| puts a.to_path}; nil&lt;/code&gt; , 可以看到所有视图目录，Rails 是在这些目录下一个一个找，找到了就停止查找。可以看到，Rails Proejct 的目录是在最前面的。&lt;/p&gt;

&lt;p&gt;这种方式会替换此视图，没办法像 Deface 可以根据 DOM 查找添加内容到指定位置，或删除指定节点。&lt;/p&gt;

&lt;p&gt;删除 app/views/spree/home/index.html 文件，方便再测试。&lt;/p&gt;
&lt;h4 id="2. 基于 Deface 实现"&gt;2. 基于 Deface 实现&lt;/h4&gt;
&lt;p&gt;示例在首页的侧边添加一行 Hello world&lt;/p&gt;

&lt;p&gt;在 Rails 项目里新建 app/overrides/add_hello_to_home.rb 文件，文件内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Deface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Override&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;:virtual_path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'spree/home/index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'add_hello_to_home'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:insert_after&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"erb[silent]:contains('sidebar')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;p&amp;gt;&amp;lt;%= 'hello world' * 10 %&amp;gt;&amp;lt;/p&amp;gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后访问首页，可以看到侧边顶部增加一行 hello world.&lt;/p&gt;

&lt;p&gt;执行 &lt;code&gt;rake deface:precompile&lt;/code&gt; 命令，可以看到生成了 app/compiled_views/spree/home/index.html.erb 文件内容，内容如下：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;content_for&lt;/span&gt; &lt;span class="ss"&gt;:sidebar&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="s1"&gt;'hello world'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-hook=&lt;/span&gt;&lt;span class="s"&gt;"homepage_sidebar_navigation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:partial&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'spree/shared/taxonomies'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-hook=&lt;/span&gt;&lt;span class="s"&gt;"homepage_products"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_key_for_products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:partial&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'spree/shared/products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:locals&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而原始文件 frontend/app/views/spree/home/index.html.erb 内容如下：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;content_for&lt;/span&gt; &lt;span class="ss"&gt;:sidebar&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-hook=&lt;/span&gt;&lt;span class="s"&gt;"homepage_sidebar_navigation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:partial&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'spree/shared/taxonomies'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-hook=&lt;/span&gt;&lt;span class="s"&gt;"homepage_products"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_key_for_products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:partial&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'spree/shared/products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:locals&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 rails console，再运行 &lt;code&gt;ActionController::Base.view_paths.each{|a| puts a.to_path}; nil&lt;/code&gt; 语句，可以看到，app/compiled_views 这个目录的顺序是在 app/views 前面，排在第一位，所以最终还是靠 view paths 来实现的。&lt;/p&gt;

&lt;p&gt;deface 的作用是用来修改 erb 文件，但它解决了 erb 不能通过 dom 树来查找的问题。&lt;/p&gt;

&lt;p&gt;分析 deface 的源码发现，在 lib/deface/parser.rb 此文件，可以知道 deface 只是简单的把 &lt;code&gt;&amp;lt;%= %&amp;gt; &amp;lt;% %&amp;gt;&lt;/code&gt; 替换成 &lt;code&gt;&amp;lt;erb loud&amp;gt; &amp;lt;erb silent&amp;gt; &amp;lt;/erb&amp;gt;&lt;/code&gt; 这样的非标准的 html 标签，再通过 Nokogiri 解析，执行 deface override 代码里的替换，替换完后再把 erb 标签替换回来。&lt;/p&gt;
&lt;h3 id="结尾"&gt;结尾&lt;/h3&gt;
&lt;p&gt;示例项目源码：&lt;a href="https://github.com/mangege/spree_hack_example" rel="nofollow" target="_blank" title=""&gt;https://github.com/mangege/spree_hack_example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;为类增加代码很简单，但删除就很麻烦。比如从 Model 移除一个属性的 validate ,这个时候需要分析 Rails 的 validate 的实现，再写 hack 代码。&lt;/p&gt;

&lt;p&gt;单元测试非常重要，因为没有单元测试，你没有办法保证你的 hack 代码在下个版本的 spree 和 rails 还是能正常运行。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Sun, 20 Sep 2015 18:59:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/27407</link>
      <guid>https://ruby-china.org/topics/27407</guid>
    </item>
    <item>
      <title>来斗鱼看逗逼程序员直接写 Ruby 代码</title>
      <description>&lt;p&gt;&lt;a href="http://www.douyutv.com/codemonkeys" rel="nofollow" target="_blank"&gt;http://www.douyutv.com/codemonkeys&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;娱乐一下而已，已停，后期会找一些真正的娱乐的代码来直播.
目前播放 ffmpeg 的 hello world 并显示当前时间&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 11 Feb 2015 10:34:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/24217</link>
      <guid>https://ruby-china.org/topics/24217</guid>
    </item>
    <item>
      <title>怎么查 Rails 应用的灵异问题? (结帖,问题解决,但具体原因不清楚)</title>
      <description>&lt;p&gt;应用组合 nginx + puma + mysql，网站有时报 502 的错误，查 nginx 错误日志显示是&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[error] 992#0: *68629 upstream timed out (110: Connection timed out) while reading response header from upstream, client:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看日志是 nginx 连接 puma 有问题，某些请求运行时间过长.
以为是代码写的有问题，参考  &lt;a href="http://underthehood.meltwater.com/blog/2014/03/21/debugging-unicorn-rails-timeouts/" rel="nofollow" target="_blank" title=""&gt;Debugging, Unicorns, Rails and Timeouts&lt;/a&gt;  这篇的指导，运行了 24 小时，下面是一些日志摘要&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W, [2015-01-09T18:27:22.815086 #9680]  WARN -- : /null? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T18:27:43.346427 #9680]  WARN -- : /null? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T18:29:35.144107 #9680]  WARN -- : /api/product_orders?page=1&amp;amp;token=xxxx&amp;amp;10=10 I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T18:30:03.399384 #9680]  WARN -- : /api/product_orders?page=1&amp;amp;token=xxxx&amp;amp;10=10 I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:51:58.257420 #9674]  WARN -- : /farm_products? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:51:58.347016 #9672]  WARN -- : /farm_products/155? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:52:14.973153 #9672]  WARN -- : /farm_products? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:52:26.380556 #9672]  WARN -- : /farm_products? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:52:38.458941 #9672]  WARN -- : /farm_products? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:52:48.322228 #9680]  WARN -- : /farm_products/155? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T19:53:30.383086 #9680]  WARN -- : /? I’m about to timeout bringing down my unicorn worker too :(
W, [2015-01-09T20:02:16.656869 #9680]  WARN -- : /? I’m about to timeout bringing down my unicorn worker too :(
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些请求有时会超过 8 秒钟，但你去访问的时候又正常了，随机在不同的请求路径出现.
最不可理解的是 "/null?" 这个是 android 手机端调用的 bug，地址是不存在的，也就是说在在 routes 就被返回 404 了，根本不会有什么数据库查询之类的，但它处理时间居然超过了 8 秒&lt;/p&gt;

&lt;p&gt;服务器应该也没有问题，查了同一时间能正常响应的请求，最快的请求 9ms 就完成了，一般 100ms 左右&lt;/p&gt;

&lt;p&gt;感觉也不像是 puma 的问题，因为其它的网站也有用 puma，没有出现这问题。&lt;/p&gt;

&lt;p&gt;也不像代码有死循环问题，从 puma 进程启动时间来看，只要我没有部署手动重启，启动时间不会变.god 都没有因为超过内存或 cpu 使用限制而杀掉 puma 进程，因为没有超过限制过&lt;/p&gt;

&lt;p&gt;自己对此问题实在不知道怎么下手了，所以用论坛的大牛们求救？指点一下有没有什么工具或方法查此类问题。先说声谢谢了&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Sat, 10 Jan 2015 10:37:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/23653</link>
      <guid>https://ruby-china.org/topics/23653</guid>
    </item>
    <item>
      <title>有网页界面配置角色权限的 Rails 的开源系统参考吗?</title>
      <description>&lt;p&gt;spree 基于 cancan, Refinery Radiant CMS 权限管理都很简单，角色可操作的资源都是在代码写死的。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Fri, 29 Aug 2014 17:14:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/21289</link>
      <guid>https://ruby-china.org/topics/21289</guid>
    </item>
    <item>
      <title>你们的 Redmine 是最新版本吗?</title>
      <description>&lt;p&gt;感觉升级好麻烦，特别当还用了一些第三方插件时&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 02 Jul 2014 16:46:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/20278</link>
      <guid>https://ruby-china.org/topics/20278</guid>
    </item>
    <item>
      <title>构建你自己的 BAE Ruby Runtime</title>
      <description>&lt;p&gt;项目地址：&lt;a href="https://github.com/mangege/baerr" rel="nofollow" target="_blank"&gt;https://github.com/mangege/baerr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一般来说，不需要重新构建，直接拿现有的二进制压缩包就行了，信不过的可以自己编译构建。&lt;/p&gt;

&lt;p&gt;现有的 Rails 项目部署到 BAE 的流程
&lt;a href="https://github.com/mangege/baerr/blob/master/APP_SETUP.md" rel="nofollow" target="_blank"&gt;https://github.com/mangege/baerr/blob/master/APP_SETUP.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;示例 Rails 项目
&lt;a href="http://pan.baidu.com/s/1c0gnQ28" rel="nofollow" target="_blank"&gt;http://pan.baidu.com/s/1c0gnQ28&lt;/a&gt;&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Sun, 04 May 2014 19:08:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/19008</link>
      <guid>https://ruby-china.org/topics/19008</guid>
    </item>
    <item>
      <title>[已解决] Faye 多 Tab 怎样只通知一次?</title>
      <description>&lt;p&gt;打开了多个 Tabl，订阅同一 channel，发送消息后所有 Tab 都接收到了，然后所有都调用 html5 notification 显示，一下子显示好几条 html5 notification，有办法打开多个 Tab 只最终只有一个接收到消息并处理吗？&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 30 Apr 2014 13:47:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/18935</link>
      <guid>https://ruby-china.org/topics/18935</guid>
    </item>
    <item>
      <title>貌似 BAE 跑 Rails 速度还可以</title>
      <description>&lt;p&gt;&lt;a href="http://hellorails.duapp.com/posts" rel="nofollow" target="_blank"&gt;http://hellorails.duapp.com/posts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;nginx+puma(1 worker), 256RAM&lt;/p&gt;

&lt;p&gt;简单的连接 Mysql 查询，X-Runtime:0.010077&lt;/p&gt;

&lt;p&gt;ab 简单测试结果&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ab -n 1000 -c 100 http://hellorails.duapp.com/posts

Server Software:        nginx/1.5.13
Server Hostname:        hellorails.duapp.com
Server Port:            80

Document Path:          /posts
Document Length:        1242 bytes

Concurrency Level:      100
Time taken for tests:   15.148 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      2099000 bytes
HTML transferred:       1242000 bytes
Requests per second:    66.01 [#/sec] (mean)
Time per request:       1514.832 [ms] (mean)
Time per request:       15.148 [ms] (mean, across all concurrent requests)
Transfer rate:          135.32 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       28   30   1.5     30      34
Processing:    66 1115 618.9   1073    7491
Waiting:       66 1109 601.5   1072    7466
Total:         94 1145 618.8   1102    7519

Percentage of the requests served within a certain time (ms)
  50%   1102
  66%   1176
  75%   1213
  80%   1246
  90%   1507
  95%   1586
  98%   2568
  99%   4272
 100%   7519 (longest request)

&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# production.log
I, [2014-04-29T09:22:17.227352 #1908]  INFO -- : Started GET "/posts" for 222.240.208.131 at 2014-04-29 09:22:17 +0800
I, [2014-04-29T09:22:17.229053 #1908]  INFO -- : Processing by PostsController#index as HTML
I, [2014-04-29T09:22:17.233108 #1908]  INFO -- :   Rendered posts/index.html.erb within layouts/application (2.9ms)
I, [2014-04-29T09:22:17.233823 #1908]  INFO -- : Completed 200 OK in 5ms (Views: 2.8ms | ActiveRecord: 1.1ms)
I, [2014-04-29T09:22:19.549007 #1908]  INFO -- : Started GET "/posts" for 222.240.208.131 at 2014-04-29 09:22:19 +0800
I, [2014-04-29T09:22:19.552775 #1908]  INFO -- : Processing by PostsController#index as HTML
I, [2014-04-29T09:22:19.557303 #1908]  INFO -- :   Rendered posts/index.html.erb within layouts/application (3.4ms)
I, [2014-04-29T09:22:19.558028 #1908]  INFO -- : Completed 200 OK in 5ms (Views: 2.9ms | ActiveRecord: 1.5ms)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="/mobiwolf" class="user-mention" title="@mobiwolf"&gt;&lt;i&gt;@&lt;/i&gt;mobiwolf&lt;/a&gt; 自定义 Ruby 运行时目前就差 Supervisor 启动问题了，在 &lt;a href="http://cloudqa.duapp.com/?qa=1280&amp;amp;qa_1=%E8%AF%B7%E9%97%AE%E4%B8%BA%E4%BB%80%E4%B9%88custom%E6%97%B6%E7%9A%84supervisord-conf%E6%B2%A1%E6%9C%89%E8%BF%90%E8%A1%8C" rel="nofollow" target="_blank" title=""&gt;cloudqa&lt;/a&gt; 发帖等待回复中 &lt;/p&gt;

&lt;p&gt;-============================================-&lt;/p&gt;

&lt;p&gt;项目源码已经放出，请看 &lt;a href="http://ruby-china.org/topics/19008" rel="nofollow" target="_blank"&gt;http://ruby-china.org/topics/19008&lt;/a&gt;&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Tue, 29 Apr 2014 09:33:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/18903</link>
      <guid>https://ruby-china.org/topics/18903</guid>
    </item>
    <item>
      <title>AngularJS 与移动端共用一套 API 吗?</title>
      <description>&lt;p&gt;写一个移动端的后台应用，因为只需要提供后台管理功能与移动端 API，所以想试试 AngularJS.&lt;/p&gt;

&lt;p&gt;想请教论坛里的高人，接口应该怎么写？&lt;/p&gt;

&lt;p&gt;网页与移动共用一套接口的问题就是返回的数据有些可能是不想要的.
比如文章下面有很多评论，获取文章详情时，移动端需要显示评论，而网页却不需要显示评论。&lt;/p&gt;

&lt;p&gt;看了谷歌的 API，有 fields 参数，允许返回的数据显示你想要的字段.
&lt;a href="https://developers.google.com/google-apps/calendar/v3/reference/calendarList/get" rel="nofollow" target="_blank"&gt;https://developers.google.com/google-apps/calendar/v3/reference/calendarList/get&lt;/a&gt;
不晓得 rails 有没有类似的 gem&lt;/p&gt;

&lt;p&gt;网页与移动分开写一套接口的问题就是有些地方的代码会重复，不符合 DRY&lt;/p&gt;

&lt;p&gt;还有一个问题就是网页端验证是采用 cookie 还是 token?&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Mon, 10 Mar 2014 16:26:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/17784</link>
      <guid>https://ruby-china.org/topics/17784</guid>
    </item>
    <item>
      <title>Rails 4.0.2 发布了,安全更新</title>
      <description>&lt;p&gt;小伙伴们赶快升级吧
&lt;a href="http://weblog.rubyonrails.org/2013/12/3/Rails_3_2_16_and_4_0_2_have_been_released/" rel="nofollow" target="_blank"&gt;http://weblog.rubyonrails.org/2013/12/3/Rails_3_2_16_and_4_0_2_have_been_released/&lt;/a&gt;&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 04 Dec 2013 14:23:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/15992</link>
      <guid>https://ruby-china.org/topics/15992</guid>
    </item>
    <item>
      <title>给手机用的 Web API 应该返回什么样的错误信息</title>
      <description>&lt;p&gt;目前的仿 github 的，返回的消息格式有点不一致.
Java 端调用时，因为格式不统一，自动映射有点困难。&lt;/p&gt;

&lt;p&gt;比如 github 的 403 错误一般是直接给个 message,errors 无&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "message": "API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details."
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;422 错误时返回的带 errors，这样的格式貌似手机端解析出来不太好显示给用户，得自己解析 code 再显示自己写的错误信息.
另外碰到非 field 错误时应该怎么处理。例如找回密码，邮箱等信息是正确的，但是发送邮件的时候失败了，那应该返回什么样的信息格式给调用端。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "message": "Validation Failed",
   "errors": [
     {
       "resource": "Issue",
       "field": "title",
       "code": "missing_field"
     }
   ]
 }
```

twiiter的消息格式不错,直接返回一个errors父接点,数组里面再是错误信息与对应的code.需要直接显示错误的直接显示,不想直接显示自己判断code,再显示自己的错误信息.
也许可以考虑再加入一个field字段,这样手机端就能很好的定位是那个输入框的数据有问题,从而高亮提醒用户.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{"errors":[{"message":"Bad Authentication data","code":215}]}&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
github的错误响应文档: http://developer.github.com/v3/#client-errors
twitter的错误响应文档 https://dev.twitter.com/docs/error-codes-responses

待讨论的问题有两点
* 错误消息的格式
* 错误代码的设计


---------------

另外自己碰到一个坑,list返回最好包含一个root接点,像github一样直接返回一个数组,Java是不能很好处理自动映射的.
http://developer.github.com/v3/issues/#list-issues
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>cxh116</author>
      <pubDate>Thu, 21 Nov 2013 15:26:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/15706</link>
      <guid>https://ruby-china.org/topics/15706</guid>
    </item>
    <item>
      <title>出考题了,用命令或简单脚本快速统计锦江 csv 记录再排序</title>
      <description>&lt;p&gt;这个 csv 的头&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name,CardNo,Descriot,CtfTp,CtfId,Gender,Birthday,Address,Zip,Dirty,District1,District2,District3,District4,District5,District6,FirstNm,LastNm,Duty,Mobile,Tel,Fax,EMail,Nation,Taste,Education,Company,CTel,CAddress,CZip,Family,Version,id
陈xx,,,OTH,010-116321,M,19000101,北京市海淀区xxx,100080, ,,CHN,0,0,,,,,,10116,010-xx,010-xx-208,xx@xx.com.cn,,,,,,,,0,2012-12-23 11:13:38,2
赵xx,,,ID,21010219880204xxxx,M,1988xxx,-,-, ,,CHN,21,210102,,,,,,186024xxx,-,-,xxx@qq.com,,,,,,,,0,2011-3-22 17:35:12,100

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如统计最白菜的名字，或邮箱后缀等&lt;/p&gt;

&lt;p&gt;使用的源文件 &lt;a href="https://www.copy.com/s/UyiPPS4mZtnZ/Public/shifenzheng.csv.zip" rel="nofollow" target="_blank"&gt;https://www.copy.com/s/UyiPPS4mZtnZ/Public/shifenzheng.csv.zip&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;另外问个问题，为什么用 sort 和 uniq 命令排序统计这么省内存，如果用 ruby 实现这样省内存的排序统计&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Wed, 16 Oct 2013 14:36:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/14794</link>
      <guid>https://ruby-china.org/topics/14794</guid>
    </item>
    <item>
      <title>买个便宜的国外 VPS 也不错</title>
      <description>&lt;p&gt;搞了个 128M 内存的 openvz vps 玩玩，年付不到 100 元。感觉还是挺值的.虽然有时不太稳定。&lt;/p&gt;

&lt;p&gt;用处有以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;看不能看的网站。&lt;/li&gt;
&lt;li&gt;下载国外低速资源，机房选东海岸的，由原来的几十 KB 速度上升到几百 KB 的速度。&lt;/li&gt;
&lt;li&gt;反向代理 openshift,appfog.这些云平台还是挺不错的，不过已经都不能访问了，像 appfog 在东海岸有机房，VPS ping 过去，延迟只有 7ms，访问速度还是可以接受的.

&lt;ul&gt;
&lt;li&gt;示例：&lt;a href="http://h-fs.mangege.com" rel="nofollow" target="_blank"&gt;http://h-fs.mangege.com&lt;/a&gt; ,部署在 appfog，学习 wordpress android 用的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;顺便，免费贡献两个反代小站名额，不过 VPS 是 budgetvm 的，稳定性一般，放些学习项目还是可以的。&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Tue, 20 Aug 2013 09:56:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/13449</link>
      <guid>https://ruby-china.org/topics/13449</guid>
    </item>
    <item>
      <title>rails4 之 jbuilder 坑</title>
      <description>&lt;p&gt;看着 &lt;a href="https://github.com/rails/jbuilder" rel="nofollow" target="_blank"&gt;https://github.com/rails/jbuilder&lt;/a&gt; 文档，尝试用 partial
&lt;code&gt;json.partial! partial: 'posts/post', collection: @posts, as: :post&lt;/code&gt;能打印出结果，但只是拿第一个值.
&lt;code&gt;json.partial! 'posts/post', collection: @posts, as: :post&lt;/code&gt; 报错
&lt;code&gt;json.array! @posts, partial: 'posts/post', as: :post&lt;/code&gt; 还是报错&lt;/p&gt;

&lt;p&gt;无意中看下 gemfile, 版本显示为&lt;code&gt;gem 'jbuilder', '~&amp;gt; 1.2&lt;/code&gt;,而 rubygems 上面显示的&lt;code&gt;gem "jbuilder", "~&amp;gt; 1.5.0"&lt;/code&gt;.
修改，升级，终于可以正常使用 partial 功能了&lt;/p&gt;</description>
      <author>cxh116</author>
      <pubDate>Thu, 01 Aug 2013 16:31:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/12978</link>
      <guid>https://ruby-china.org/topics/12978</guid>
    </item>
  </channel>
</rss>
