<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kamiiyu (Vince Ho)</title>
    <link>https://ruby-china.org/kamiiyu</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>{今天: {Ruby: '24 岁', 我: '36 岁'}}</title>
      <description>&lt;p&gt;低头猛赶稿，已经不知道是过了多少个凌晨 3 点了，只知道令人惭愧的进度。&lt;br&gt;
Ruby 已经迎来 2.4 版本了，而手上的《Ruby 基础教程》第五版（对应 Ruby2.3）的稿件还有三分之一没完成。&lt;br&gt;
凑巧某处提到 1993 年 2 月 24 日是 Ruby 的“诞生日”，才发现原来今天就是 Ruby24 岁生日！&lt;br&gt;
回想 07 年知道 Ruby，11 年开始用 Ruby 至今，感慨万千。  &lt;/p&gt;

&lt;p&gt;“你以前搞 Java 的，为什么会想到转 Ruby 呢？”&lt;br&gt;
“Ruby 很小众，不好招人吧！？”&lt;br&gt;
“带过团队吗？”&lt;br&gt;
“Ruby 性能不好啊！”&lt;br&gt;
“Ruby 怎么那么慢？”&lt;br&gt;
“Ruby 怎么会这样？”&lt;br&gt;
“Ruby 怎么会那样？”&lt;br&gt;
“Ruby……和 PHP 差不多？”&lt;br&gt;
“Ruby……”&lt;br&gt;
“哦，那还是不要用 Ruby 吧！”   &lt;/p&gt;

&lt;p&gt;已经过了血气方刚的年龄，已经“不敢”和人争辩。&lt;br&gt;
听到，心里也就是咯噔一下，眉头皱一皱，然后……就过去了。  &lt;/p&gt;

&lt;p&gt;“因为 Ruby 会令你感到快乐，而且喜欢 Ruby 社区的氛围……”&lt;br&gt;
“不好招，都是找 PHP 转的，或者……”&lt;br&gt;
“带过，小团队，因为……”&lt;br&gt;
“但开发效率高啊，而且现在硬件性能也好……”&lt;br&gt;
“也还好吧，因为 Ruby 比较灵活，还有……”&lt;br&gt;
“其实是那样的……”&lt;br&gt;
“其实是这样的……”&lt;br&gt;
“也……是啦，主要都是用来做网页的……”&lt;br&gt;
“嗯……明白……说的也是……”&lt;br&gt;
“也……不是……特别地……反对用 NodeJS，……都行……”  &lt;/p&gt;

&lt;p&gt;认识 Ruby 以前，转职时讲的是“……想换个环境、平台……”。&lt;br&gt;
认识 Ruby 以后，讲的是“……因为公司不准备用 Ruby 了……”。  &lt;/p&gt;

&lt;p&gt;曾经背叛过。&lt;br&gt;
现在也在想是否应该放弃。  &lt;/p&gt;

&lt;p&gt;为什么要坚持。&lt;br&gt;
坚持的到底是什么。&lt;br&gt;
……&lt;br&gt;
还是用 Ruby 的时候才是最开心的。  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;感恩！&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;感谢！&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby!&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Happy Birthday!&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby 程序员！&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Happy Hacking!&lt;/strong&gt;  &lt;/p&gt;</description>
      <author>kamiiyu</author>
      <pubDate>Fri, 24 Feb 2017 23:48:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/32378</link>
      <guid>https://ruby-china.org/topics/32378</guid>
    </item>
    <item>
      <title>浅谈 ActiveRecord 的 N + 1 查询问题</title>
      <description>&lt;h2 id="ORM框架的性能小坑"&gt;ORM 框架的性能小坑&lt;/h2&gt;
&lt;p&gt;在使用 ActiveRecord 这样的 ORM 工具时，常会嵌套遍历 model。&lt;br&gt;
例如，有两个 model，Post、Comment，关系是一对多。  &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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&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;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总共有 4 个 post。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="no"&gt;COUNT&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="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取每个 post 的所有 comment，我们可以：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
  &lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="s2"&gt;"post_id"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="s2"&gt;"post_id"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到为了得到 4 条数据，我们执行了 5（4 + 1）次的查询，这就是所谓 N + 1 查询问题。  &lt;/p&gt;
