<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>piecehealth</title>
    <link>https://ruby-china.org/piecehealth</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Web 后端框架支持 Turbo</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/piecehealth/ab9fe127-3103-47b5-a54f-7a31108819ad.png!large" title="" alt="dhh"&gt;&lt;/p&gt;

&lt;p&gt;老凡尔赛 dhh 发布了他的 40kb js 做出 hey.com 的&lt;a href="https://hotwire.dev/" rel="nofollow" target="_blank" title=""&gt;new magic&lt;/a&gt;，Turbo 则是"new magic"的核心。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwire.dev/" rel="nofollow" target="_blank" title=""&gt;Turbo&lt;/a&gt;仅仅是一个 JavaScript 库，所以理论上任何后端框架都可以配合 Turbo，下面介绍一下 Turbo 是怎么工作的以及后端框架如何实现对 Turbo 的支持。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;本文重点不是介绍 Turbo，看过 dhh 的视频以及 Turbo 文档后再来看本文效果更佳&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="Turbo Drive"&gt;&lt;a href="https://turbo.hotwire.dev/handbook/drive" rel="nofollow" target="_blank" title=""&gt;Turbo Drive&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Turbo Drive 是浏览器导航（navigation）加速方案，作为 Rails 程序员都很熟悉，就是 turbolinks 的马甲，是一个纯前端的方案，所有服务端渲染的框架不需要做任何改变就可以直接使用。&lt;/p&gt;
&lt;h2 id="Turbo Frames"&gt;&lt;a href="https://turbo.hotwire.dev/handbook/frames" rel="nofollow" target="_blank" title=""&gt;Turbo Frames&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Turbo Frames 可以使页面局部更新（url 不会改变），比如在 dhh 视频中&lt;code&gt;rooms#show&lt;/code&gt;页面有三部分（蓝框的是 turbo frame）：房间信息（frame id 是&lt;code&gt;room&lt;/code&gt;），此房间的聊天记录，发送聊天记录的 form。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/piecehealth/625ca786-95b8-45a3-8ba8-88e0de596e0d.png!large" title="" alt="rooms#show"&gt;&lt;/p&gt;

&lt;p&gt;同时，我们也有&lt;code&gt;rooms#edit&lt;/code&gt;页面，这个页面只有一个编辑 room 的 form（frame id 也是&lt;code&gt;room&lt;/code&gt;）跟一些提示信息。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/piecehealth/92a65eb0-6095-4374-9853-90f803c2729b.png!large" title="" alt="rooms#edit"&gt;&lt;/p&gt;

&lt;p&gt;使用 frame 之后，在房间信息里点“edit”（url 是&lt;code&gt;/rooms/:id/edit&lt;/code&gt;），页面并没有跳转，只是同名 frame 做了替换（&lt;code&gt;rooms#edit&lt;/code&gt;页面 frame 之外的内容被丢弃）&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/piecehealth/d281fda9-5976-4cdd-83d2-0d52e65c5df8.png!large" title="" alt="rooms#show#edit"&gt;&lt;/p&gt;

&lt;p&gt;实现原理也很简单：在 frame 里的 link 被点击后，会向服务器发送一个 get 请求（这里的请求是&lt;code&gt;/rooms/:id/edit&lt;/code&gt;），服务端会返回完整的 html，然后 turbo 只需要把返回的 html 中同名 frame 与当前 frame 替换即可。&lt;/p&gt;

&lt;p&gt;后端框架理论上也不需要做任何事就能支持，只需要在 html 模版中加入&lt;code&gt;turbo-frame&lt;/code&gt;标签并加上对应的 id 即可。后端可以优化的是，turbo frame 里发出请求的 response 不需要返回模版的 layout（例如 Rails 的 application.html.erb）&lt;/p&gt;

&lt;p&gt;后端需要：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;通过看 headers 里的&lt;code&gt;Turbo-Frame&lt;/code&gt;来判断是否是来自 turbo-frame 里的请求。&lt;/li&gt;
&lt;li&gt;如果是来自 turbo-frame 的请求，response 里不带通用的 layout&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# turbo-rails 实现&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Turbo::Frames::FrameRequest&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_request?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                 &lt;span class="c1"&gt;# &amp;lt;- 来自turbo frame的请求layout设为false&lt;/span&gt;
    &lt;span class="n"&gt;etag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:frame&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_request?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;turbo_frame_request?&lt;/span&gt;                            &lt;span class="c1"&gt;# &amp;lt;- 判断请求是否来自turbo frame&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Turbo-Frame"&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Turbo Streams"&gt;&lt;a href="https://turbo.hotwire.dev/handbook/streams" rel="nofollow" target="_blank" title=""&gt;Turbo Streams&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Turbo Streams 是 Turbo 最灵性的地方，通过服务端返回一小段 html 来更新当前页面。原理简单，容易理解，效果也不错，值得拥有！&lt;/p&gt;

