<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>pinewong</title>
    <link>https://ruby-china.org/pinewong</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>开源一个后台管理系统</title>
      <description>&lt;p&gt;抽了一个后台管理系统的简单版本，方便以后做小项目用。技术栈基于 Vue.js + Rails，功能亮点：RBAC 权限认证、前后端 I18n 多语言、开发部署容器化、CI/CD。开源出来，如果对你有帮助，请帮忙给个 Star，谢谢。&lt;/p&gt;

&lt;p&gt;地址：&lt;a href="https://github.com/songhuangcn/admin-template" rel="nofollow" target="_blank"&gt;https://github.com/songhuangcn/admin-template&lt;/a&gt;，具体介绍见项目 README&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Fri, 26 Jan 2024 13:00:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/43571</link>
      <guid>https://ruby-china.org/topics/43571</guid>
    </item>
    <item>
      <title>Docker 启动命令太长了，有没有约定好配置的封装工具？</title>
      <description>&lt;p&gt;Docker 有很多优点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不依赖操作系统，怎么升级系统都不怕&lt;/li&gt;
&lt;li&gt;由于第 1 点，方便运行一些操作系统已经不支持的软件版本&lt;/li&gt;
&lt;li&gt;因为隔离性，很方便同时运行一个软件的不同版本（开发不同项目时经常需要）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;但是 Docker 也有一些缺点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;配置太繁琐，新人需要了解一堆新概念：volume, publish, export&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不知道有没有用户有跟我一样的需求：就是想在电脑上用 Docker 跑起来一个服务比如 MySQL，但不需要处理太多容器的细节和配置，你跑的跟原生 MySQL 一样就行了，跑完就能用 3306 端口，日志和数据在固定的路径能找到。&lt;/p&gt;

&lt;p&gt;总结来说这个工具用户接口很像包管理工具，比如 Homebrew:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew services start mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是底层用的是 Docker，并帮忙我们配置好了启动命令。（注意我需要的是用户不用管配置的，docker compose 解决不了）&lt;/p&gt;

&lt;p&gt;我自己没找到这类工具，就搞了一个原型版本：&lt;a href="https://github.com/songhuangcn/dpm" rel="nofollow" target="_blank"&gt;https://github.com/songhuangcn/dpm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;大家有这类工具推荐吗？&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 27 Aug 2022 15:37:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/42613</link>
      <guid>https://ruby-china.org/topics/42613</guid>
    </item>
    <item>
      <title>内部系统的 API 响应和异常实践</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;Web 开发中前后端分离的一大阻碍是交互的数据结构复杂难用，离服务端直接渲染那样简单和灵活相差甚远。另外很多项目没考虑自身场景的滥用了 API 规范，比如内部的后台系统，经常被“规范”束缚强制统一响应结构，将 4xx 甚至 5xx 异常全部改成 2xx 响应，然后自己定义一套复杂的异常规范。&lt;/p&gt;

&lt;p&gt;对于内部后台系统，这种简单场景下的 API 响应和异常处理，其实设计可以考虑这些：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完全用 HTTP Status 规范，不另起 code。&lt;/li&gt;
&lt;li&gt;2xx, 4xx, 5xx 分开处理，基本所有 HTTP Client 都能很好的区分他们，不需要强制统一起来（后面会讲分开的优点）。&lt;/li&gt;
&lt;li&gt;正常响应情况，只要统一用一个规范的响应体即可，这里不难。对于任何异常情况，后端统一用异常处理，这样可以统一到基类一起处理响应，业务代码中也可以快速 return（少写很多 else）。另外可以在基类声明一个 ClientError 异常，出错时不清楚该用什么异常就都用这个。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后面我们基于这些，详细设计出一种方案，并给出实践例子。&lt;/p&gt;
&lt;h2 id="四种响应和异常"&gt;四种响应和异常&lt;/h2&gt;
&lt;p&gt;首先，我们需要为响应和异常分类，这里根据场景频率对异常和响应分类，对于内部系统，大部分情况是正确响应和只需要一个错误信息的异常响应，这里将他们分别归类到“2xx 响应”和“4xx 响应”。剩下情况是服务器异常和复杂业务异常响应，这里将它们分别归类到“5xx 响应”和“6xx+ 响应”。&lt;/p&gt;

&lt;p&gt;根据二八原则，常用的前两者应该尽可能做到使用简单，因此使用方式要区别开后面两者。不常见的“6xx+ 响应”为了保证功能强大允许使用起来复杂一点（反正用的少）。&lt;/p&gt;
&lt;h3 id="2xx 响应 - 正常情况"&gt;2xx 响应 - 正常情况&lt;/h3&gt;
&lt;p&gt;这个比较简单，也很多规范，只要约束住 HTTP Status 为 2xx 和结构体就行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- status: 2xx
- message: 附带消息，比如成功提示
- data：功能主数据
- meta：功能附带数据，比如分页筛选等信息
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4xx 响应 - 只需要一个错误信息的异常情况"&gt;4xx 响应 - 只需要一个错误信息的异常情况&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- status: 4xx，常用错误，后台系统一般直接抛除一个 ClientError 并传入一个错误信息即可。
- message: 附带消息，前端统一处理所有这类异常，消息提示参考：`API Client Error: ${message}`
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="5xx 响应 - 服务器异常情况"&gt;5xx 响应 - 服务器异常情况&lt;/h3&gt;
&lt;p&gt;完全不处理，这样能最大限度保留后端堆栈等异常。前端也统一处理所有这类异常，消息提示参考：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`API Server Error: ${statusText}`
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="6xx+ 响应 - 复杂业务异常情况"&gt;6xx+ 响应 - 复杂业务异常情况&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- status: 6xx+，这是不常用的错误，因此用法允许复杂一点，可以配合配置文件中使用，方便汇总所有业务错误，+的意思是还可以用 7xx, 8xx（HTTP Status 标准中，大于 600 都属于 Custom Status）。
- message：附带信息，一般是一个总结性的错误描述，也可以放到配置文件里。
- detail：指引前端如何进一步处理的主数据，要有这个，才需要用这类响应，否则应该使用简单的 4xx 响应。
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="实践例子"&gt;实践例子&lt;/h2&gt;
&lt;p&gt;下面代码以后端 Rails 5，前端 Vue.js 2 举例：&lt;/p&gt;

&lt;p&gt;1、后端路由定义和功能处理，功能为了简单，放到一个 controller 中，根据不同参数处理四种响应。这里为了简单，放到一起了，实际上这四种场景会遍布在你的项目的每个功能里：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;

&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/test_api/:status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'test_api#index'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controller/api/test_api_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::TestApiController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'2xx'&lt;/span&gt;
      &lt;span class="n"&gt;render_data&lt;/span&gt; &lt;span class="s1"&gt;'我是正常响应数据'&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'4xx'&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ClientError&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="s1"&gt;'我是一个 4xx 错误'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 这里常用异常处理很简单，使用起来跟后端渲染中的设置 flash 错误差不多简单了&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'5xx'&lt;/span&gt;
      &lt;span class="n"&gt;undefined_method&lt;/span&gt; &lt;span class="c1"&gt;# 这是一个没有定义的方法或变量，这里目的是手动制造一个 5xx 异常&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'6xx'&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;CustomError&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;:order_create_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'我是业务错误 detail 信息'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'没找到该 status 处理方式'&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;2、后端基类定义，这个是后端最重要的地方，包括三种响应的统一处理（5xx 应用不处理）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controller/api/application_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:detail&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="n"&gt;error&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;custom_errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 4xx 响应&lt;/span&gt;
  &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;ClientError&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;exception&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 6xx+ 响应&lt;/span&gt;
  &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;CustomError&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;exception&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;detail: &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 2xx 响应&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;meta: &lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;meta: &lt;/span&gt;&lt;span class="n"&gt;meta&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;3、后端 CustomError 配置，汇总在一起使得错误信息更清晰：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/config_custom_errors.rb&lt;/span&gt;

&lt;span class="n"&gt;file_dir&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;root&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="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'custom_errors.yml'&lt;/span&gt;&lt;span class="p"&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;custom_errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/custom_errors.yml&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_create_failed&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;订单创建失败&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;601&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_cancel_failed&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;订单取消失败&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、前端路由定义和功能处理：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/routes/index.js&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/test_api/:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/pages/test_api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight vue"&gt;&lt;code&gt;// pages/test_api/index.vue

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"$flash"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;beforeMount &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;mounted &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;request&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/test_api/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// 只有功能有 6xx+ 响应时才需要 catch，普通功能不必写这个&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`我是一个业务错误，&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;，&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 做一些 600 异常处理的事情，可以使用后端传过来的 detail&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;601&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 做一些 601 异常处理的事情，可以使用后端传过来的 detail&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、前端请求统一处理处，这个是前端最重要的地方：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// utils/request.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// 请求发出失败处理，比如无网络&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;
    &lt;span class="c1"&gt;// 这个是一个永远 pending 的 Promise，可以阻止 Promise 持续传播到 then 中&lt;/span&gt;
    &lt;span class="c1"&gt;// https://juejin.cn/post/6935404767778701325&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 无响应处理，比如 timeout&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`API Noresponse Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&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="c1"&gt;// 返回一个 pending 的 Promise，防止执行功能的 then 方法&lt;/span&gt;
    &lt;span class="c1"&gt;// 4xx 响应处理&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;499&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`API Client Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;  &lt;span class="c1"&gt;// 当 4xx 错误是由 Web 服务器或者应用服务器触发的，此时没有 message 值，这里要特殊处理一下&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&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="c1"&gt;// 5xx 响应处理&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;599&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;$flash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`API Server Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&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="c1"&gt;// 6xx+ 响应处理&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 不处理，直接抛异常给业务层处理&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 &lt;code&gt;$flash&lt;/code&gt; 只是一个简略的全局变量（这个处理不是重点，因此没有扩展），实际应用中可以实现具体的响应处理。&lt;/p&gt;

&lt;p&gt;这样就大功告成了。&lt;/p&gt;
&lt;h2 id="其他"&gt;其他&lt;/h2&gt;
&lt;p&gt;以上是一个思路，具体应用到项目中，可以在添加很多规范约束上的东西，比如考虑同伴如果不使用规范怎么拒绝（这个其实可以参照 Rails 对重复设置 response 或者没有设置 response 的处理）。&lt;/p&gt;

&lt;p&gt;API 响应和异常处理属于很常见的技术问题了，以上是我的一些浅显看法，欢迎大家发表任何想法，一起讨论进步。&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 07 Aug 2021 14:16:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/41558</link>
      <guid>https://ruby-china.org/topics/41558</guid>
    </item>
    <item>
      <title>后台系统重构 - 菜单同步</title>
      <description>&lt;h2 id="需求"&gt;需求&lt;/h2&gt;