&lt;h2 id="发现问题"&gt;发现问题&lt;/h2&gt;
&lt;p&gt;除了凭经验外，有一些 gem 也可以帮助我们提早发现 N + 1 查询问题。&lt;br&gt;
例如收费的&lt;a href="https://newrelic.com/" rel="nofollow" target="_blank" title=""&gt;New Relic&lt;/a&gt;，免费的&lt;a href="https://github.com/flyerhzm/bullet" rel="nofollow" target="_blank" title=""&gt;Bullet&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="解决问题"&gt;解决问题&lt;/h2&gt;&lt;h3 id="预加载"&gt;预加载&lt;/h3&gt;
&lt;p&gt;简单来说，就是提前加载 model 关系，让 ActiveRecord 预先加载所需要的数据。&lt;br&gt;
ActiveRecord 提供了以下三个方法预加载。  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;includes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eager_load&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;他们的区别可以参考&lt;a href="http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;或&lt;a href="https://ruby-china.org/topics/17866" title=""&gt;这里&lt;/a&gt;。&lt;br&gt;
以最常用的&lt;code&gt;includes&lt;/code&gt;方法为例。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
  &lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到的结果一样，但执行的查询只有两次。  &lt;/p&gt;
&lt;h3 id="傻瓜式预加载(Goldiloader)"&gt;傻瓜式预加载 (Goldiloader)&lt;/h3&gt;&lt;h4 id="传统预加载的“问题”"&gt;传统预加载的“问题”&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;includes&lt;/code&gt;方法的确很惊艳，但……  &lt;/p&gt;

&lt;p&gt;第一，代码不够优雅。&lt;br&gt;
例如，假设我们现在想找的是 id 在 1 到 3 之间的 post 的 comment。&lt;br&gt;
一般的我们的逻辑是，查找 id 在 1 到 3 之间的 post，获取各 post 的 comment 然后合并。&lt;br&gt;
而预加载后的逻辑是，查找 id 在 1 到 3 之间的 post，&lt;strong&gt;关联 comment，再&lt;/strong&gt;获取各 post 的 comment 然后合并。&lt;br&gt;
总觉得有点冗余。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;?)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二，不符合 DRY。&lt;br&gt;
既然我们都不喜欢 N + 1，那就应该从源头上杜绝，而不是每次查询时都要主动&lt;code&gt;includes&lt;/code&gt;一次。  &lt;/p&gt;
&lt;h4 id="Goldiloader"&gt;Goldiloader&lt;/h4&gt;
&lt;p&gt;懒癌程序员的救星&lt;a href="https://github.com/salsify/goldiloader" rel="nofollow" target="_blank" title=""&gt;Goldiloader&lt;/a&gt;——几乎完美的解决了以上两个问题。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'goldiloader'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bundle install&lt;/code&gt;以后，就可以用最直接（傻瓜）的方式点点点……  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;?)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="auto_include"&gt;auto_include&lt;/h5&gt;
&lt;p&gt;Goldiloader 默认自动加载所有关联数据，用&lt;code&gt;auto_include: false&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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_include: &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;h5 id="fully_load"&gt;fully_load&lt;/h5&gt;
&lt;p&gt;以下的方法比较特殊，如果关系已经加载了，则会直接返回已缓存的值，如果没被加载，则会通过 SQL 查询。  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first&lt;/li&gt;
&lt;li&gt;second&lt;/li&gt;
&lt;li&gt;third&lt;/li&gt;
&lt;li&gt;fourth&lt;/li&gt;
&lt;li&gt;fifth&lt;/li&gt;
&lt;li&gt;forty_two&lt;/li&gt;
&lt;li&gt;last&lt;/li&gt;
&lt;li&gt;size&lt;/li&gt;
&lt;li&gt;ids_reader&lt;/li&gt;
&lt;li&gt;empty?&lt;/li&gt;
&lt;li&gt;exists?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;假设现在我们需要获取每个 post 的最新的 comment。&lt;br&gt;
但这不是我们想要的。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
  &lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加选项&lt;code&gt;full_load: true&lt;/code&gt;后，当调用上述方法时，Goldiloader 会强制自动加载所需的关系。  &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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;fully_load: &lt;/span&gt;&lt;span class="kp"&gt;true&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
  &lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="Goldiloader也不是万能的"&gt;Goldiloader 也不是万能的&lt;/h4&gt;&lt;h5 id="has_one使用SQL limit时的隐患"&gt;has_one 使用 SQL limit 时的隐患&lt;/h5&gt;