&lt;p&gt;拿&lt;a href="https://hotwire.dev/" rel="nofollow" target="_blank" title=""&gt;dhh 的教程&lt;/a&gt;举例：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/piecehealth/625ca786-95b8-45a3-8ba8-88e0de596e0d.png!large" title="" alt="rooms#show"&gt;&lt;/p&gt;

&lt;p&gt;发送一条新 message（下方蓝框内的 turbo-frame 里的 form submit），服务端将返回&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-stream&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"messages"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"append"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message_4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;08 Feb 15:24: spam4&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-stream&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turbo 理解了这段 html 的意思：往 id 是&lt;code&gt;messages&lt;/code&gt;的 dom 元素里&lt;code&gt;append&lt;/code&gt; template 里面的 html，即&lt;code&gt;&amp;lt;p id="message_4"&amp;gt;08 Feb 15:24: spam4&amp;lt;/p&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;实际应用中，&lt;code&gt;&amp;lt;p id="message_4"&amp;gt;08 Feb 15:24: spam4&amp;lt;/p&amp;gt;&lt;/code&gt;往往来自一个局部模版。如&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= turbo_stream.append "messages", @message %&amp;gt; # 根据@message类型推断使用`messages/_message.html.erb`模版。
等价于
&amp;lt;turbo-stream action="append" target="messages"&amp;gt;
  &amp;lt;template&amp;gt;
    &amp;lt;%= render @message %&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/turbo-stream&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以说 Turbo 即复用了服务端渲染的模版，又可以在不写 JavaScript 的情况下完成页面的更新。&lt;/p&gt;

&lt;p&gt;服务端一般两个地方返回 Turbo Stream HTML：Turbo Frame 里提交的 form 可以返回 Turbo Stream HTML，也可以通过 websocket 推过来 Turbo Stream HTML。&lt;/p&gt;
&lt;h3 id="Turbo Frame里提交的Form"&gt;Turbo Frame 里提交的 Form&lt;/h3&gt;
&lt;p&gt;Turbo Frame 里提交的 Form 的请求头的 accpet type 是&lt;code&gt;text/vnd.turbo-stream.html&lt;/code&gt;，遇到这个请求头就可以返回 Turbo Stream HTML，并且把 response 的 content type 也设置成&lt;code&gt;text/vnd.turbo-stream.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# turbo-rails注册turbo mimetype&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Turbo&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Engine&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt;
    &lt;span class="n"&gt;initializer&lt;/span&gt; &lt;span class="s2"&gt;"turbo.mimetype"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="s2"&gt;"text/vnd.turbo-stream.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:turbo_stream&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="c1"&gt;# app/controllers/messages_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessagesController&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="ss"&gt;:set_room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[ new create ]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@room.messages.create&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt;                        &lt;span class="c1"&gt;# 自动找 messages/create.turbo_stream.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@room&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="kp"&gt;private&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_room&lt;/span&gt;
      &lt;span class="vi"&gt;@room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:room_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;message_params&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;require&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="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:content&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;h3 id="通过websocket推过来Turbo Stream HTML"&gt;通过 websocket 推过来 Turbo Stream HTML&lt;/h3&gt;