&lt;p&gt;公司决定要使用前后端分离方式，重构一个维护了十余年的后台系统（原来是后端渲染方式）。由于持续过程较长，需要新旧系统并存一段时间。这个并存希望对使用者透明，因此我们把新系统界面结构弄的跟旧系统很相似，且我们要实现两个系统菜单完全一致。&lt;/p&gt;

&lt;p&gt;要做到完全一致，首先需要将旧系统菜单抽离出来，改成可配置形式。其次因为新系统是前端渲染形式，所以新系统菜单需要通过 API 获取。接下来详细介绍具体实现的要点，实现代码以框架 Rails 5 和 Vue.js 2 举例，实际上其他框架大同小异。&lt;/p&gt;
&lt;h2 id="请求路由定义"&gt;请求路由定义&lt;/h2&gt;
&lt;p&gt;首先是路由定义。路由定义每个系统单独定义自己的功能，假如现在总共有三个功能，其中一个功能已经重构到了新系统内。&lt;/p&gt;

&lt;p&gt;1、旧系统（Rails）还剩下两个路由：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"function-A1"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"function_a1#index"&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"function-A2"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"function_a2#index"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、新系统（Vue.js）增加一个路由：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/configs/routes.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/function-B1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/pages/function_b1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="菜单改成可配置化"&gt;菜单改成可配置化&lt;/h2&gt;
&lt;p&gt;然后是菜单配置问题。配置形式采用 yaml 格式，使用最简单的树形结构，配置内容尽可能的少。&lt;/p&gt;

&lt;p&gt;1、配置菜单：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/menus.yml&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;菜单A&lt;/span&gt;
  &lt;span class="na"&gt;icon_old&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;icon-a&lt;/span&gt;
  &lt;span class="na"&gt;icon_new&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdi-a&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;菜单A1&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/function-A1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;菜单A2&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/function-A2&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;菜单B&lt;/span&gt;
  &lt;span class="na"&gt;icon_old&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;icon-b&lt;/span&gt;
  &lt;span class="na"&gt;icon_new&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdi-b&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;菜单B1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/frontend/function-B1&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里假定 /frontend 开头的连接，会使用新系统（Vue.js）进行服务（这里需要配置代理和 Nginx，此文先不展开），其他情况使用旧系统（Rails）进行服务。&lt;/p&gt;

&lt;p&gt;2、加载菜单配置文件：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/config_menus.rb&lt;/span&gt;

&lt;span class="n"&gt;file_dir&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;root&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="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'menus.yml'&lt;/span&gt;&lt;span class="p"&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;menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 之后可在程序中这样获取：Rails.configuration.menus&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（进阶一）其中我们可能要经常要在菜单配置中使用 Rails Routes Helper 方法，可以改用如下加载方式：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/config_menus.rb&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;after_initialize&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;application&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# 开发环境此时路由数据依然为空，需要主动 reload&lt;/span&gt;
  &lt;span class="c1"&gt;# https://stackoverflow.com/a/8707687&lt;/span&gt;
  &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload_routes!&lt;/span&gt;

  &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&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="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'menus.yml'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="c1"&gt;# yaml 配置中，可以用 &amp;lt;%= xxx_path %&amp;gt; 方法&lt;/span&gt;
  &lt;span class="n"&gt;bind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ERB&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;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（进阶二）树形结构不好通过子节点取父节点，这里通过程序给菜单自动添加一个 tree_nodes 属性，使得后续用起来更方便：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/config_menus.rb&lt;/span&gt;

&lt;span class="n"&gt;file_dir&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;root&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="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'menus.yml'&lt;/span&gt;&lt;span class="p"&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;menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 菜单A的 tree_nodes 为: [1], 菜单A1: [1, 1], 菜单A2: [1, 2], 菜单B: [2]&lt;/span&gt;
&lt;span class="n"&gt;add_tree_nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent_tree_nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&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;menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;tree_nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent_tree_nodes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tree_nodes"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tree_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;add_tree_nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tree_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;add_tree_nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&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;menus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="旧系统菜单渲染"&gt;旧系统菜单渲染&lt;/h2&gt;
&lt;p&gt;旧系统是后端渲染方式，可以直接取到配置并渲染。渲染采用递归形式渲染，下面是一些核心方法。&lt;/p&gt;

&lt;p&gt;1、在布局里渲染菜单：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/views/layouts/application.html.erb&lt;/span&gt;

&lt;span class="c1"&gt;# 菜单栏&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= render_menus(Rails.configuration.menus) %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、菜单渲染核心方法（UI 框架为 Bootstrap 2）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/helpers/menus_helper.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MenuHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_menus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;doms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;menu&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&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;HTML&lt;/span&gt;&lt;span class="sh"&gt;
          # 这里假设 open 样式会让父菜单打开折叠
          &amp;lt;li class="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="s1"&gt;'open'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;menu_active?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;
            &amp;lt;a href="#" class="dropdown-toggle"&amp;gt;
              &amp;lt;i class="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'icon_old'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'icon-double-angle-right'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;&amp;lt;/i&amp;gt;
              &amp;lt;span class="menu-text"&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/span&amp;gt;
              &amp;lt;b class="arrow icon-angle-down"&amp;gt;&amp;lt;/b&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;ul class="submenu"&amp;gt;
              &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;render_menus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
            &amp;lt;/ul&amp;gt;
          &amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;        HTML&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="sh"&gt;
          # 这里假设 active 样式会让子菜单高亮
          &amp;lt;li class="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;menu_active?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;
            &amp;lt;a href="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'link'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;
              &amp;lt;i class="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'icon_old'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'icon-double-angle-right'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&amp;gt;&amp;lt;/i&amp;gt;
              &amp;lt;span class="menu-text"&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;        HTML&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;doms&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="nf"&gt;html_safe&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

  &lt;span class="c1"&gt;# 这里用到了上面菜单配置进阶二中的 tree_nodes 属性，如果不用，也可以用其他方式解决&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;menu_active?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_active_menu&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;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tree_nodes'&lt;/span&gt;&lt;span class="p"&gt;)&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;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'tree_nodes'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 深度遍历，获取第一个命中的菜单（命中算法这里简单使用当前 url 的 path，实际情况可能更复杂，这里不展开讨论）&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_active_menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@current_active_menu&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;menus&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;menus&lt;/span&gt;
      &lt;span class="n"&gt;menus&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;menu&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="n"&gt;active_menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_active_menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'children'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;active_menu&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;active_menu&lt;/span&gt;
        &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'link'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;menu&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="kp"&gt;nil&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;h2 id="新系统菜单渲染"&gt;新系统菜单渲染&lt;/h2&gt;
&lt;p&gt;新系统是前端渲染形式，除了获取菜单数据方式需要通过 API 形式，其他跟上面逻辑大同小异，大部分就是 Ruby 代码改 JS 代码。&lt;/p&gt;

&lt;p&gt;1、首先定义后端菜单获取的 API：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/api/menus_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MenusController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;menus: &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;menus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、然后是前端布局中渲染菜单：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;layouts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Menus&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Menus&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/Menus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Menus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;mounted &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/menus.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt;
        &lt;span class="c1"&gt;// 根据当前访问 url，设置匹配到的菜单，后续高亮这个菜单要用&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCurrentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentActiveMenu &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCurrentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeMenu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeMenu&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、最后是完成核心菜单组件（UI 框架为 Vuetify 2）：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Menus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu in menus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.children&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isSubmenu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isMenuActive(menu)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;activator&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!isSubmenu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.icon_new || 'mdi-notification-clear-all'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-item-icon&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-item-content&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Menus&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;menus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.children&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;depth + 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-group&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{ 'v-list-item--active': isMenuActive(menu) }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!isSubmenu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.icon_new || 'mdi-notification-clear-all'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-item-icon&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-item-content&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list-item&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/v-list&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Menus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 采用递归组件形式，需要配置组件名称&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;menus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&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="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;isMenuActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActiveMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tree_nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tree_nodes&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;isSubmenu &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&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="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="其他"&gt;其他&lt;/h2&gt;
&lt;p&gt;以上是最小核心解决方式，后面要在实际项目中用起来，还要处理菜单是否可见、菜单是否新窗口打开、菜单是否有权限等一系列问题，大部分可以在配置文件中增加配置项解决。&lt;/p&gt;

&lt;p&gt;其实将系统菜单配置到 menus.yml 中才是最花时间的，手动整理了 1000 多行 -_-&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Wed, 04 Aug 2021 08:13:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/41535</link>
      <guid>https://ruby-china.org/topics/41535</guid>
    </item>
    <item>
      <title>Codable - 更好的使用 code 字段</title>
      <description>&lt;p&gt;写了一个跟随 Rails 大版本的 Gem，代码很简单，并且已经在线上以 Concern 形式稳定跑了一年了（意思就是很安全，放心用）。&lt;/p&gt;

&lt;p&gt;另外试玩了一下 &lt;a href="https://github.com/features/actions" rel="nofollow" target="_blank" title=""&gt;GitHub Actions&lt;/a&gt;。有个疑问，这个 matrix 配置看文档只能放到 jobs.job_id 底下？我想要多个 jobs 复用，找不到方案，然后就只能 rubocop 和 test 两个 job 都复制一遍同样的 matrix 了。&lt;/p&gt;

&lt;p&gt;项目地址：&lt;a href="https://github.com/hdgcs/codable" rel="nofollow" target="_blank"&gt;https://github.com/hdgcs/codable&lt;/a&gt;&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Tue, 04 Aug 2020 19:53:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/40229</link>
      <guid>https://ruby-china.org/topics/40229</guid>
    </item>
    <item>
      <title>ActiveNotifier 帮助你的应用更简单的发送钉钉消息</title>
      <description>&lt;p&gt;&lt;strong&gt;警告：项目已更换，请使用 &lt;a href="https://github.com/pinewong/dingtalk-client" rel="nofollow" target="_blank" title=""&gt;Dingtalk Client&lt;/a&gt; 代替&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;以下是旧帖内容...&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;通过 webhook 发送消息，例如 &lt;a href="https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.bGnfFb&amp;amp;treeId=257&amp;amp;articleId=105733&amp;amp;docType=1" rel="nofollow" target="_blank" title=""&gt;钉钉机器人&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="安装"&gt;安装&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;active_notifier
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="基本使用"&gt;基本使用&lt;/h3&gt;
&lt;p&gt;直接通过命令形式发送消息。必须参数：token（第三方 webhook token，默认第三方适配器是钉钉），message&lt;/p&gt;