&lt;p&gt;Goldiloader 是 ActiveRecord 的衍生工具，所以 ActiveRecord 预加载的副作用也一并继承了。&lt;br&gt;
现在我们自定义一个&lt;code&gt;has_one&lt;/code&gt;关系，用以获取最新的一条 comment。  &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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;fully_load: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Comment'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遍历 post 获取最新的 comment。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不使用 Goldiloader 或者预加载时，每条 SQL 自动回加上&lt;code&gt;limit 1&lt;/code&gt;。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
&lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&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="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt;  &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"LIMIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 Goldiloader 或者预加载时，世界变清净了，但同时会有性能隐患，因为 post 的数据量可能非常大。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"posts"&lt;/span&gt;
&lt;span class="no"&gt;Comment&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"post_id"&lt;/span&gt; &lt;span class="no"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;ORDER&lt;/span&gt; &lt;span class="no"&gt;BY&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt; &lt;span class="no"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="其他限制"&gt;其他限制&lt;/h5&gt;
&lt;p&gt;遇到以下的关系（方法），Goldiloader 会自动关闭自动预加载。  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;limit&lt;/li&gt;
&lt;li&gt;offset&lt;/li&gt;
&lt;li&gt;finder_sql&lt;/li&gt;
&lt;li&gt;group (due to a Rails bug)&lt;/li&gt;
&lt;li&gt;from (due to a Rails bug)&lt;/li&gt;
&lt;li&gt;joins (only Rails 4.0/4.1 - due to a Rails bug)&lt;/li&gt;
&lt;li&gt;uniq (only Rails 3.2 - due to a Rails bug)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="本文结束之前"&gt;本文结束之前&lt;/h2&gt;
&lt;p&gt;N + 1 查询问题是一个容易被忽略的问题。&lt;br&gt;
发现解决它也不难，&lt;code&gt;includes&lt;/code&gt;已经够用，Goldiloader 更是锦上添花，对新手足够友好。&lt;br&gt;
不过对于我这种被 Rails“坑”习惯的斯德哥尔摩症候群患者来说，没有&lt;code&gt;includes&lt;/code&gt;反而没安全感了&amp;gt;_&amp;lt;|||  &lt;/p&gt;
&lt;h2 id="参考文档"&gt;参考文档&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations" rel="nofollow" target="_blank" title=""&gt;Eager Loading Associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/salsify/goldiloader" rel="nofollow" target="_blank" title=""&gt;Goldiloader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.salsify.com/blog/engineering/automatic-eager-loading-rails" rel="nofollow" target="_blank" title=""&gt;AUTOMATIC EAGER LOADING IN RAILS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kamiiyu/n_plus_1_demo" rel="nofollow" target="_blank" title=""&gt;本文例子&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>kamiiyu</author>
      <pubDate>Thu, 23 Feb 2017 00:04:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/32364</link>
      <guid>https://ruby-china.org/topics/32364</guid>
    </item>
    <item>
      <title>非动态，也非静态，Ruby 3 Typing 的第三条路</title>
      <description>&lt;p&gt;Matz 在 2016 年的 Rubykaigi 里做了一个关于 Ruby3 Typing 的分享。&lt;br&gt;
首先简单介绍一下什么是&lt;a href="https://ja.wikipedia.org/wiki/RubyKaigi" rel="nofollow" target="_blank" title=""&gt;RubyKaigi&lt;/a&gt;。&lt;br&gt;
Kaigi 就是日语【会議】的罗马字母写法，顾名思义也就是在日本举行的 RubyConf。&lt;br&gt;
RubyKaigi 在 2006 年首次举行时的名字就是【日本 Ruby カンファレンス】（Japan Ruby Conference），由于容易与&lt;a href="http://rubycentral.org/" rel="nofollow" target="_blank" title=""&gt;Ruby Central&lt;/a&gt;混淆，因此在 2007 年改名为【日本 Ruby 会議】，直到 2011 年停办。&lt;br&gt;
2013 年大会重开，改名为【RubyKaigi】，并统一使用英语作为大会的官方名称。  &lt;/p&gt;