&lt;p&gt;turbo-rails 的做法是自定义一个 html 标签&lt;code&gt;turbo-cable-stream-source&lt;/code&gt;。标签中有一个加密过的 topic，例如&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@room&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会生成&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-cable-stream-source&lt;/span&gt; &lt;span class="na"&gt;channel=&lt;/span&gt;&lt;span class="s"&gt;"Turbo::StreamsChannel"&lt;/span&gt; &lt;span class="na"&gt;signed-stream-name=&lt;/span&gt;&lt;span class="s"&gt;"IloybGtPaTh2WT"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-cable-stream-source&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;标签对应的 javascript 代码：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connectStreamSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disconnectStreamSource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/turbo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subscribeTo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboCableStreamSourceElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;connectStreamSource&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;subscribeTo&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;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;received&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;dispatchMessageEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;disconnectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;disconnectStreamSource&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="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;subscription&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;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;dispatchMessageEvent&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&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;data&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="nf"&gt;dispatchEvent&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;channel&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;channel&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channel&lt;/span&gt;&lt;span class="dl"&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;signed_stream_name&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;signed-stream-name&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signed_stream_name&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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo-cable-stream-source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TurboCableStreamSourceElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;通过自定义标签的&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="nofollow" target="_blank" title=""&gt;connectedCallback&lt;/a&gt;，会自动订阅相应的 topic。&lt;/li&gt;
&lt;li&gt;开发者只需要向 topic 推对应的 turbo stream 的 html 片段，&lt;code&gt;TurboCableStreamSourceElement&lt;/code&gt;会把这个 html 片段通过 dispatchEvent 传递给 Turbo，Turbo 最终完成页面的局部更新。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;支持 websocket 的后端框架可以复制这一个过程：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先要自定一个类似&lt;code&gt;turbo-cable-stream-source&lt;/code&gt;的 html 标签，配合 JavaScript 代码使标签出现后自动订阅一个 topic（suscribe topic 的代码根据不同的后端框架需要有调整）。&lt;/li&gt;
&lt;li&gt;服务端有一个 channel 来 handle 这些 topic，例如在 turbo-rails 里是&lt;code&gt;Turbo::StreamsChannel&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Turbo::StreamsChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcasts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamName&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verified_stream_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verified_stream_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:signed_stream_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
      &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="n"&gt;verified_stream_name&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;reject&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;ul&gt;
&lt;li&gt;这时候只需要向 topic 发 turob stream html 就好了。为了方便，可以把发送 html 做成 helper 方法方便调用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有了这些背景知识，很容易就能看懂&lt;a href="https://github.com/hotwired/turbo-rails" rel="nofollow" target="_blank" title=""&gt;turbo-rails&lt;/a&gt;，并且了解 turbo 是怎么工作的。&lt;/p&gt;

&lt;p&gt;我也自己写了一个 phoinex 版本的&lt;a href="https://github.com/piecehealth/phoenix_turbo" rel="nofollow" target="_blank" title=""&gt;phoenix_turbo&lt;/a&gt; &lt;/p&gt;</description>
      <author>piecehealth</author>
      <pubDate>Tue, 09 Feb 2021 15:14:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/40905</link>
      <guid>https://ruby-china.org/topics/40905</guid>
    </item>
    <item>
      <title>[上海 - 美资-E 轮 - 电商] GOAT Group 招聘后端工程师程序员</title>
      <description>&lt;p&gt;公司，岗位介绍，薪资福利请参考
&lt;a href="https://ruby-china.org/topics/39368" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/39368&lt;/a&gt;
需要更新的信息：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;年底会搬到兴业太古汇，地铁南京西路站。&lt;/li&gt;
&lt;li&gt;最近刚刚完成&lt;a href="https://www.crunchbase.com/funding_round/goatapp-series-e--79326310" rel="nofollow" target="_blank" title=""&gt;E 轮融资&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;这次招聘目标是中级或者优秀的初级工程师，高级资深的职位最近还没有 open。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要 highlight 的福利：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;比绝大多数公司好补充的商业医疗保险，好多私立医院、公立医院的特需门诊都可以报销。&lt;/li&gt;
&lt;li&gt;可以跟 CUBA 退役球员一起打篮球。&lt;/li&gt;
&lt;li&gt;员工买鞋、卖鞋都有优惠以及渠道上的便利。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于后端工程师的好消息：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby，Rails，以及第三方 gems 版本都紧跟时代，目前 ruby 2.7.1p83, Rails 6.0.3.3，Rails 6 的多数据支持也快上线了。&lt;/li&gt;
&lt;li&gt;目前有很多 Go 项目，未来的技术栈也会向 Go 倾斜。&lt;/li&gt;
&lt;li&gt;用户量、并发量比大多数 Rails 项目要大，有机会施展你的屠龙之技。&lt;/li&gt;
&lt;li&gt;公司规模在增长，技术、架构上也会有很多机遇与挑战。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同时还招 高级安卓开发工程师（英文流利），Manual QA&lt;/p&gt;

&lt;p&gt;简历投递：piecehealth@sina.com&lt;/p&gt;</description>
      <author>piecehealth</author>
      <pubDate>Tue, 20 Oct 2020 11:21:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/40490</link>
      <guid>https://ruby-china.org/topics/40490</guid>
    </item>
    <item>
      <title>ActiveRecord Schema cache dump</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;ActiveRecord 可以自动映射数据库字段到对应类属性，是一个非常酷的特性，但是也带来一个问题：这个特性是否是免费的？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Schema Cache"&gt;Schema Cache&lt;/h2&gt;