&lt;p&gt;在 IRB 环境中执行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s2"&gt;"your-webhook-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"your-message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次都指定 token 太麻烦，另外字符串的消息形式也不好用，如果能像 Rails 的模版渲染一样就好了。这些我们都能实现。&lt;/p&gt;

&lt;p&gt;在任何你想配置 ActiveNotifier 的文件中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;my_channel: &lt;/span&gt;&lt;span class="s2"&gt;"xxx"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;template_home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SOME_DIR&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改上述对应频道 &lt;code&gt;my_channel&lt;/code&gt; 的模板：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"## #{data[:title]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; #{data[:body]}}"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; SOME_DIR/my_channel.markdown.erb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以改用下面的方法发送消息（其中 data 里的数据会被传到模板中进行预编译）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token_channel: :my_channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;template: :my_channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Message Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Message Body"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外当我们 &lt;code&gt;token_channel&lt;/code&gt; 和 &lt;code&gt;template&lt;/code&gt; 名字一致时，我们可以使用 &lt;code&gt;ActiveNotifier::exec&lt;/code&gt; 的可选参数 &lt;code&gt;channel&lt;/code&gt; 简化：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Message Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Message Body"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="Rails 应用"&gt;Rails 应用&lt;/h4&gt;
&lt;p&gt;对于 Rails 应用，这里还有一些生成器，可以简化上面一些复制粘贴的工作：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails generate active_notifier:install
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="其他知识"&gt;其他知识&lt;/h3&gt;&lt;h4 id="关键方法"&gt;关键方法&lt;/h4&gt;
&lt;p&gt;通过上述使用，我们可以知道 ActiveNotifer 功能主要就围绕一个 &lt;code&gt;::exec&lt;/code&gt; 方法，该方法还支持一些参数选项，使得更符合你的需求，具体可以看这里：&lt;a href="https://www.rubydoc.info/github/pinewong/active_notifier/master/ActiveNotifier%2FNotifiable%2FClassMethods:exec" rel="nofollow" target="_blank" title=""&gt;RDoc exec&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="动态判断消息类型"&gt;动态判断消息类型&lt;/h4&gt;
&lt;p&gt;消息类型选项 &lt;code&gt;type&lt;/code&gt; 一般来说我们不用指定，他会根据我们模板类型自动判断。但是如果你有两个以上的模板类型，这时候你可以通过这个选项手动指定：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Message Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Message Body"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;type: :text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外我们还有一个全局配置项 &lt;code&gt;priority_type&lt;/code&gt;，该选项可以指定优先的类型，假设对于一个消息模版有三种实现：&lt;code&gt;order.text.erb&lt;/code&gt;, &lt;code&gt;order.markdown.erb&lt;/code&gt;, &lt;code&gt;order.flow.erb&lt;/code&gt;，这时候 ActiveNotifier 会优先选择 &lt;code&gt;priority_type&lt;/code&gt; 配置值：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# default is :markdown&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;priority_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面命令发送的消息类型，会是 &lt;code&gt;text&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Message Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Message Body"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;# 如果是 type(同 priority_type) 类型，可以不显示指定&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="设置简短常量"&gt;设置简短常量&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveNotifer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:Notifier&lt;/span&gt;
&lt;span class="no"&gt;Notifer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&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;/code&gt;&lt;/pre&gt;&lt;h3 id="文档和帮助"&gt;文档和帮助&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pinewong/active_notifier/blob/master/spec/active_notifier_spec.rb" rel="nofollow" target="_blank" title=""&gt;更多例子&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rubydoc.info/github/pinewong/active_notifier" rel="nofollow" target="_blank" title=""&gt;RDoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.rubydoc.info/gems/active_notifier/0.4.2" rel="nofollow" target="_blank" title=""&gt;Gem RDoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="最后"&gt;最后&lt;/h3&gt;
&lt;p&gt;希望对大家有用。项目链接：&lt;a href="https://github.com/pinewong/active_notifier" rel="nofollow" target="_blank"&gt;https://github.com/pinewong/active_notifier&lt;/a&gt;&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Thu, 02 Aug 2018 09:14:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/37263</link>
      <guid>https://ruby-china.org/topics/37263</guid>
    </item>
    <item>
      <title>为 Rails 所有的异步请求添加加载动画</title>
      <description>&lt;p&gt;有一个需求，需要为 Rails 上所有的异步请求添加加载动画（其中 Rails: 5.1.4，jQuery: 3.3.1）。具体设计是发出请求后在页面展示正在请求的提示动画，请求结束时隐藏或清除该动画。下面是分情况的实现：&lt;/p&gt;
&lt;h2 id="禁用 rails-ujs 的情况"&gt;禁用 rails-ujs 的情况&lt;/h2&gt;
&lt;p&gt;如果没有使用 rails-ujs&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// application.js&lt;/span&gt;
&lt;span class="c1"&gt;// 确定项目中下面这行已经被移除&lt;/span&gt;
&lt;span class="c1"&gt;//= require rails-ujs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么实现只要添加 JS 事件就行了&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajaxSend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;// 加载异步请求动画&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajaxComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;// 移除异步请求动画&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;#loading&lt;/code&gt; 是布局层面的 html 加载动画模板，每个页面都需要加载，初始化时先隐藏起来。&lt;/p&gt;
&lt;h2 id="使用 rails-ujs 的情况（仅使用于非动态元素）"&gt;使用 rails-ujs 的情况（仅使用于非动态元素）&lt;/h2&gt;
&lt;p&gt;如果是按默认的 Rails 配置，项目中需要使用 rails-ujs 时，ujs 提供的异步请求（a 或 button 添加 remote: true 这种）绑定的事件不是原生事件，这时候需要绑定另外一种事件实现&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajax:send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;// 加载异步请求动画&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajax:complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;// 移除异步请求动画&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这么处理在一般的情况下没有问题。但是当绑定 send 事件的元素在后端处理中被移除了的时候，后续的事件就无效了。举个例子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tests/index.html.erb&lt;/li&gt;
&lt;/ul&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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"我会异步删除自己"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;remote: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: &lt;/span&gt;&lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;tests/destroy.js.erb&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后点击测试中的链接之后，加载异步请求动画成功，但是请求结束后，动画不会移除。查询资料，说这种&lt;a href="https://github.com/rails/jquery-ujs/issues/223" rel="nofollow" target="_blank" title=""&gt;异步请求未完成就改变元素的操作不是好实践&lt;/a&gt;。但是这种需求在 Rails 还是很普遍啊，例如大家用的 kaminari 分页库的异步加载，就会重新渲染整个列表，也就是触发换页操作的按钮也被移除过了，所以也会出现上述的问题。于是这个问题还是要解决的。&lt;/p&gt;
&lt;h2 id="使用 rails-ujs 的情况（完整）"&gt;使用 rails-ujs 的情况（完整）&lt;/h2&gt;
&lt;p&gt;具体要解决 Rails 异步请求加载动画的问题，需要使用到 ajaxSend 事件的 &lt;a href="http://devdocs.io/jquery/jquery.ajax#jqXHR" rel="nofollow" target="_blank" title=""&gt;jqXHR&lt;/a&gt; 参数，用这个参数值来绑定后续事件，而不再使用 ajaxComplete 事件。&lt;/p&gt;

&lt;p&gt;首先，因为当前的 Rails 版本自带的 rails-ujs 不再依赖 jQuery，因此 ajax:send 事件的参数中，不是 jqXHR，所以首先需要替换回原来的 jquery_ujs 库&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemfile&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'jquery-rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;application.js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//= require rails-ujs 替换为&lt;/span&gt;
&lt;span class="c1"&gt;//= require jquery_ujs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后用以下的方式绑定事件&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ajax:send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jqXHR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 加载异步请求动画&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;jqXHR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;always&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 移除异步请求动画&lt;/span&gt;
      &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 &lt;a href="/n5ken" class="user-mention" title="@n5ken"&gt;&lt;i&gt;@&lt;/i&gt;n5ken&lt;/a&gt; 提示，还有一个多请求干扰的问题。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如：异步删除两个资源，A 的删除动作触发了动画显示，随后 A 的任务完成后动画消失，但 B 如果还在进行中的话，动画实际上已经消失了。 &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此还要添加一个全局计数器&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ajaxSendCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ajax:send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jqXHR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ajaxSendCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// 加载异步请求动画&lt;/span&gt;
      &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;ajaxSendCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;jqXHR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;always&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ajaxSendCount&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 清除异步请求动画&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在可以正常的加载异步请求的动画了。&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Wed, 31 Jan 2018 22:02:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/34983</link>
      <guid>https://ruby-china.org/topics/34983</guid>
    </item>
    <item>
      <title>一些 Ruby/Rails 小技巧续</title>
      <description>&lt;p&gt;第一篇地址在这：&lt;a href="https://ruby-china.org/topics/34061" title=""&gt;https://ruby-china.org/topics/34061&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们接着记录开发应用中遇到的一些小问题。&lt;/p&gt;
&lt;h2 id="在 sql 中拼接字符串形式的时间需要注意时区问题"&gt;在 sql 中拼接字符串形式的时间需要注意时区问题&lt;/h2&gt;
&lt;p&gt;我们经常会使用这样的查询：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at &amp;gt; %"&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="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的应用设置了时区，这里 Rails 会帮我们把时间转化为 UTC 时间插入。但如果我们传入的是字符串而不是 Rails 的 Time/Date/DateTime 格式，情况又是怎么样呢？能不能直接 &lt;code&gt;2.days.ago.to_s&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;能不能直接尝试后就知道了。经过尝试，一个时间对象转换成字符串的后会在当前时区时间加上 &lt;code&gt;+0800&lt;/code&gt; 这样的时区后缀。但是问题就在有些数据库并不能解析这个后缀。在我们用的 PostgreSQL 中，这样的后缀可以说是被直接忽略掉的。&lt;/p&gt;

&lt;p&gt;因此通过上面的分析，该问题的答案就变成了当 sql 需要传入字符串时间时，需要转换成 UTC 时间字符串。具体可以这么处理：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 原始时间是对象&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;