&lt;p&gt;今年的&lt;a href="http://rubykaigi.org/2016/" rel="nofollow" target="_blank" title=""&gt;RubyKaigi2016&lt;/a&gt;在 9 月 8 号到 10 号京都举行。由于近水流台，有不少像 Matz 这样的 Ruby committer 参与分享，所以大会含金量非常高。&lt;br&gt;
&lt;a href="http://rubykaigi.org/2016/" rel="nofollow" target="_blank" title=""&gt;官网&lt;/a&gt;中以及发布了所有视频，希望以后有机会可以亲身去感受下。  &lt;/p&gt;

&lt;p&gt;大会的首个 Topic 是 Matz 的 &lt;a href="http://rubykaigi.org/2016/presentations/yukihiro_matz.html" rel="nofollow" target="_blank" title=""&gt;Ruby3 Typing&lt;/a&gt;（&lt;a href="https://www.youtube.com/watch?v=2Ag8l-wq5qk" rel="nofollow" target="_blank" title=""&gt;油管直通车&lt;/a&gt;）  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/02.ruby3_typing.png" title="" alt="02.ruby3_typing"&gt;&lt;/p&gt;

&lt;p&gt;演讲者 Matz 就不多做介绍了。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/01.matz.png" title="" alt="01.matz"&gt;&lt;/p&gt;

&lt;p&gt;官方没有提供在线版的 PPT，我把这个日语演讲“翻译”成以下的文字，为了保持文字通顺，稍微加工了一下。   &lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="Ruby3 Typing（Matz）"&gt;Ruby3 Typing (Matz)&lt;/h2&gt;
&lt;p&gt;在 2010 年以来的新语言很多都是静态类型语言（Static Typed Languages），例如 TypeScript、Flow、Go、Swift 等等。&lt;br&gt;
与之相比，Ruby 没有静态类型，又是上世纪 90 年代语言，所以有些人会说“Ruby is Dead”、“Rails is Dead”。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/03.no_static_typing.png" title="" alt="03.no_static_typing"&gt;  &lt;/p&gt;

&lt;p&gt;但技术有时候就像钟摆，有时候偏向一种技术，有时候又偏向另外一种技术，这是常有的事情。例如静态类型与动态类型“之争”。  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1970s 到 1980s 左右，从 Smalltalk、lisp 摆到 Java、C++  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;接下来又从 Java 摆到 Ruby、JavaScript  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;到最近又从 Ruby、JavaScript 摆到 Swift、Go  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么未来 Swift、Go 又会摆到哪里去呢？未来 Ruby3 的类型又会有什么改变呢？  &lt;/p&gt;

&lt;p&gt;首先我们需要知道什么是 Ruby 的 type？对于动态类型语言来说，Class 不是类型。&lt;br&gt;
另外在 Ruby 其中一个重要的原则就是&lt;a href="https://en.wikipedia.org/wiki/Duck_typing" rel="nofollow" target="_blank" title=""&gt;Duck typing&lt;/a&gt;，也就是说对于一个 Ruby 对象来说，我们不关心她继承关系（inheritance），也不关心她的内部结构（structure），我们只关心她的行为（behaves）。  &lt;/p&gt;

&lt;p&gt;请看下面&lt;code&gt;String IO&lt;/code&gt;的例子。&lt;/p&gt;

&lt;p&gt;Ruby 版本的日志输出代码片段：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"error!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;静态类型版本：  &lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mesg&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby &lt;code&gt;StringIO&lt;/code&gt;版本：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;StringIO&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"error!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; retrieve string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的&lt;code&gt;StringIO&lt;/code&gt;例子在静态类型的世界行不通，因为&lt;code&gt;StringIO&lt;/code&gt;没有与&lt;code&gt;IO&lt;/code&gt;有共同的&lt;code&gt;superclass&lt;/code&gt;或&lt;code&gt;interface&lt;/code&gt;，所以无法通过编译。  &lt;/p&gt;