&lt;p&gt;可以做一个简单的实验：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在你的 Rails 项目中添加一个 initalizer 用来输入所有执行过的数据库 query:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/print_sql.rb&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sql.active_record"&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;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&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="ss"&gt;:sql&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;/li&gt;
&lt;li&gt;
&lt;p&gt;打开一个&lt;code&gt;rails console&lt;/code&gt;，查看一个 model 对象：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  rails_app git:(master) ✗ rails console
development main · &amp;gt; User
=&amp;gt; User (call 'User.connection' to establish a connection)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时的 User model 并不知道自己有哪些属性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行&lt;code&gt;User.connection&lt;/code&gt;或者任何用到数据库属性的操作：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="err"&gt;·&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_min_messages&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;standard_conforming_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'29s'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'UTC'&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_type&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'int2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'oid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bool'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typelem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typdelim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typinput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rngsubtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typbasetype&lt;/span&gt;
          &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_type&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
          &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_range&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rngtypid&lt;/span&gt;
          &lt;span class="k"&gt;WHERE&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'int2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'oid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'varchar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'char'&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="s1"&gt;'bpchar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bool'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'varbit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'timestamptz'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'money'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bytea'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'point'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hstore'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jsonb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cidr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'uuid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'xml'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tsvector'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'macaddr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'citext'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ltree'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lseg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'box'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'polygon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'circle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'interval'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'timestamp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'numeric'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typtype&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typinput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'array_in(cstring,oid,integer)'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regprocedure&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typelem&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;
          &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypmod&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="n"&gt;pg_get_expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adbin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adrelid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnotnull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypmod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt;
            &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_attribute&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
            &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_attrdef&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adrelid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adnum&lt;/span&gt;
            &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
            &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_collation&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attcollation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attcollation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typcollation&lt;/span&gt;
           &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'"users"'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;
             &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attisdropped&lt;/span&gt;
           &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;max_identifier_length&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_schemas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;indrelid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generate_subscripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indkey&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="n"&gt;idx&lt;/span&gt;
       &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt;
      &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;indrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'"users"'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;indisprimary&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_attribute&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indrelid&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indkey&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这时，ActiveRecord 会查询数据库来获得 User model 的属性以及其他 metadata（以上是 postgreSQL 用到的 query，不同数据库略有不同），真正完成 User model 的初始化工作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每个数据库表相关的属性都会存到数据库 connection 中，这样下次再遇到 User 表就不用再次查询数据库，存这些 metadata 的地方就是&lt;a href="https://github.com/rails/rails/blob/v6.0.3/activerecord/lib/active_record/connection_adapters/schema_cache.rb" rel="nofollow" target="_blank" title=""&gt;schema cache&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Schema Cache Dump"&gt;Schema Cache Dump&lt;/h2&gt;
&lt;p&gt;上述 ActiveRecord 的动作方式，对程序员比较友好，可以少些很多“模版代码”，但是对数据库并不友好：schema cache 是 connection pool 共享的，不同的 rails 进程是不会共享的。换句话说，当你的应用有很多 rails 进程的时候，读取每个表结构的 query 都要在每个 rails 进程中执行一次。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;这些 query 是没法被优化、命中索引的，所以在你的应用重新部署后，基本是所有的 rails 进程都在并发执行不效率的 query。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;解决这个问题的方法也很简单：在跑完&lt;code&gt;rake db:migrate&lt;/code&gt;之后，把此刻数据库的&lt;code&gt;schema cache&lt;/code&gt;导出一个文件，新的 rails 进程直接通过访问导出的文件来获取表结构等 metadata 即可。&lt;/p&gt;