&lt;span class="c1"&gt;# 原始时间是字符串&lt;/span&gt;
&lt;span class="s2"&gt;"2017/11/24 12:00:00"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_time_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;  
&lt;span class="c1"&gt;# 或者&lt;/span&gt;
&lt;span class="s2"&gt;"2017/11/24 12:00:00"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# 这里不能使用 to_datetime，因为 to_time 比 to_datetime 多支持一个时区参数，并且该参数默认值为 :local , 符合我们需求&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果有其他更直观的方法，请留言告诉我。&lt;/p&gt;
&lt;h2 id="理解 map/collect/inject/reduce/each_with_object"&gt;理解 map/collect/inject/reduce/each_with_object&lt;/h2&gt;
&lt;p&gt;标题是不合理的，因为他们并不全部相关。但是这些方法经常困扰着一些新人，所以放一起对比解释一下。&lt;/p&gt;
&lt;h3 id="map/collect"&gt;map/collect&lt;/h3&gt;
&lt;p&gt;他们只是别名，C 源码中用的是 collect 方法统一定义。使用的话记住两点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;最终返回一个数组，每次迭代的值为该数组的元素；&lt;/li&gt;
&lt;li&gt;操作单元素枚举对象（Array）时，块中应该指定一个参数；操作双元素枚举对象（Hash）时，块中应该指定 1..2 个参数，当指定 1 个参数时，其实是将 Hash 先转化成 Array 在进行操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="inject/reduce"&gt;inject/reduce&lt;/h3&gt;
&lt;p&gt;他们也是别名，源码中用的是 inject 方法统一定义。inject 的作用是迭代更新每次结果。需要注意的是当没有初始值参数时，实际上迭代次数为 size-1，操作枚举对象的第一个元素被指定为初始值，没有进行迭代。以下是代码说明：&lt;/p&gt;
&lt;h4 id="当指定初始值参数时"&gt;当指定初始值参数时&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&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="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt;16&lt;/span&gt;

&lt;span class="c1"&gt;# 等价于&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="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="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# 这里的 result + item 表达式与 map 中迭代的表达式一致&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="当没有指定初始值参数时"&gt;当没有指定初始值参数时&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&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="nf"&gt;inject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 6&lt;/span&gt;

&lt;span class="c1"&gt;# 等价于&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# 此时只有 2, 3 元素被迭代&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="each_with_object"&gt;each_with_object&lt;/h3&gt;
&lt;p&gt;这个方法其实很好理解，就是迭代前，引入一个变量并最终返回那个变量（至于这个变量你有没有用到，方法并不关心）。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&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="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; {1=&amp;gt;0, 2=&amp;gt;0, 3=&amp;gt;0}&lt;/span&gt;
&lt;span class="c1"&gt;# 等价于&lt;/span&gt;
&lt;span class="n"&gt;object&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="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="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; {1=&amp;gt;0, 2=&amp;gt;0, 3=&amp;gt;0}&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="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; {}&lt;/span&gt;
&lt;span class="c1"&gt;# 等价于&lt;/span&gt;
&lt;span class="n"&gt;object&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="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="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; {}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;这三个方法都是为 ruby 枚举对象迭代多做了一些额外的工作，其中 map/collect 是在枚举对象迭代时，收集元素，最后生成一个数组；each_with_object 是帮助我们为枚举对象的迭代引入一个结果变量（作为参数引入）；最后 inject/reduce 做的事情是引入结果变量后，还修改了迭代体，每次迭代体最后返回的结果存给了该变量。&lt;/p&gt;
&lt;h2 id="什么时候该用 render/redirect_to .. and return"&gt;什么时候该用 render/redirect_to .. and return&lt;/h2&gt;
&lt;p&gt;官方 Guides 中提到了 action 中重复渲染/重定向的问题，并建议了使用 &lt;code&gt;render/redirect_to .. and return&lt;/code&gt; 解决。但是有些人刚开始可能会不明白具体哪里需要（比如我），这里先将语句展开：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;render/redirect_to .. and return&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;code&gt;(render/redirect_to ..) &amp;amp;&amp;amp; return&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;code&gt;if (render/redirect_to ..); return; end&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;很好理解，他是希望在一个方法里提前结束，因为 &lt;code&gt;render/redirect_to&lt;/code&gt; 并不等于 &lt;code&gt;return&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Rails 还有 &lt;code&gt;before_action&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;HomeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_url&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# 不需要 and return&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里不需要做其他处理的原因是因为 Rails 会判断其他域（方法、块）中是否已经渲染或重定向。&lt;/p&gt;

&lt;p&gt;总结：Action 和回调方法中有多个显示的渲染或重定向，除了最后一个都需要 &lt;code&gt;and return&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="在 model 层而不是数据库层设置默认值"&gt;在 model 层而不是数据库层设置默认值&lt;/h2&gt;
&lt;p&gt;原因：model 层设置默认值更灵活，且适用场景更多，例如设置动态值等。&lt;/p&gt;

&lt;p&gt;如何设置：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="no"&gt;User&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="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;after_initialize&lt;/span&gt; &lt;span class="ss"&gt;:set_defaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :new_record?&lt;/span&gt; &lt;span class="c1"&gt;# 仅当初始化的是新纪录的时候设置默认值&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_defaults&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute&lt;/span&gt;  &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'some value'&lt;/span&gt; &lt;span class="c1"&gt;# bool 以外的字段设置&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bool_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bool_field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="c1"&gt;# bool 字段设置&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="常量的自动加载"&gt;常量的自动加载&lt;/h2&gt;&lt;h3 id="开发环境中，Rails 应用的自动加载会与 Ruby 的 require 冲突"&gt;开发环境中，Rails 应用的自动加载会与 Ruby 的 require 冲突&lt;/h3&gt;
&lt;p&gt;原因：Ruby &lt;code&gt;require&lt;/code&gt; 维护着 &lt;code&gt;$LOADED_FEATURES&lt;/code&gt; 全局变量，Rails 的 &lt;code&gt;load&lt;/code&gt; 加载不会更新 &lt;code&gt;$LOADED_FEATURES&lt;/code&gt;，所以 Rails 自动加载后，&lt;code&gt;require&lt;/code&gt; 还是会再执行一遍。另外如果 &lt;code&gt;require&lt;/code&gt; 先运行了，Rails 没有完成该常量的自动加载，也就不会把该常量标记为自动加载的常量，因此开发环境设置的常量重新加载会失效。&lt;/p&gt;

&lt;p&gt;做法：Rails 中，应保持默认自动加载的目录文件规则。即使出现需要手动引入常量，也应该使用 Rails 的 &lt;code&gt;require_dependency&lt;/code&gt; 解决。&lt;/p&gt;
&lt;h3 id="自动模块"&gt;自动模块&lt;/h3&gt;
&lt;p&gt;在一些项目的 lib 文件夹中，经常会出现 api.rb, api/product.rb, api/user.rb 之类的结构，其中 api.rb 只有一个空模块定义。但其实 Rails 已经为我们处理了这类需求。假设 Rails 自动加载目录中含有 api 目录，且 Rails 自动加载中为找到 Api 常量定义，则 Rails 会为 Api 常量定义好空模块。即上述出现的结果中，api.rb 文件其实是可省略的，因为 Rails 帮我们这么干了。&lt;/p&gt;
&lt;h2 id="不要依靠赋值方法的返回值"&gt;不要依靠赋值方法的返回值&lt;/h2&gt;
&lt;p&gt;这个主要是因为 Ruby 中有一个约定：赋值语句/方法总是返回表达式右值。我们看一个案例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; ""&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; nil&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Pine"&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; "Pine"&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; "Pine"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 User#name=() 的 return 语句会被忽略，不会得到想要的效果。&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Fri, 24 Nov 2017 16:51:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/34620</link>
      <guid>https://ruby-china.org/topics/34620</guid>
    </item>
    <item>
      <title>一些 Ruby/Rails 小技巧</title>
      <description>&lt;p&gt;最近做项目的一些小记录，有些地方可能考虑的不对，发出来一起讨论，希望大家可以一起提高&lt;img title=":grimacing:" alt="😬" src="https://twemoji.ruby-china.com/2/svg/1f62c.svg" class="twemoji"&gt; &lt;/p&gt;

&lt;p&gt;现在假设我们在开发一个小商店应用（以下用 Store），并且领导说可以用 Rails 5。&lt;/p&gt;
&lt;h2 id="初始化应用"&gt;初始化应用&lt;/h2&gt;
&lt;p&gt;那第一步就是初始化依赖，做一些应用配置之类的，在这里记的小问题有：&lt;/p&gt;
&lt;h3 id="PostgreSQL 中，添加 host 设置的同时，也需要设置用户名和密码"&gt;PostgreSQL 中，添加 host 设置的同时，也需要设置用户名和密码&lt;/h3&gt;
&lt;p&gt;这里有 pg 登录途径的相关知识，因为有了 Unix Socket 方式，于是在保持默认安装的 pg(9.6) 配置时，应用的数据库配置文件只需要设置非常简单的几项就能正确连接数据库，但这里要注意的是，一旦在 &lt;code&gt;database.yml&lt;/code&gt; 中设置了 host（即使是 localhost），这时候你还要设置用户名和密码才能连接，原因是 pg 此时改用用户名密码的登录方式了。&lt;/p&gt;
&lt;h3 id="使用 dotenv 配置项目能方便项目的协作开发"&gt;使用 dotenv 配置项目能方便项目的协作开发&lt;/h3&gt;
&lt;p&gt;对比 figaro 的 &lt;code&gt;.sample&lt;/code&gt; 方案，dotenv 多了个 &lt;code&gt;.env&lt;/code&gt; 文件可以设置变量默认值，可以方便不常变化的变量设置。但有很多项目在使用 &lt;code&gt;.sample&lt;/code&gt; 方式配置项目，这里有点不太理解&lt;img title=":neutral_face:" alt="😐" src="https://twemoji.ruby-china.com/2/svg/1f610.svg" class="twemoji"&gt; （我搜到的有安全问题，但我总觉的可以绕过这个问题啊）。&lt;/p&gt;
&lt;h2 id="应用模型"&gt;应用模型&lt;/h2&gt;
&lt;p&gt;初始化完后，就来到对应用模型的设计环节了。&lt;/p&gt;
&lt;h3 id="persisted? (exists?) 才是判断是否创建成功最有效的方式"&gt;persisted? (&lt;del&gt;exists?&lt;/del&gt;) 才是判断是否创建成功最有效的方式&lt;/h3&gt;
&lt;p&gt;首先，我们要知道 &lt;code&gt;Model.create&lt;/code&gt; 方法无论成功与否都返回了一个对象，一般情况下，我们会想到用 &lt;code&gt;valid?&lt;/code&gt; 来判断：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"pine"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt;
  &lt;span class="c1"&gt;# 设想的成功情况&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# 失败情况&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;什么情况下不一定成功？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;One&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:twos&lt;/span&gt;
  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:create_twos_after_create&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_twos_after_create&lt;/span&gt;
    &lt;span class="c1"&gt;# Use bang method in callbacks, than it will rollback while create  two failed &lt;/span&gt;
    &lt;span class="n"&gt;twos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;    &lt;span class="c1"&gt;# This will fail because lack of the column `number`&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Two&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &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;code&gt;One.create&lt;/code&gt; 失败，但 &lt;code&gt;One.create.valid?&lt;/code&gt; 返回 &lt;code&gt;true&lt;/code&gt;，所以正确操作应该是 (经二楼朋友提示，改用更精简的 &lt;code&gt;persisted?&lt;/code&gt;)：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# if User.exists?(user.id)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted?&lt;/span&gt;
  &lt;span class="c1"&gt;# Success&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# Failed&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这有详细解释：&lt;a href="https://stackoverflow.com/a/46091806/7840077" rel="nofollow" target="_blank" title=""&gt;Ruby on Rails Active Record return value when create fails?&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="正确的终止回调"&gt;正确的终止回调&lt;/h3&gt;