&lt;p&gt;Duck typing 使我们在开发的时候不需花时间研究各个 Class 间的关系，大大降低开发者的开发成本，而且扩展起来也更加灵活。&lt;br&gt;
所以我们可以认为，在 Ruby 的 type 就是“Duck”。“Duck”不是 Java 那样的&lt;a href="https://en.wikipedia.org/wiki/Nominal_type_system" rel="nofollow" target="_blank" title=""&gt;Nominal type&lt;/a&gt;，更不是 Class，它是一种被期待的行为。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/04.duck_is_behavior.png" title="" alt="04.duck_is_behavior"&gt;  &lt;/p&gt;

&lt;p&gt;“期待”只是存在于我们的脑子里面，很暧昧的想法，也正因此 Ruby 的 Type 可以很灵活，对比用 Class 来定义 Type 的方式会有很多限制。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/04.typing_by_class.png" title="" alt="04.typing_by_class"&gt;  &lt;/p&gt;

&lt;p&gt;对比 Nominal typing，我（Matz）更喜欢 Go 的 Interface，也就是&lt;a href="https://en.wikipedia.org/wiki/Structural_type_system" rel="nofollow" target="_blank" title=""&gt;Structural Subtyping&lt;/a&gt;这种方式。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/04.go_interface.png" title="" alt="04.go_interface"&gt;  &lt;/p&gt;

&lt;p&gt;在上面的例子中，上面三行定义一个包含&lt;code&gt;Write&lt;/code&gt;方法的 interface &lt;code&gt;LogDst&lt;/code&gt;，下面三行的 log 函数接受两个参数&lt;code&gt;LogDst&lt;/code&gt;和字符串&lt;code&gt;mesg&lt;/code&gt;。在这个函数里我们只需要&lt;code&gt;LogDst&lt;/code&gt;有&lt;code&gt;Write&lt;/code&gt;的行为（方法）就可以了，它可能是输出到 standard IO、String，或者其他什么地方，我们并不需要关心它的内部逻辑。&lt;br&gt;
Structural Subtyping 和 Duck typing 同样保持很好的灵活性，当然我（Matz）还是更喜欢 Ruby 的 Duck Typing :-)。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/05.duck_typing_is_awesome.png" title="" alt="05.duck_typing_is_awesome"&gt;    &lt;/p&gt;