&lt;p&gt;导出文件的命令是&lt;code&gt;rake db:schema:cache:dump&lt;/code&gt;(&lt;a href="https://github.com/rails/rails/blob/v6.0.3/activerecord/lib/active_record/railties/databases.rake#L409-L418" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/v6.0.3/activerecord/lib/active_record/railties/databases.rake#L409-L418&lt;/a&gt;) 默认会导出到&lt;code&gt;db/schema_cache.yml&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;读取&lt;code&gt;schema_cache&lt;/code&gt;是 rails&lt;a href="https://github.com/rails/rails/blob/v6.0.3/activerecord/lib/active_record/railtie.rb#L124-L145" rel="nofollow" target="_blank" title=""&gt;默认的行为&lt;/a&gt;，只要监测到&lt;code&gt;db/schema_cache.yml&lt;/code&gt;文件就会尝试解析，如果&lt;code&gt;db/schema_cache.yml&lt;/code&gt;中的 migration 版本跟当前数据库版本一致，则把 schema_cache 的内容加载到 connection pool 中，从而省去很多没有必要的 query。&lt;/p&gt;

&lt;p&gt;在实践中跑&lt;code&gt;rake db:migrate&lt;/code&gt;的机器跟真正启动 rails 进程的机器不是一台，把&lt;code&gt;db/schema_cache.yml&lt;/code&gt;同步到跑 rails 进程的机器需要自己或者运维人员做点工作，我们的做法是把文件内容写到 memcache 中，rails 进程启动时再生成&lt;code&gt;db/schema_cache.yml&lt;/code&gt;（读取 schema cache 是在 after_initialize 之后，所以在 initializer 里生成文件即可）&lt;/p&gt;
&lt;h2 id="如果你正在使用Resque"&gt;如果你正在使用 Resque&lt;/h2&gt;
&lt;p&gt;Resque 的工作方式是每个 job 都 fork 一个进程出来，即每个 job 都要新初始化一个数据库连接，初始化表结构这些事都要重新做一遍，所以使用 schema cache dump 功能对 Resque 提升巨大。&lt;/p&gt;
&lt;h2 id="如果你正在使用PostgreSQL"&gt;如果你正在使用 PostgreSQL&lt;/h2&gt;
&lt;p&gt;当初始化一个数据库连接时，除了查询表结构的 query，PostgreSQL 还会执行一系列 set 操作，如&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;client_min_messages&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'warning'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;standard_conforming_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'29s'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 pgbouncer 可以省去上面的查询
&lt;a href="http://www.pgbouncer.org/" rel="nofollow" target="_blank"&gt;http://www.pgbouncer.org/&lt;/a&gt;
&lt;a href="https://github.com/remind101/activerecord-pgbouncer" rel="nofollow" target="_blank"&gt;https://github.com/remind101/activerecord-pgbouncer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;还有一些对 postgreSQL 特有/自定义类型字段的查询&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_type&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'int2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'oid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bool'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typelem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typdelim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typinput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rngsubtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typbasetype&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_type&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_range&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rngtypid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typname&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'int2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'int8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'oid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'float8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'varchar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'char'&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="s1"&gt;'bpchar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bool'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'varbit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'timestamptz'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'money'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bytea'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'point'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hstore'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jsonb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cidr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'uuid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'xml'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tsvector'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'macaddr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'citext'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ltree'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'line'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lseg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'box'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'polygon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'circle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'interval'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'timestamp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'numeric'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typtype&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typinput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'array_in(cstring,oid,integer)'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regprocedure&lt;/span&gt;
  &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typelem&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这类 query 对某些 Resque job 比较多应用也是一笔不小的开销 &lt;a href="https://github.com/rails/rails/issues/35311" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/issues/35311&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;把这些 query 结果放到 schema_cache.yml 中也许是个可以接受的方案 &lt;a href="https://github.com/rails/rails/pull/39077" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/pull/39077&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;参考&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/5162" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/pull/5162&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kirshatrov.com/2016/12/13/schema-cache/" rel="nofollow" target="_blank"&gt;https://kirshatrov.com/2016/12/13/schema-cache/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>piecehealth</author>
      <pubDate>Tue, 12 May 2020 12:52:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/39858</link>
      <guid>https://ruby-china.org/topics/39858</guid>
    </item>
    <item>
      <title>[过春节长知识] Rails log 的小知识</title>
      <description>&lt;p&gt;春节期间大家闲着也是闲着，不如一起分享一些知识&lt;del&gt;来准备年后的面试&lt;/del&gt;。&lt;/p&gt;

&lt;p&gt;分享的原则是尽量简单&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;写太详细没人看。&lt;/li&gt;
&lt;li&gt;大家都写简单点，鼓励更多的人参与分享。&lt;/li&gt;
&lt;li&gt;写的简单就看得人多，懂得人少，有利于形成讨论，增加大家参与感。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Request ID 实现"&gt;Request ID 实现&lt;/h2&gt;
&lt;p&gt;Rails 生产环境的 log 都会带着当前 request id，对 debug 十分有帮助。如&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[582284e9-e137-4470-a61c-4c55cdbbc408] Started GET "/pages/index" for ::1 at 2020-01-28 14:38:36 +0800
[582284e9-e137-4470-a61c-4c55cdbbc408] Processing by PagesController#index as HTML
[582284e9-e137-4470-a61c-4c55cdbbc408]   Rendering pages/index.html.erb within layouts/application
[582284e9-e137-4470-a61c-4c55cdbbc408]   Rendered pages/index.html.erb within layouts/application (Duration: 0.0ms | Allocations: 4)
[582284e9-e137-4470-a61c-4c55cdbbc408] Completed 200 OK in 10ms (Views: 8.9ms | ActiveRecord: 0.0ms | Allocations: 4276)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;这个功能是靠&lt;code&gt;config/production.rb&lt;/code&gt;中的&lt;code&gt;config.log_tags = [ :request_id ]&lt;/code&gt;来开启&lt;/li&gt;
&lt;li&gt;Reqeust ID 靠中间件&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/actionpack/lib/action_dispatch/middleware/request_id.rb" rel="nofollow" target="_blank" title=""&gt;ActionDispatch::RequestId&lt;/a&gt;产生，来自请求上游（load balancer, firewall, webserver）赋给请求头&lt;code&gt;headers['X-Request-Id']&lt;/code&gt;的值，如果没有则自己生成一个 uuid。&lt;/li&gt;
&lt;li&gt;另一个中间件&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/railties/lib/rails/rack/logger.rb" rel="nofollow" target="_blank" title=""&gt;Rails::Rack::Logger&lt;/a&gt;会读到第 1 步的&lt;code&gt;config.log_tags&lt;/code&gt;，利用&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/activesupport/lib/active_support/tagged_logging.rb" rel="nofollow" target="_blank" title=""&gt;ActiveSupport::TaggedLogging&lt;/a&gt;来把&lt;code&gt;request_id&lt;/code&gt;加到当前请求的每一行 log 输出。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ActiveSupport::TaggedLogging"&gt;ActiveSupport::TaggedLogging&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ActiveSupport::TaggedLogging&lt;/code&gt;很简单，看一眼文档就会使用了&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Wraps any standard Logger object to provide tagging capabilities.
#
#   logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
#   logger.tagged('BCX') { logger.info 'Stuff' }                            # Logs "[BCX] Stuff"
#   logger.tagged('BCX', "Jason") { logger.info 'Stuff' }                   # Logs "[BCX] [Jason] Stuff"
#   logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
#
# This is used by the default Rails.logger as configured by Railties to make
# it easy to stamp log lines with subdomains, request ids, and anything else
# to aid debugging of multi-user production applications.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="development环境下Rails的log是输出到STDOUT的吗？"&gt;development 环境下 Rails 的 log 是输出到 STDOUT 的吗？&lt;/h2&gt;
&lt;p&gt;看到&lt;code&gt;ActiveSupport::TaggedLogging&lt;/code&gt;的文档，迫不及待打开一个&lt;code&gt;rails console&lt;/code&gt;试一下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.6.5 :001 &amp;gt; Rails.logger.tagged("武汉") { Rails.logger.info("加油") }
加油
 =&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期望输出是&lt;code&gt;[武汉]加油&lt;/code&gt;，但是实际只有&lt;code&gt;加油&lt;/code&gt;，但是&lt;code&gt;tag&lt;/code&gt;并没有白加。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; $ tail -1 log/development.log
[武汉] 加油
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 Rails 在 development 环境下实际是输出到&lt;code&gt;log/development.log&lt;/code&gt;，在&lt;code&gt;rails console&lt;/code&gt;里面能看到&lt;code&gt;Rails.logger&lt;/code&gt;输出，实际是因为启动&lt;code&gt;rails console&lt;/code&gt;的时候，&lt;code&gt;active_record&lt;/code&gt;让&lt;code&gt;Rails.logger&lt;/code&gt;的输出复制一份并输出到 STDERR 中 (&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/railtie.rb#L58-L61" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/railtie.rb#L58-L61&lt;/a&gt;)。&lt;/p&gt;

&lt;p&gt;同理，&lt;code&gt;rails server&lt;/code&gt;能看到 log 输出，也是因为&lt;code&gt;Rails.logger&lt;/code&gt;的输出被复制并输出到 STDOUT 中 (&lt;a href="https://github.com/rails/rails/blob/bf625f7fecabbcda22b388e088ad5c29016b2385/railties/lib/rails/commands/server/server_command.rb#L76-L86" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/bf625f7fecabbcda22b388e088ad5c29016b2385/railties/lib/rails/commands/server/server_command.rb#L76-L86&lt;/a&gt;)。&lt;/p&gt;

&lt;p&gt;有时我们会启动一个进程，例如&lt;code&gt;resque worker&lt;/code&gt;，会发现开这个进程的终端一点 log 输出也没有，就是上面的原因所致：log 本来就不会往 STDOUT 写。如果想在 STDOUT 中看到 log，参考上面代码，复制&lt;code&gt;Rails.logger&lt;/code&gt;输出到 STDOUT 即可&lt;/p&gt;
&lt;h2 id="请求结束时最后一条log是怎么生成的"&gt;请求结束时最后一条 log 是怎么生成的&lt;/h2&gt;
&lt;p&gt;当一个请求结束时，默认会有一条汇总的 log&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Completed 200 OK in 10ms (Views: 8.9ms | ActiveRecord: 0.0ms | Allocations: 4276)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大部分中小 Rails 应用，&lt;code&gt;Views&lt;/code&gt;与&lt;code&gt;ActiveRecord&lt;/code&gt;的耗时是大头。但是随着我们业务发展，规模增大，我们应用里耗时部分越来越多，例如 redis 读写，rpc 请求，甚至有同步的外部 http 请求，我们也可以把这些部分的耗时一起写在最后一条 log 中。&lt;/p&gt;

&lt;p&gt;由于这条 log 是一个发生在请求中（最后）的 log，所以肯定是&lt;code&gt;ActionController&lt;/code&gt;模块生成的。(&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/actionpack/lib/action_controller/log_subscriber.rb#L32-L34" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/v6.0.2.1/actionpack/lib/action_controller/log_subscriber.rb#L32-L34&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;各个耗时项目的汇总输出，是下面代码完成的&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;additions&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_process_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s2"&gt;"Completed &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&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="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_STATUS_CODES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms"&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;" (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;additions&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="s2"&gt;" | "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而&lt;code&gt;ActionController::Base.log_process_action&lt;/code&gt;本身的实现只有&lt;code&gt;Views&lt;/code&gt;的耗时，但是我们可以通过 overwrite &lt;code&gt;ActionController::Base.log_process_action&lt;/code&gt;来添加我们想输出的指标。实际上&lt;code&gt;ActiveRecord&lt;/code&gt;就是通过这个方法把自己的耗时加到最后一条 log 里的&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/railties/controller_runtime.rb" rel="nofollow" target="_blank" title=""&gt;ActiveRecord::Railties::ControllerRuntime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/railtie.rb#L206-L211" rel="nofollow" target="_blank" title=""&gt;ActiveRecord::Railtie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;参照&lt;code&gt;ActiveRecord::Railties::ControllerRuntime&lt;/code&gt;，我们可以把其他指标也输出到最后一条 log 中……如果我们能得到这些指标。&lt;/p&gt;
&lt;h2 id="Active Support Instrumentation"&gt;Active Support Instrumentation&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/active_support_instrumentation.html" rel="nofollow" target="_blank" title=""&gt;Active Support Instrumentation&lt;/a&gt; 是中级以上的 Rails 工程师不能不知道的 API。
在开发环境中，我们能看到每个 view render 的时间，每条 database query 执行的时间都是得益于这套 API，是我们在不侵入业务代码的情况下可以得到很多指标，指标包括执行时间。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/active_support_instrumentation.html" rel="nofollow" target="_blank" title=""&gt;Rails guide&lt;/a&gt;介绍的很清楚，这里就不多介绍。接上面的章节，我们只要实现自己的&lt;code&gt;ActiveSupport::Notifications.instrument&lt;/code&gt;跟&lt;code&gt;ActiveSupport::Notifications.subscribe&lt;/code&gt;方法，就可以得到我们想要的指标，配合&lt;code&gt;around_action&lt;/code&gt;或者&lt;code&gt;middleware&lt;/code&gt;将汇总的指标写到&lt;code&gt;ActionController::Base.log_process_action&lt;/code&gt;方法的返回，就可以在自定义最后一条 log 的输出了。&lt;/p&gt;</description>
      <author>piecehealth</author>
      <pubDate>Tue, 28 Jan 2020 18:10:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/39468</link>
      <guid>https://ruby-china.org/topics/39468</guid>
    </item>
    <item>
      <title>[上海] Sap 招聘 Ruby / 前端 / Full stack 工程师</title>
      <description>&lt;p&gt;&lt;strong&gt;SAP Jam – Senior Software Developer - Shanghai&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SAP started in 1972 as a team of five colleagues with a desire to do something new. Together, they changed enterprise software and reinvented how business was done. Today, as a market leader in enterprise application software, we remain true to our roots. That’s why we engineer solutions to fuel innovation, foster equality and spread opportunity for our employees and customers across borders and cultures.&lt;/p&gt;

&lt;p&gt;SAP values the entrepreneurial spirit, fostering creativity and building lasting relationships with our employees. We know that a diverse and inclusive workforce keeps us competitive and provides opportunities for all. We believe that together we can transform industries, grow economics, lift up societies and sustain our environment. Because it’s the best-run businesses that make the world run better and improve people’s lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PURPOSE AND OBJECTIVES&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will be working as a Senior Developer in a Scrum team to work on SAP Jam. You will help to build, scale, integrate and extend SAP Jam- SAP's enterprise social networking product. SAP Jam brings together several types of social collaboration including social networking, collaboration with external participants (customers, recruits, partners, vendors, suppliers), structured collaboration for problem solving  (business tools to strategize, rank items, enabling groups to weigh in on options, etc.), and business process integration. We emphasize teamwork and a trust-based working model. Collaboration with other teams in an international environment will be a regular part of your work. Wherever possible, we offer opportunities for professional development and coaching. As we are not only designing and developing our offerings but also run them, you should be open for the mindset of DevOps and SRE.&lt;/p&gt;

&lt;p&gt;One part, SAP Jam team in Shanghai will focus on creating developer and integration toolkits for SAP Jam on the SAP Cloud Platform, working on Jam APIs and integration widgets, and working on end-to-end integrations with various integration partners. The second is Platform: DevOps, Security (including Authentication and Authorization), Administrative Functionality, Performance Engineering and Platform Architecture. We are a team for people who love systems and back-end work, but also step into front-end work from time to time. Because of the nature of the topics, we work closely with many other teams both inside and outside SAP.&lt;/p&gt;

&lt;p&gt;The SAP Jam team works in a fast-paced engineering environment working with current technologies. The team has strong engineers to learn from and share knowledge with. We also have the satisfaction of working on a product that has many actual users and so each developer’s contributions can have a tangible and noticeable impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXPECTATIONS AND TASKS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drive, design, and develop new features by working with Product Management, User Experience, and Software Testing to elaborate on the features, estimate task time, and then quickly write high-quality code for complex features.
Work in a scrum development project model, with a daily team scrum meeting, and a weekly production deployment
Participate actively in requirements gathering, design, and code reviews
Work with other Jam Engineering teams and Operations teams located at other SAP locations such as Vancouver, Shanghai, Palo Alto, India, Germany and elsewhere.
Be excited about keeping up with technology and changing tools, environments, and an agile development process where weekly deployments across multiple data centers world-wide is the norm.&lt;/p&gt;

&lt;p&gt;Enjoy a strong technical environment where all members of the Jam engineering team - including management, are close to the code, and making technical contributions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDUCATION AND QUALIFICATIONS / SKILLS AND COMPETENCIES&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Required experience and qualifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bachelor’s degree in Computer Science or a related technical field&lt;/li&gt;
&lt;li&gt;The ability to solve technical computer problems by writing actual working code; this is very much a hands-on position.&lt;/li&gt;
&lt;li&gt;The ability to learn new technologies&lt;/li&gt;
&lt;li&gt;Good written and verbal communication skills in English&lt;/li&gt;
&lt;li&gt;Interest in working directly with colleagues across sites&lt;/li&gt;
&lt;li&gt;Easy to work with and adaptable to change&lt;/li&gt;
&lt;li&gt;Initiative and the desire to improve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Desirable experience and qualifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby on Rails, Python, JavaScript/HTML/CSS, Java, Node.js, Linux and shell scripting, SQL databases, Web programming, Test-driven development, version control systems (GIT)&lt;/li&gt;
&lt;li&gt;Evidence of excellence in some capacity (could be via personal projects, exceptional success in some endeavor, etc.)&lt;/li&gt;
&lt;li&gt;Interest in technology as evidenced by knowledge of various forward-facing technologies, personal projects, contributions to open source etc.&lt;/li&gt;
&lt;li&gt;Interest in advanced programming languages&lt;/li&gt;
&lt;li&gt;Graduate degree&lt;/li&gt;
&lt;li&gt;SAP Cloud platform development experience will be a plus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;WORK EXPERIENCE&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6+ years of experience in a development organization. &lt;/li&gt;
&lt;li&gt;Working knowledge in REST/oData/JSon&lt;/li&gt;
&lt;li&gt;Knowledge in cloud scale distributed systems&lt;/li&gt;
&lt;li&gt;Willingness to take responsibility, drive new developments, and work creatively on challenging and groundbreaking development tasks in accordance with the highest technical standards, plus a high level of commitment, team-spirit, flexibility, and initiative&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JD 上的要求都可以适当放宽。&lt;/p&gt;

&lt;p&gt;福利环境参照 &lt;a href="https://ruby-china.org/topics/34939" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/34939&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;简历投递 piecehealth@sina.com&lt;/p&gt;</description>
      <author>piecehealth</author>
      <pubDate>Wed, 19 Sep 2018 16:41:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/37512</link>
      <guid>https://ruby-china.org/topics/37512</guid>
    </item>
  </channel>
</rss>