&lt;p&gt;官方 Guides 中提到使用 &lt;code&gt;throw :abort&lt;/code&gt; 手动终止回调一个回调链，并且给出了下面的提示：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;当回调链停止后，Rails 会重新抛出除了 ActiveRecord::Rollback 和 ActiveRecord::RecordInvalid 之外的其他异常。这可能导致那些预期 save 和 update_attributes 等方法（通常返回 true 或 false）不会引发异常的代码出错。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但是经过测试，&lt;code&gt;throw :abort&lt;/code&gt; 只在 &lt;code&gt;before_&lt;/code&gt; 类型的回调中能其作用，&lt;code&gt;after_&lt;/code&gt; 回调里，使用 &lt;code&gt;throw :abort&lt;/code&gt; 会得到一个 &lt;code&gt;UncaughtThrowError&lt;/code&gt; 的异常，因此，&lt;code&gt;raise ActiveRecord::Rollback&lt;/code&gt; 应该是现在手动终止回调最佳的办法了。&lt;/p&gt;
&lt;h3 id="回调里的异常正确处理并不是那么简单"&gt;回调里的异常正确处理并不是那么简单&lt;/h3&gt;
&lt;p&gt;同样使用上面 &lt;code&gt;One&lt;/code&gt;，&lt;code&gt;Two&lt;/code&gt; 模型创建的例子，完整的异常的处理是怎么样的呢？大概应该是这样的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;One&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:twos&lt;/span&gt;
  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:create_twos_after_create&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_twos_after_create&lt;/span&gt;
    &lt;span class="c1"&gt;# Use bang method in callbacks, than it will rollback while create  two failed &lt;/span&gt;
    &lt;span class="n"&gt;twos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;    &lt;span class="c1"&gt;# This will fail because lack of the column `number`&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordInvalid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;  &lt;span class="c1"&gt;# This is exception of Two, but not One&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rollback&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Two&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &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;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controllers&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OneController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="err"&gt;＝&lt;/span&gt; &lt;span class="no"&gt;One&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;One&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&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;redirect_to&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- views --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  one.errors.full_messages.each do |message|
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  end
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就基本完成了，上述主动捕获的 &lt;code&gt;ActiveRecord::RecordInvalid&lt;/code&gt; 是其他模型的，上述 Guides 中提到的：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rails 会重新抛出除了 ActiveRecord::Rollback 和 ActiveRecord::RecordInvalid 之外的其他异常&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;里，虽然 &lt;code&gt;ActiveRecord::RecordInvalid&lt;/code&gt; 会被处理成回滚操作，但是 Rails 自身的处理会丢失了错误信息，因此，我们这里要捕获异常，并作记录错误信息操作，最后手动终止回调。&lt;/p&gt;

&lt;p&gt;另外，因为这是不属于本模型的错误，我们把它加到 &lt;code&gt;:base&lt;/code&gt; 键里。同理，如果回调中有其他可能需要终止执行的操作，也可以这样操作：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_twos_after_create&lt;/span&gt;
  &lt;span class="c1"&gt;# Use bang method in callbacks, than it will rollback while create  two failed &lt;/span&gt;
  &lt;span class="n"&gt;twos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;    &lt;span class="c1"&gt;# This will fail because lack of the column `number`&lt;/span&gt;
  &lt;span class="n"&gt;api_get!&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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordInvalid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ApiError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;  &lt;span class="c1"&gt;# When get exception after call API&lt;/span&gt;
  &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rollback&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="开发应用"&gt;开发应用&lt;/h2&gt;
&lt;p&gt;对于开发应用的过程，就有更多的小问题了：&lt;/p&gt;
&lt;h3 id="字符串间的操作，请使用插入代替拼接"&gt;字符串间的操作，请使用插入代替拼接&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Good&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# Bad&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;' - '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述两种方案里，后者当 object.name, object.number 出现 nil, 或其他非字符串类型值，你就会得到一个 &lt;code&gt;TypeError&lt;/code&gt; 的异常，而第一种会帮我们尝试转化字符串，对于写 JS 多，第二种会更常用，可能需要注意一下。&lt;/p&gt;
&lt;h3 id="正确的理解方法和变量"&gt;正确的理解方法和变量&lt;/h3&gt;
&lt;p&gt;这个属于 Ruby 语言问题，Ruby 方法省略括号，给了我们很大方便，似乎是促使我们将方法和变量一同看待。这样使得语法精炼很多，但理解不深时也会容易造成一些误解。&lt;/p&gt;

&lt;p&gt;列举一个案例，我现在需要通过一个传参，清空所有参数，并且由于对 params 对象了解不够，用了最简单的赋值清空：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controllers&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;  &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:deny_all&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里也是不正确的，当 &lt;code&gt;"true" == params[:deny_all]&lt;/code&gt; 条件不成立时，params（此时是变量）的值会被设成 nil。这里其实是 Ruby 的基础（不同于其他语言的几点地方），但有时业务一上来，可能就会没注意到这些了。具体问题是啥？我们继续进一步补全 Ruby 的语法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;  &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="ss"&gt;:deny_all&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; 
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样问题就暴露的很明显了，这是 params 由方法变成变量引起的，这里正确的解决办法就不贴了，大概就是应该是去查看 &lt;code&gt;params&lt;/code&gt; 文档，使用方法清空而非引入变量。&lt;/p&gt;
&lt;h3 id="利用 document DOM 对动态节点绑定事件"&gt;利用 document DOM 对动态节点绑定事件&lt;/h3&gt;
&lt;p&gt;当一个元素是用户 JS 触发插入的，我们该如何为该元素事先添加事件？&lt;/p&gt;

&lt;p&gt;现在 Store 中有一个订单页，用户可以通过手动添加商品，这里，我们要为每一个商品项添加监听数量变动的事件，商品项中数量节点的类为 &lt;code&gt;.product-count&lt;/code&gt;，我们应该如下操作：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Right&lt;/span&gt;
&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.product-count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Wrong&lt;/span&gt;
&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.product-count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于商品项元素是 JS 添加的，商品项中的 &lt;code&gt;.product-count&lt;/code&gt; 元素也即属于动态节点，而 jQuery 提供了事例的方案为动态节点绑定时间，具体请参照文档：&lt;a href="http://devdocs.io/jquery/on" rel="nofollow" target="_blank" title=""&gt;jQuery-On&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="为静态资源添加范围限制"&gt;为静态资源添加范围限制&lt;/h3&gt;
&lt;p&gt;Rails 默认会将所有的 css 和 js 分别打包成一个一个文件引入，为了提升程序健壮性和性能，我们要自己为专属资源添加范围限制，这里，最简单快捷的可以使用资源名化作范围。现在我要为管理模块的订单流水记录 (admin/flow_records) 的样式和脚本添加限制：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- layouts/application.html.erb --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;controller_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[\/\_]/&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="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// javascripts/admin_flow_records.js&lt;/span&gt;
&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-flow-records&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* stylesheets/admin_flow_records.scss */&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.admin-flow-records&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;...&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="待续..."&gt;待续...&lt;/h3&gt;</description>
      <author>pinewong</author>
      <pubDate>Thu, 07 Sep 2017 22:41:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/34061</link>
      <guid>https://ruby-china.org/topics/34061</guid>
    </item>
    <item>
      <title>Swagger UI 不解析生成的 JSON 文件</title>
      <description>&lt;p&gt;使用 &lt;a href="https://github.com/richhollis/swagger-docs" rel="nofollow" target="_blank" title=""&gt;swagger-docs&lt;/a&gt; 这个 gem，当执行完 &lt;code&gt;rake swagger:docs&lt;/code&gt; 之后，api-docs.json 显示如下：&lt;/p&gt;

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

&lt;p&gt;然后手动引入 swagger-ui 的 dist 文件夹到 public 目录，修改文件夹名字为 swagger-ui, 访问 /public/swagger-ui/index.html 文件，输入生成的 api-docs.json 路径，却显示不了 api 文档（没有 controller 的写的 api 文档）&lt;/p&gt;

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

&lt;p&gt;请问大家，有哪步做错了的吗？&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Thu, 12 Jan 2017 11:58:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/32110</link>
      <guid>https://ruby-china.org/topics/32110</guid>
    </item>
    <item>
      <title>macOS 日常使用技巧小记, 欢迎大家一起分享</title>
      <description>&lt;p&gt;以下这些对用过的人都是很简单的东西，但调查了一些朋友，也有大多数人不了解。工欲善其事，必先利其器，能利用好工具也是一件很重要的事。这里先小记一下自己的一些使用，最后希望大家能不吝指教，一起分享。&lt;/p&gt;
&lt;h2 id="文本替换"&gt;文本替换&lt;/h2&gt;
&lt;p&gt;用了会上瘾，另外第三方输入法也有自己类似的功能&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/138b0c0866c6160c295710833711d21b.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2016/b00cec9f18f46be2ffb614a7e70176e5.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="分屏"&gt;分屏&lt;/h2&gt;
&lt;p&gt;长按窗口左上角全屏按钮触发&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/95572d647091fe4889f1a2be3b9ff894.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2016/000af87e451598029c530607d71e99cd.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="Spotlight 不只是搜索"&gt;Spotlight 不只是搜索&lt;/h2&gt;
&lt;p&gt;他是词典，是计算器&lt;/p&gt;

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

&lt;p&gt;还能转化单位，能打开程序 (其中有了打开程序这个，基本就没有 Lanchpad 什么事了)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/0a1b95b96ddede35427bf2a46ab3c01b.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2016/26cbcce90c68b0aaf0f7346c80c19672.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="更靠近键盘的触摸板"&gt;更靠近键盘的触摸板&lt;/h2&gt;
&lt;p&gt;从 Ubuntu/黑苹果转过来, 第一件事就是原来鼠标漂移卡顿了，不好用了，这逼着没买苹果鼠标的你尝试用触摸板，但是用了之后，也能让你欲罢不能，特别是习惯使用 Vim 和 Emacs 键盘操控一切，不喜欢脱离键盘太远的人。&lt;/p&gt;