&lt;p&gt;DRY(Don't repeat yourself) 是 Ruby 另外一个重要原则。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/06.do_not_repeat_yourself.png" title="" alt="06.do_not_repeat_yourself"&gt;    &lt;/p&gt;

&lt;p&gt;为了避免不必要的重复，我们不会在程序写实际上不需要的东西，也就是说 Ruby 程序的运行不依赖于 type annotations，因此我们就不需要它们，甚至要去除它们。  &lt;/p&gt;

&lt;p&gt;但是 Dynamic Typing 也是存在不少不足的地方。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在程序运行时能发现错误&lt;br&gt;
&lt;img src="http://kamiiyu.github.io/images/2016-11-30/07.errors_only_found_in_runtime.png" title="" alt="07.errors_only_found_in_runtime"&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;error message 不友好，信息量少，下面可能是我们最熟悉但又“莫名其妙”的错误信息&lt;br&gt;
&lt;img src="http://kamiiyu.github.io/images/2016-11-30/08.undefined_method.png" title="" alt="08.undefined_method"&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;如果测试覆盖不够，可能会有预想不到的 typing error&lt;/li&gt;
&lt;li&gt;缺少文档，像 Ruby 这样没有类型的语言写程序的时候非常爽，但读程序的时候就可能有困难了，所以有些人会写下面这样的注释&lt;br&gt;
&lt;img src="http://kamiiyu.github.io/images/2016-11-30/09.document.png" title="" alt="09.document"&gt;&lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可能会有人吐槽，最终不还是要把类型写出来吗。。。&lt;br&gt;
但。。。无论如何还是不想指定类型，绝对不想。。。（Matz 特别强调两次，全场都笑了）  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/10.do_not_want_to_specify_types.png" title="" alt="10.do_not_want_to_specify_types"&gt;    &lt;/p&gt;

&lt;p&gt;因为这样会降低程序的灵活性，但为了以后的维护，我们又希望有可读性好的文档。&lt;br&gt;
除了把类型信息像刚才那样写在注释外，还有另外一个做法是把他写在文档中。把类型写在文档里，但实际程序又不会做类型检查，到头来实际两边都没有讨好。&lt;br&gt;
至少对 Ruby 来说 Type Annotation，Mixed/Gradual 都不是好主意。  &lt;/p&gt;

&lt;p&gt;正是因为还有以上种种问题，Ruby 还有很多改进的空间，  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/11.room_for_improvement.png" title="" alt="11.room_for_improvement"&gt;    &lt;/p&gt;

&lt;p&gt;并且我们作为一个工程师应该要主动去解决这些问题。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/12.solve_problems.png" title="" alt="12.solve_problems"&gt;    &lt;/p&gt;

&lt;p&gt;有些人提出了 Static Typing with &lt;a href="https://en.wikipedia.org/wiki/Type_inference" rel="nofollow" target="_blank" title=""&gt;Type Inference&lt;/a&gt;的解决方案，但这个方案还是没能解决静态类型不够灵活的缺点。&lt;br&gt;
又有人提出 Gradual Typing 或者 Optinal Typing 的解决方案，但这两种类型实际还是静态类型，因此灵活性这个问题还是没能得到解决。&lt;br&gt;
Ruby 需要除上面以外其他的什么东西，一种像 Static Typing 这样进行类型检查，但又像 Duck Typing 这样灵活的类型。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/13.static_type_with_duck_typing.png" title="" alt="13.static_type_with_duck_typing"&gt;    &lt;/p&gt;

&lt;p&gt;暂且就把她叫做 Soft typing。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/14.soft_typing.png" title="" alt="14.soft_typing"&gt;    &lt;/p&gt;

&lt;p&gt;Soft typing 是一套用行为来定义的 Type System。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/15.soft_typing_system.png" title="" alt="15.soft_typing_system"&gt;    &lt;/p&gt;

&lt;p&gt;所谓行为就是一组的方法和参数数量、类型等。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/16.behavior_is_a_set_of_method.png" title="" alt="16.behavior_is_a_set_of_method"&gt;      &lt;/p&gt;

&lt;p&gt;回到刚才日志输出的例子，Go 版本的 interface 其实可以让程序自动生成，并且我们写程序的时候也不需要关心
interface(Type) 的名字 (取名字对有些人来说是件麻烦事)，  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/17.not_to_worry_about_type_names.png" title="" alt="17.not_to_worry_about_type_names"&gt;        &lt;/p&gt;

&lt;p&gt;因此我们只可以忽略这些细节，专注于程序开发。（Happy Programming 的真谛）  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/18.vague_ideas.png" title="" alt="18.vague_ideas"&gt;   &lt;/p&gt;

&lt;p&gt;例如，我们可以把 Type 信息搜集起来，就像放到数据库中一样。然后，我看可以从这个数据库中获取 Type 的定义和 Type 行为（方法）。&lt;br&gt;
我们也这些 Type 信息看做是一种表达式（expression），  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/19.retrieve_a_type_as_a_expression.png" title="" alt="19.retrieve_a_type_as_a_expression"&gt;   &lt;/p&gt;

&lt;p&gt;例如，我们可以检查当把 A 表达式赋值给 B 表达式时是否兼容。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/20.check_compatiblity.png" title="" alt="20.check_compatiblity"&gt;   &lt;/p&gt;

&lt;p&gt;我们也可以检查某个类型有没有对应的方法。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/21.check_method.png" title="" alt="21.check_method"&gt;   &lt;/p&gt;

&lt;p&gt;这样的做法也许并不能做到 100% 的类型检查，但还是比之前一点都没用要好。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/22.better_than_nothing.png" title="" alt="22.better_than_nothing"&gt;   &lt;/p&gt;

&lt;p&gt;如果找不到对应的类型信息，由于本来就是 dynamic typing，那么我们就退回到 dynamic typing 就可以了。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/23.fallback.png" title="" alt="23.fallback"&gt;   &lt;/p&gt;

&lt;p&gt;有两种方式实现 Soft typing。  &lt;/p&gt;

&lt;p&gt;一个是利用 ad-hoc type 的信息。&lt;br&gt;
例如，有 a 表达式（也有可能是变量），我们期望她有 gsub，slice，map 三个方法，如果找不到有对应的 class 满足这个条件的，那就抛出错误信息。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/24.ad_doc_example.png" title="" alt="24.ad_doc_example"&gt;   &lt;/p&gt;

&lt;p&gt;但对于在运行时不断动态添加或修改的方法，这种检查方式就无能为力了。  &lt;/p&gt;

&lt;p&gt;另外一个是在运行时搜集类型信息，  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/25.collect_from_runtime.png" title="" alt="25.collect_from_runtime"&gt;   &lt;/p&gt;

&lt;p&gt;特别是在测试的时候，  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/26.collect_from_test.png" title="" alt="26.collect_from_test"&gt;   &lt;/p&gt;

&lt;p&gt;一般 Libray 或者 Gem 都会进行测试，那么我们可以在测试的同时，建立类型数据库。&lt;br&gt;
这样我们就可以在发布 Gem 的同时，以某种方式一起创建和发布与之对应的类型数据库。    &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/27.build_type_database_from_gem.png" title="" alt="27.build_type_database_from_gem"&gt;     &lt;/p&gt;

&lt;p&gt;IDE 也可以利用这些 Type Database 的信息，让我们可以构造更加有效率、聪明的开发环境。&lt;br&gt;
可惜的是在先阶段以上这些暂时都还只是构想，我们还不能用到。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/28.still_mere_concept.png" title="" alt="28.still_mere_concept"&gt;     &lt;/p&gt;

&lt;p&gt;所以让我们一起期待 Ruby3 吧！  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/29.part_of_ruby3.png" title="" alt="29.part_of_ruby3"&gt;     &lt;/p&gt;

&lt;p&gt;最后，我们（Ruby committees）有一个很重要的信息传递给大家，我们是非常重视开发者的。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/30.we_care_about_you.png" title="" alt="30.we_care_about_you"&gt;     &lt;/p&gt;

&lt;p&gt;我们不会对 Dynamic typing 的“缺点“视而不见，或者叫开发者多做测试就了事，而是希望努力的改善 Ruby，让开发者有更好的开发体验。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/31.willing_improve_development_experience.png" title="" alt="31.willing_improve_development_experience"&gt;     &lt;/p&gt;

&lt;p&gt;关于 Ruby3 什么时候发布，目前还是不知道。。。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/32.i_dont_know.png" title="" alt="32.i_dont_know"&gt;     &lt;/p&gt;

&lt;p&gt;从 committee management 的角度来看，开源软件一般没有所谓的 dead line，也没有很明确的 road map，至少对于 Ruby 这个项目来说没有。但如果什么都没有又很难开展工作，因此我们对 Ruby3 的开发指定了 3 个目标。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/33_three_goals_of_ruby3.png" title="" alt="33_three_goals_of_ruby3"&gt;     &lt;/p&gt;

&lt;p&gt;就像当年美国登月一样，也是先定了一个困难、远大的目标，然后大家一起为之努力，最后成功。&lt;br&gt;
那 Ruby3 的三个目标什么时候才能实现呢？我（Matz）希望在下一次的日本奥运会的时候。。。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/34.wait_for_years.png" title="" alt="34.wait_for_years"&gt;     &lt;/p&gt;

&lt;p&gt;虽然 Ruby3 还”遥遥无期“，但 Ruby 前进的脚步是不会停止的。  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/35.keep_moving_forward.png" title="" alt="35.keep_moving_forward"&gt;   &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/36.i_promise.png" title="" alt="36.i_promise"&gt;     &lt;/p&gt;

&lt;p&gt;我们会一直不遗余力的帮助广大开发者在编程中找到乐趣--Happy Hacking！  &lt;/p&gt;

&lt;p&gt;&lt;img src="http://kamiiyu.github.io/images/2016-11-30/37.happy_hacking.png" title="" alt="37.happy_hacking"&gt;    &lt;/p&gt;</description>
      <author>kamiiyu</author>
      <pubDate>Fri, 02 Dec 2016 11:31:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/31763</link>
      <guid>https://ruby-china.org/topics/31763</guid>
    </item>
  </channel>
</rss>