&lt;p&gt;例如经常使用 Control-Left/Right 切换 Space 的人，你现在能用四指轻易滑动实现，相似的还有 Mission Control 等&lt;/p&gt;

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

&lt;p&gt;另外还有三指预览，以及配合 Safari 的前进后退翻页特效&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/31449418f52c4a8411f04e13ecaaeaaf.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2016/be5f264f9fb85e2c9271025251e9fdad.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="画中画功能"&gt;画中画功能&lt;/h2&gt;
&lt;p&gt;看着视频，又想全屏敲代码已经不再是难事，详情：&lt;a href="https://support.apple.com/zh-cn/HT206997" rel="nofollow" target="_blank" title=""&gt;画中画 - Apple Support&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/dc4ad8ca7589083ab9328eda932e14b8.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="自带截屏"&gt;自带截屏&lt;/h2&gt;&lt;h3 id="选定区域截屏"&gt;选定区域截屏&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Command-Shift-4&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="智能区域截屏(窗口)"&gt;智能区域截屏 (窗口)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Command-Shift-4&lt;/code&gt;, 指针变化后，按键盘 &lt;code&gt;Space&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="全屏截屏"&gt;全屏截屏&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Command-Shift-3&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="截屏到粘贴板"&gt;截屏到粘贴板&lt;/h3&gt;
&lt;p&gt;上述命令都是直接在桌面生成图片，如果想截图到粘贴板一次性使用，请在快捷键中增加 &lt;code&gt;Control&lt;/code&gt; 键
例如截取全屏到粘贴板：&lt;code&gt;Command-Shift-Control-3&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="一些其他应该知道的"&gt;一些其他应该知道的&lt;/h2&gt;&lt;h3 id="三指拖动"&gt;三指拖动&lt;/h3&gt;
&lt;p&gt;当初用 Win 时被同学 Air 的三指拖动震惊了一下，然而自己使用，居然没有了这功能!! 搜索，原来是苹果为了强调 Force Touch 体验，默认关闭了该功能，但这阻止不了我，第一时间开启，另外配合开启点按功能后，我发现触摸板基本没有按下去的必要了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/d89cc8ac22d464bf079482fefdf487c7.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="主机名设置"&gt;主机名设置&lt;/h3&gt;
&lt;p&gt;然后使用命令行，就会经常看到主机名，赶紧改一个 (我的理由是默认的太长，占位置...)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/351f15fe74ab3a2d46a0d9f043dff7c2.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="恢复模式"&gt;恢复模式&lt;/h3&gt;
&lt;p&gt;另外，对于第一次使用 Mac, 进入恢复模式 (装系统，管理磁盘):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Command(⌘)-R&lt;/code&gt;, 如果无效，可使用 &lt;code&gt;Option-Command(⌘)-R&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="官方硬件检查:"&gt;官方硬件检查：&lt;/h3&gt;
&lt;p&gt;检测硬件是否原配？检测硬件是否损伤？详情：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.apple.com/zh-cn/HT202731" rel="nofollow" target="_blank" title=""&gt;Apple Diagnostics&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="键位转换"&gt;键位转换&lt;/h3&gt;
&lt;p&gt;从之前 Win 转化过来，键盘没有了 Home, PageUp, PageDown, End, Delete(macOS Delete 实际上是常见的 Backspace), 于是，我们需要知道这些：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Home -&amp;gt; Fn-Left(编辑状态) / Command(⌘)-Left(非编辑状态)
End -&amp;gt; Fn-Right(编辑状态) / Command(⌘)-Right(非编辑状态)
PageUp -&amp;gt; Fn-Up
PageDown -&amp;gt; Fn-Down
Delete -&amp;gt; Fn-Delete
Insert -&amp;gt; Fn-Enter(这个是网上的, 我在 word 上使用没有成功)
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 10 Dec 2016 17:11:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/31870</link>
      <guid>https://ruby-china.org/topics/31870</guid>
    </item>
    <item>
      <title>rails test 报错, 关于 sqlite3</title>
      <description>&lt;p&gt;执行 rails test 报错：&lt;/p&gt;

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

&lt;p&gt;具体搜到了，这个链接 &lt;a href="https://bugs.ruby-lang.org/attachments/6178/log.txt" rel="nofollow" target="_blank" title=""&gt;https://bugs.ruby-lang.org/attachments/6178/log.txt&lt;/a&gt;, 好像是一个东西，但没有解答。&lt;/p&gt;

&lt;p&gt;最后，求大家帮忙&lt;img title=":joy:" alt="😂" src="https://twemoji.ruby-china.com/2/svg/1f602.svg" class="twemoji"&gt; &lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Mon, 05 Dec 2016 22:12:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/31806</link>
      <guid>https://ruby-china.org/topics/31806</guid>
    </item>
    <item>
      <title>运维环境简单化 - 容器封装</title>
      <description>&lt;h2 id="Overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;一个产品出现，从线下开发到线上 internal 服务器最后到 release 服务器，需要维护许多处的环境。现在，我们需要将环境同样抽象出来，做到本地开发环境变了，所有线上服务器部署前可以一并自动得到同步。&lt;/p&gt;
&lt;h2 id="Summary"&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;构建镜像 (在项目中添加 Dockerfile)&lt;/li&gt;
&lt;li&gt;增加部署项目，例如：app-deploy&lt;/li&gt;
&lt;li&gt;新建 Docker Hub 账户，并绑定你的项目地址 (GitHub, Bitbucket)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Quickly realize"&gt;Quickly realize&lt;/h2&gt;&lt;h3 id="1. 构建基础镜像"&gt;1. 构建基础镜像&lt;/h3&gt;
&lt;p&gt;在 &lt;a href="https://docs.docker.com/" rel="nofollow" target="_blank" title=""&gt;Docker Docs&lt;/a&gt; 里学习镜像相关操作，最后将现有的环境封装到镜像里，作为你应用的基础镜像，并最后上传 &lt;a href="https://hub.docker.com/" rel="nofollow" target="_blank" title=""&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;基础镜像的构建可以参照官方 Library. 例如：&lt;a href="https://github.com/docker-library/rails" rel="nofollow" target="_blank" title=""&gt;Rails 基础镜像&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;假设我们最后得到基础镜像名为：&lt;code&gt;pinewong/app-base:latest&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="2. 构建部署镜像(将基础镜像与项目源码组合)"&gt;2. 构建部署镜像 (将基础镜像与项目源码组合)&lt;/h3&gt;
&lt;p&gt;在项目主目录添加 Dockerfile 文件，内容不需要太多，对于 Rails 应用，类似这样：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM pinewong/app-base:latest
MAINTAINER PineWong &amp;lt;pinewong@163.com&amp;gt;

# Bundle
WORKDIR /app
ADD ./Gemfile Gemfile
ADD ./Gemfile.lock Gemfile.lock
RUN bundle install

# Assets
ADD ./app
RUN bundle exec rake assets:precompile RAILS_ENV=production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后在 &lt;a href="https://hub.docker.com/" rel="nofollow" target="_blank" title=""&gt;Docker Hub&lt;/a&gt; 上建立该项目的自动构建关联，并 Trigger 第一个镜像，得到部署镜像：&lt;code&gt;pinewong/app:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/e036ea82d252b9ad3f06ebcb332d0acd.png!large" title="" alt="Docker Hub与GitHub项目关联"&gt;&lt;/p&gt;
&lt;h3 id="3. 新建 app-deploy 项目"&gt;3. 新建 app-deploy 项目&lt;/h3&gt;
&lt;p&gt;目录大概如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;|app-deploy/
|__config/               &lt;span class="c"&gt;# 配置文件目录&lt;/span&gt;
|__|__nginx_site.conf    &lt;span class="c"&gt;# nginx site 设置&lt;/span&gt;
|__|__...             
|__docker-compose.yml    &lt;span class="c"&gt;# 主配置文件&lt;/span&gt;
|__app.default.env       &lt;span class="c"&gt;# 默认环境变量&lt;/span&gt;
|__...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-compose.yml:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '2'

services:
  app:
    image: pinewong/app:latest
    command: puma -C config/puma.rb
    depends_on:
      - postgres
    volumes:
      - ./config/nginx_site.conf:/etc/nginx/conf.d/default.conf
  nginx:
    image: nginx:1.11.5
    ports:
      - "80:80"
    depends_on:
      - app
  postgres:
    image: postgres:9.6.1
  # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该步骤具体配置可以参照社区 &lt;a href="http://gethomeland.com/install/" rel="nofollow" target="_blank" title=""&gt;homeland-docker&lt;/a&gt; 项目。&lt;/p&gt;

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

&lt;p&gt;最后将该项目分发到各服务器和开发人员，在目录下，直接：&lt;code&gt;docker-compose up&lt;/code&gt; 即可运行这个完全封装的应用。&lt;/p&gt;
&lt;h2 id="Advance"&gt;Advance&lt;/h2&gt;&lt;h3 id="开发调试"&gt;开发调试&lt;/h3&gt;
&lt;p&gt;上述步骤简单封装了一个生产环境，开发环境只需要做一下小修改，添加一个 docker-compose-dev.yml 文件，内容大致是去掉 Nginx 这类生产中用的组件，和替换一些轻量级开发组件：postgres 替换成 sqlite 等，并在环境变量配置中修改 RAILS_ENV 值，最后启动应用时添加文件参数 &lt;code&gt;--file docker-compose-dev.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;另外开发需要实时变化源码，我们直接使用容器的 Volume 功能，将本地源码目录映射替换容器源码目录：&lt;/p&gt;

&lt;p&gt;docker-compose.yml:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app:
  volumes:
    - ./app-dir:/app    # app-dir代表本地当前目录下的源码目录，app是上述部署容器所添加源码的目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，当启动容器运行程序后，你还可以进到容器内部调试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose &lt;span class="nb"&gt;exec &lt;/span&gt;app bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是，你就进到了一个应用环境的命令窗口，这里可以得到虚拟机的体验。&lt;/p&gt;
&lt;h3 id="对于项目的基础镜像管理"&gt;对于项目的基础镜像管理&lt;/h3&gt;
&lt;p&gt;为了更好管理各项目的基础镜像，这里可以学习官方操作，在自己的 GitHub 中建立一个 Library 源，这里有我一些环境的例子：&lt;a href="https://github.com/pinewong/docker-library" rel="nofollow" target="_blank" title=""&gt;pinewong/docker-library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这样还有一个好处，可以让基础镜像也使用自动构建服务。&lt;/p&gt;
&lt;h3 id="简化安装和命令输入"&gt;简化安装和命令输入&lt;/h3&gt;
&lt;p&gt;第一次部署项目时具体需要的操作有许多，例如初始化本地环境变量文件，数据库初始化，生成秘钥等等，这些都可以自动化，你可以写一个 shell 脚本来做这些，但更优雅的是学习 &lt;a href="https://github.com/ruby-china/homeland-docker" rel="nofollow" target="_blank" title=""&gt;homeland-docker&lt;/a&gt; 项目，使用 Makefile 清晰实现封装。&lt;/p&gt;

&lt;p&gt;另外如果使用了 Makefile 实现一些封装，你还可以将开发中的命令简短不少，例如这样设置 Makefile:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;status:
  @docker-compose ps
stop:
  @docker-compose stop
restart:
  @docker-compose restart &lt;span class="si"&gt;$(&lt;/span&gt;name&lt;span class="si"&gt;)&lt;/span&gt;
start:
  @docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
console:
  @docker-compose run app rails console
update:
  @make pull
  @make reload
pull:
  @docker pull pinewong/app:latest
  @docker pull nginx:1.11.5
  @docker pull postgres:9.6.1
reload:
  @docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--force-recreate&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后当你想要进入 Rails 控制台，可以使用命令&lt;code&gt;make console&lt;/code&gt; 代替 &lt;code&gt;docker-compose run app rails console&lt;/code&gt;；当发布新容器，需要升级时可以使用&lt;code&gt;make update&lt;/code&gt;代替&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull pinewong/app:latest
docker pull nginx:1.11.5
docker pull postgres:9.6.1
&lt;span class="c"&gt;# ...&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--force-recreate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，你也可以自己在 Makefile 中定义需要的命令。&lt;/p&gt;
&lt;h3 id="持续部署"&gt;持续部署&lt;/h3&gt;
&lt;p&gt;上面虽然解决了环境一致的问题，但部署由于脱离了 Mina 等工具变得麻烦起来，这时我们可以使用一些 CI 工具来自动完成，且上述用到的基本都是标准组件，持续部署将很容易实现。&lt;/p&gt;

&lt;p&gt;需要实现持续部署，首先你要明确整个流程，如果我们从项目源码提交开始，大致是这样：&lt;/p&gt;

&lt;p&gt;Commit -&amp;gt; Docker Hub 构建部署镜像 -&amp;gt; 各服务器升级程序 (pull 新镜像，重启容器)&lt;/p&gt;

&lt;p&gt;如果是基础环境变了，例如应用从 MySQL 迁移到 Postgres , 环境依赖需要安装 libmysqlclient-dev, 大致流程变成这样：&lt;/p&gt;

&lt;p&gt;docker-library 更新 -&amp;gt; Docker Hub 重新构建基础镜像 -&amp;gt; Docker Hub 检测到基础镜像改变 -&amp;gt; Docker Hub 重新构建部署镜像 -&amp;gt; 各服务器升级程序 (pull 新镜像，重启容器)&lt;/p&gt;

&lt;p&gt;综上，不管什么触发，当我们理解了流程，我们就可以在 CI 工具中新建一个任务。开心的是，Docker Hub 和 GitHub 已经结合的相当紧密，一般的触发可以直接在项目设置 webholk 来实现。最后对于 CI 工具，我使用的是 &lt;a href="https://jenkins.io/" rel="nofollow" target="_blank" title=""&gt;Jenkins&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Extend"&gt;Extend&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ruby-china/homeland-docker" rel="nofollow" target="_blank" title=""&gt;Ruby China 社区部署的好例子&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.infoq.com/cn/articles/continuous-deployment-containers" rel="nofollow" target="_blank" title=""&gt;通过容器进行持续部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-china.org/topics/30098" title=""&gt;生产环境使用 Docker 部署 Rails 应用 Puma 和 Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.embbnux.com/2016/02/21/docker_for_rails_development_with_postgresql_and_redis/" rel="nofollow" target="_blank" title=""&gt;使用 docker 快速构建 rails 开发环境&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 19 Nov 2016 18:57:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/31639</link>
      <guid>https://ruby-china.org/topics/31639</guid>
    </item>
    <item>
      <title>统一账户管理平台</title>
      <description>&lt;p&gt;Google, GitHub, Twitter, Facebook, Weibo...纷纷有自己的认证系统，使得在他们平台注册过的用户可以快速在其他产品获取一个身份，不再需要设置用户名等一些基本信息等。&lt;/p&gt;

&lt;p&gt;但是，他们始终自己也是一个产品/互联网公司，在账户管理上限制的有点多了，例如 GitHub 是一个开发者必备平台，对一般拥有账号的开发者很方便，但普通用户就不太可能用到了。另外，由其他的互联网公司提供授权，对于很多公司，本来就不太乐意。&lt;/p&gt;

&lt;p&gt;所以，如果有一个统一的账户管理平台非盈利组织 (以下称作 Account Platform ), 他没有绑定产品，做到了这些 (暂时想到): &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每个用户/组织有唯一的 key 值，该值可以很长，由系统自动生成，是用户/组织在 Account Platform 的标识符。&lt;/li&gt;
&lt;li&gt;可供修改的 ID 值，该 ID 值是登录其他产品的标识符，即授权给一个产品后，便类似于以该 ID 注册一个身份 (对于产品内部可以给个字段标示，以防止与产品原生 ID 冲突). &lt;/li&gt;
&lt;li&gt;国籍值，账户所属主国籍，在下面会阐述需要该字段的原因。&lt;/li&gt;
&lt;li&gt;提供基本的信息设置：Email, Fullname, Avatar, Bio...&lt;/li&gt;
&lt;li&gt;已授权产品的管理，在 Account Platform 下管理自己互联网上的账户，登录信息，账户安全等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;如果以上需要实现，虽然对一个互联网用户很友好 (一个 Account Platform 通行证就行了), 但是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;安全问题是主要的，在便利的同时，安全也寄托在了一处。此处我想到的是，该平台不应该独立成某个盈利组织，应该由各大巨头共同维护。&lt;/li&gt;
&lt;li&gt;由于 Account Platform 适用于所有互联网用户，因此数据庞大。对于 ID 被占用的情况的问题 (这是 Geek 很看重的东西), 我想到是设置一个国籍标志，让每个 Account Platform 都拥有一个主国籍。理论上在同一个国籍下，ID 冲突的可能性还是很低的。当然对于普通用户，注册或授权时会自动根据地理位置设置好国籍，完全不需要理会。于是，当世界上两个国家里名字都叫做 Tom 的人在连续注册的过程中不会产生困扰，组织更是如此。&lt;/li&gt;
&lt;li&gt;对于大多数产品需要邮箱注册的问题，Account Platform 注册成功时会提示绑定一个邮箱，但不强制。当用户尝试用为设置邮箱的 Account Platform 账户授权时会给出提示绑定邮箱。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="最后"&gt;最后&lt;/h2&gt;
&lt;p&gt;这个"扯淡"是本人经常在各大网站登录，并且由于 ID 各网站不统一 (被占用) 导致各种登录困难而随意想出来的，其可行性是想到了 Gravatar 的成功，当然头像不属于敏感信息，实现起来比这个容易多了。&lt;/p&gt;

&lt;p&gt;最后，这个想法能实现吗？如果不能，关键在于哪些点？&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 12 Nov 2016 14:22:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/31580</link>
      <guid>https://ruby-china.org/topics/31580</guid>
    </item>
    <item>
      <title>对于 2016 MacBook Pro 新模具, 大家怎么看</title>
      <description>&lt;p&gt;今年的 Pro 似乎改的有点激进，苹果自己也保留了一款，只更新了配置。&lt;a href="http://www.apple.com/shop/buy-mac/macbook-pro" rel="nofollow" target="_blank" title=""&gt;Buy MacBook Pro - Apple&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;但是 Pro 算到现在几乎四年才改了一次外观，旧款又感觉不值...&lt;/p&gt;

&lt;p&gt;所以，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对于 2016 MacBook Pro 新模具，大家怎么看？&lt;/li&gt;
&lt;li&gt;价格一定的情况下，对于旧款的高配和新款更好的体验 (Touch Bar and Touch ID), 哪个更实际一点？&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 05 Nov 2016 18:01:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/31525</link>
      <guid>https://ruby-china.org/topics/31525</guid>
    </item>
    <item>
      <title>Mina 部署 Clone 失败, `No submodule mapping found in`</title>
      <description>&lt;h2 id="环境"&gt;环境&lt;/h2&gt;
&lt;p&gt;Ubuntu 14.04
Git 1.9.1&lt;/p&gt;
&lt;h2 id="第一次部署错误"&gt;第一次部署错误&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/b926b4bb8b9f8b14acc143c3d80ead84.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="出错后, 保留项目目录再部署错误"&gt;出错后，保留项目目录再部署错误&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/0f8467b34696b72cd3fc1109f307c113.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;部署步骤，设置 deploy.rb -&amp;gt; mina setup -&amp;gt; mina deploy.
Google 搜到 Git 相关内容，没有针对 Mina 的。大家有没有遇到过相关错误的？&lt;/p&gt;
&lt;h2 id="问题已解决"&gt;问题已解决&lt;/h2&gt;
&lt;p&gt;删除提示路径的空目录 /public/demo, 就不再报错了！具体原因不清楚...&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sun, 07 Aug 2016 23:11:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/30761</link>
      <guid>https://ruby-china.org/topics/30761</guid>
    </item>
    <item>
      <title>Rails 5 默认不再使用`ActionController::TestCase`，控制器测试显得有点不方便</title>
      <description>&lt;p&gt;以前的项目 generate 生成的控制器测试类都是继承&lt;code&gt;ActionController::TestCase&lt;/code&gt;类，在 Rails5 中改成继承&lt;code&gt;ActionDispatch::IntegrationTest&lt;/code&gt;类了，也就是集成测试和控制器测试已经没有区别，只是放的文件夹不同了。
这样，以前的控制器测试里，直接对 Action 的测试，现在有点变扭，下面是具体代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s1"&gt;'index action in StaticsController'&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;   &lt;span class="c1"&gt;# 之前&lt;/span&gt;
  &lt;span class="c1"&gt;# get statics_index_path   # Rails5用法，现在要指定到该方法的路径，也就是测试类下的get/post/...等方法不再特殊，与普通方法一致&lt;/span&gt;

  &lt;span class="c1"&gt;# assert...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样控制器测试在修改 Route，假设 statics_index_path 改成 root_path 或 index_path 后，测试就不能通过了。
的确&lt;code&gt;ActionController::TestCase&lt;/code&gt;类重载的 http 方法 (get/post/...) 有时会让人产生费解，但不能否认，还是挺好用的！
最后，问问大家：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minitest 中有没有其他方法解决这个问题？&lt;/li&gt;
&lt;li&gt;或者项目中都是用 RSpec，在 Minitest 中随意就好？&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>pinewong</author>
      <pubDate>Mon, 11 Jul 2016 23:03:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/30501</link>
      <guid>https://ruby-china.org/topics/30501</guid>
    </item>
    <item>
      <title>简历投递有哪些技巧</title>
      <description>&lt;p&gt;来问下大家简历投递有哪些技巧？
最近听到一个资深 hr 说，正文不吸引人上附件的直接 delete 掉！
那我们是要在正文中给出一些简历截图，还是用 WEB 形式简历代替附件，直接给出链接较好？
社区强大，看看大家都是什么回复。&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sun, 03 Jul 2016 18:02:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/30434</link>
      <guid>https://ruby-china.org/topics/30434</guid>
    </item>
    <item>
      <title>Docker 部署多站点、镜像与代码分离实践</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Docker 为部署提供了便捷，能让新手快速部署复杂的服务器环境，并把环境保存下来。网上有许多 Docker 实践，本文实践的是如何利用 Docker 容器封装一个与代码配置分离的服务器环境，每次修改配置只需重启容器，无需重新建立镜像，并简单实现了多个站点部署。选择部署的环境是 Rails，基础镜像选用集成服务器环境的 phusion/passenger-ruby22，数据库选用 postgres&lt;/p&gt;
&lt;h2 id="实践过程"&gt;实践过程&lt;/h2&gt;&lt;h3 id="环境介绍"&gt;环境介绍&lt;/h3&gt;
&lt;p&gt;为了突出重点，部署没有涉及 mina 以及 puma 相关配置&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps                    # 服务器工作空间目录
|__ deploy                 # 部署配置文件目录
|__ |__ Dockerfile
|__ |__ docker.sh         # 容器每次启动运行都会执行该脚本
|__ |__ docker-link.sh     # 通过该文件使得容器启动执行脚本与镜像隔离，后面有文件内容
|__ |__ nginx.conf
|__ |__ nginx-env.conf
|__ |__ database.yml
|__ |__ secrets.yml
|__ |__ ...ddjh
|__ site_a                   # 网站源码目录
|__ site_b
|__ ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述目录结构需要事先在服务器中搭建好，我的操作步骤是建立在/home 目录下建立好 apps 目录后，在我们的工作目录 apps 里 git clone 相关网站；deploy 部署目录含相关服务器和密钥配置，下方写好后通过 scp 命令上传&lt;/p&gt;
&lt;h3 id="文件"&gt;文件&lt;/h3&gt;&lt;h4 id="Dockerfile"&gt;Dockerfile&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 以phusion/passenger-ruby22镜像为基础
FROM phusion/passenger-ruby22

# 设置项目目录
VOLUME ['/home/apps']
WORKDIR /home/apps

# 设置容器运行执行命令
CMD ["/sbin/my_init"]

# 开放80端口
EXPOSE 80

# 设置时区
RUN echo "Asia/Shanghai" &amp;gt; /etc/timezone
RUN dpkg-reconfigure -f noninteractive tzdata

# 设置当前环境变量
ENV HOME /root

# 设置rails环境为生产环境
ENV RAILS_ENV production

# 启动nginx
RUN rm -f /etc/service/nginx/down

# 增加初始化运行脚本
RUN mkdir -p /etc/my_init.d
COPY docker-link.sh /etc/my_init.d/docker-link.sh
RUN chmod +x /etc/my_init.d/docker-link.sh

# 修改国内源
RUN gem sources -r https://rubygems.org/ -a https://gems.ruby-china.org/
RUN bundle config mirror.https://rubygems.org https://gems.ruby-china.org
# RUN gem sources -r https://rubygems.org/ -a http://mirrors.aliyun.com/rubygems/
# RUN bundle config mirror.https://rubygems.org http://mirrors.aliyun.com/rubygems

# 清楚产生的缓存文件
RUN apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="docker-link.sh (使得执行脚本可以在镜像建立后自定义)"&gt;docker-link.sh (使得执行脚本可以在镜像建立后自定义)&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

bash /home/apps/deploy/docker.sh
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="docker.sh"&gt;docker.sh&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/*** 变量配置---------------:'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;workdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/home/apps'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c"&gt;# 工作空间目录，需要与Dockerfile文件的项目目录对应&lt;/span&gt;
&lt;span class="c"&gt;# workdir='/var/www/apps';      # 其他类似工作空间目录&lt;/span&gt;
&lt;span class="nv"&gt;deployname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'deploy'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c"&gt;# 部署目录名&lt;/span&gt;
&lt;span class="nv"&gt;sitearr&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s1"&gt;'site_a'&lt;/span&gt; &lt;span class="s1"&gt;'site_b'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c"&gt;# 配置所有项目名&lt;/span&gt;


&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/*** 配置nginx---------------:'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; /etc/nginx/sites-enabled/default&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;workdir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deployname&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/nginx.conf /etc/nginx/sites-enabled/nginx.conf&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/*** 添加环境文件---------------:'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;workdir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;deployname&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/nginx-env.conf /etc/nginx/main.d/nginx-env.conf&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/*** 遍历每个项目执行操作---------------:'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;site &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sitearr&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/*** &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;site&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;配置---------------:"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;workdir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;site&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    bundle &lt;span class="nb"&gt;install&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    rake assets:precompile&lt;span class="p"&gt;;&lt;/span&gt;
    rake db:create&lt;span class="p"&gt;;&lt;/span&gt;
    rake db:migrate&lt;span class="p"&gt;;&lt;/span&gt;
    rake db:seed&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="nginx.conf (site_a, site_b根据自己需求修改)"&gt;nginx.conf (site_a, site_b 根据自己需求修改)&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# site_a
server {
    listen 80;
    root /home/apps/site_a/public;
    server_name site_a.urlis.cn;

    # 开启passenger
    passenger_enabled on;
    passenger_user root;
    client_max_body_size 20M;
    passenger_max_request_queue_size 0;
}

# site_b
server {
    listen 80;
    root /home/apps/site_b/public;
    server_name site_b.urlis.cn;

    # 开启passenger
    passenger_enabled on;
    passenger_user root;
    client_max_body_size 20M;
    passenger_max_request_queue_size 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="nginx-env.conf"&gt;nginx-env.conf&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 暴露服务端口 这个示例中只有postgres服务，因此我们只需要暴露这个端口即可
env POSTGRES_PORT_5432_TCP_ADDR;
env POSTGRES_PORT_5432_TCP_PORT;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="database.yml (主要为production的host和port项目，需要与nginx-env.conf文件对应)"&gt;database.yml (主要为 production 的 host 和 port 项目，需要与 nginx-env.conf 文件对应)&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;production:
  &amp;lt;&amp;lt;: *default
  database: site_production
  host:     &amp;lt;%= ENV['POSTGRES_PORT_5432_TCP_ADDR'] || 'localhost' %&amp;gt;
  port:     &amp;lt;%= ENV['POSTGRES_PORT_5432_TCP_PORT'] || '5432' %&amp;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 plaintext"&gt;&lt;code&gt;local$ ssh user@server;
server$ mkdir -p /home/apps;
server$ cd /home/apps;
server$ git clone https://github.com/pinewong/site_a;
server$ git clone https://github.com/pinewong/site_b;
...
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="2. scp上传deploy部署配置目录 (本地机操作)"&gt;2. scp 上传 deploy 部署配置目录 (本地机操作)&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local$ scp -r deploy user@server:/home/apps
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3. 建立部署镜像并启动"&gt;3. 建立部署镜像并启动&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server$ cd /home/apps/deploy
server$ docker pull postgres; docker pull phusion/passenger-ruby22;
server$ docker rm -f postgres; docker run --name postgres -p 5432:5432 -d postgres;
server$ docker rmi -f pinewong/passenger-ruby22; docker build -t pinewong/passenger-ruby22 .;
server$ docker rm -f web; docker run --name web --link postgres -v /home/apps:/home/apps -p 80:80 -d pinewong/passenger-ruby22;
server$ docker logs -f web;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后等待终端的 log 日志输出，查看之前编写的 docker.sh 脚本执行情况。如果顺利，最后 nginx 服务器会启动成功。
以后有新项目上线测试时，只需要在服务器工作目录 clone 源码，然后添加 deploy/nginx.conf 配置，deploy/docker.sh 的 sitearr 数组变量值添加新项目名称，最后重启容器 (docker restart web)。
另外以后当修改配置文件，或新增 gem 时，也只需要执行容器容器命令&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;文章参考：&lt;a href="http://gamefu.github.io/2015/07/09/deploy-docker-passenger-rails-in-coreos/" rel="nofollow" target="_blank" title=""&gt;gameFu's Blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>pinewong</author>
      <pubDate>Fri, 17 Jun 2016 21:13:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/30308</link>
      <guid>https://ruby-china.org/topics/30308</guid>
    </item>
    <item>
      <title>Grape 健壮参数问题处理</title>
      <description>&lt;p&gt;我在 Rails 中集成 Grape，进行 Create 操作时，Rails 有个健壮参数控制，我尝试用下面两种方式解决：&lt;/p&gt;
&lt;h3 id="第一种，Rails原生方案"&gt;第一种，Rails 原生方案&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但问题是 required 参数不知道用什么，因为 API 请求跟普通站点不同，不会把参数都封装到一个哈希里面，而是全部直接放到根节点。我试过不加 required 方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是没有像预期返回包含&lt;code&gt;:name, :email&lt;/code&gt;的哈希，而是返回 NULL。
好吧，其实第一种就是自己对 params 对象的 required 和 permit 方法原理不熟导致！不知道怎么解决单层哈希的健壮参数处理&lt;/p&gt;
&lt;h3 id="第二种，Gem去除健壮参数控制，然后采用Grape的declared控制参数"&gt;第二种，Gem 去除健壮参数控制，然后采用 Grape 的 declared 控制参数&lt;/h3&gt;
&lt;p&gt;这个能解决，Grape 文档上也有说明&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但有个问题，这样我的项目里其他 Controller 中，都不能用健壮参数了，对混合程序有点麻烦.
所以来社区发个帖子，问下你们是怎么解决这个问题的？&lt;/p&gt;</description>
      <author>pinewong</author>
      <pubDate>Sat, 11 Jun 2016 09:22:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/30247</link>
      <guid>https://ruby-china.org/topics/30247</guid>
    </item>
  </channel>
</rss>
