<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>huacnlee (李华顺)</title>
    <link>https://ruby-china.org/huacnlee</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Ruby 3.3's YJIT Runs Shopify's Production Code 15% Faster</title>
      <description>&lt;p&gt;&lt;a href="https://railsatscale.com/2023-09-18-ruby-3-3-s-yjit-runs-shopify-s-production-code-15-faster/" rel="nofollow" target="_blank"&gt;https://railsatscale.com/2023-09-18-ruby-3-3-s-yjit-runs-shopify-s-production-code-15-faster/&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Wed, 20 Sep 2023 09:23:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/43345</link>
      <guid>https://ruby-china.org/topics/43345</guid>
    </item>
    <item>
      <title>Ruby China Gems 镜像变更公告</title>
      <description>&lt;p&gt;鉴于最近不少人反馈 Ruby China 的 Gems 镜像一直不太稳定，今天做了一些架构调整，为动态内容 API 增加了境外节点，以降低回源带来的稳定性问题。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gems.ruby-china.com" rel="nofollow" target="_blank"&gt;https://gems.ruby-china.com&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;目前测试下来速度稳定，比之前有较大提升，大家可以尝试看看，有问题这里回复反馈。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dig gems.ruby-china.com

&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
gems.ruby-china.com.    267 IN  CNAME   gems.ruby-china.com.cdn.dnsv1.com.
gems.ruby-china.com.cdn.dnsv1.com. 600 IN CNAME 21gxf88f.slt.sched.intlscdn.com.
21gxf88f.slt.sched.intlscdn.com. 180 IN A   43.152.14.32
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="部署架构图"&gt;部署架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/huacnlee/8c75f700-af34-426f-831b-6629f61c921f.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="变更内容"&gt;变更内容&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;在境外 &lt;a href="https://cloud.tencent.com/product/cdn" rel="nofollow" target="_blank" title=""&gt;QCloud CDN&lt;/a&gt; 上新增了节点，用于承接主要的动态 API 请求。&lt;/li&gt;
&lt;li&gt;将可以长期缓存的文件 &lt;code&gt;.gem&lt;/code&gt; 以 &lt;code&gt;302&lt;/code&gt; 的方式跳转到 &lt;code&gt;index.ruby-china.com&lt;/code&gt; 走国内 &lt;a href="https://www.upyun.com/products/cdn" rel="nofollow" target="_blank" title=""&gt;UpYun CDN&lt;/a&gt; 提升下载速度。&lt;/li&gt;
&lt;li&gt;调整了一些缓存策略，尽量遵循 rubygems.org 给出的 &lt;code&gt;cache-control&lt;/code&gt; 规则。&lt;/li&gt;
&lt;li&gt;基于 QCloud 提供的预热功能，每 30 分钟会提前预热一些文件（例如 &lt;code&gt;/versions&lt;/code&gt;）以确保大家访问的时候这些文件不会回源，提升速度。&lt;/li&gt;
&lt;li&gt;2023.9.9 - 新增全球 CDN 节点，构建与境外节点之前，以减少国内网络直连香港可能发生的丢包问题（CDN to CDN 跨境更稳定）。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;以上暂时这么调整，如有变化我会持续这里更新。&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Mon, 05 Jun 2023 17:50:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/43144</link>
      <guid>https://ruby-china.org/topics/43144</guid>
    </item>
    <item>
      <title>Ruby 3.2 带来的 YJIT 性能提升提升 ~40%</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;Overall YJIT is 38.9% faster than interpreted CRuby! 
On Railsbench specifically, YJIT is 38.9% faster than CRuby!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/huacnlee/6dbf797a-5f4a-4e21-aaf1-94da5a4cf9ef.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/huacnlee/5f516a5b-6627-4964-bdbf-bb711979ae3a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://speed.yjit.org" rel="nofollow" target="_blank"&gt;https://speed.yjit.org&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="开始使用 Ruby 3.2"&gt;开始使用 Ruby 3.2&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;brew upgrade ruby-build
rbenv &lt;span class="nb"&gt;install &lt;/span&gt;3.2.0-rc1
rbenv global 3.2.0-rc1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后设置 ENV 开启 YJIT，设置到 &lt;code&gt;.bash_profile&lt;/code&gt; 或其他地方&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;RUBY_YJIT_ENABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后检查 YJIT 是否开启，启动 irb&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;RubyVM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;YJIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>huacnlee</author>
      <pubDate>Thu, 22 Dec 2022 13:31:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/42801</link>
      <guid>https://ruby-china.org/topics/42801</guid>
    </item>
    <item>
      <title>Towards Ruby 4 JIT / RubyKaigi 2022</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/huacnlee/88e3cc05-739f-4457-b74c-57eda4d03dd8.jpeg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://speakerdeck.com/k0kubun/rubykaigi-2022" rel="nofollow" target="_blank"&gt;https://speakerdeck.com/k0kubun/rubykaigi-2022&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Wed, 14 Sep 2022 10:20:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/42649</link>
      <guid>https://ruby-china.org/topics/42649</guid>
    </item>
    <item>
      <title>Ruby on Rust - 用 Rust 编写 RubyGem Extension 提升 600% 的性能</title>
      <description>&lt;p&gt;最近我发现 RubyGems 以及合并了 Cargo builder 支持的 PR，也就意味着，我们除了可以用 C 来写 RubyGem 扩展之外，也可以用 Rust 了。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rubygems/rubygems/pull/5175" rel="nofollow" target="_blank"&gt;https://github.com/rubygems/rubygems/pull/5175&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;为了吃螃蟹，我用 autocorrect 的项目测试了一下，已经完成集成上 &lt;a href="https://github.com/huacnlee/auto-correct/runs/6111556332" rel="nofollow" target="_blank" title=""&gt;CI Result&lt;/a&gt; 以及跑同，集成好以后，我一次性删除了大量之前的 Rust 实现的代码。&lt;/p&gt;

&lt;p&gt;目前在折腾 Cross Compile 打包发布的事情。&lt;/p&gt;

&lt;p&gt;详细内容，可以作为参考：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/huacnlee/auto-correct/pull/17" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/auto-correct/pull/17&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="如何编写 Rust 扩展"&gt;如何编写 Rust 扩展&lt;/h3&gt;
&lt;p&gt;在 RubyGems 的 PR 里面给到的 &lt;a href="https://github.com/rubygems/rubygems/tree/f91ee8a6465e220f61795d539f7e901b224561fb/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example" rel="nofollow" target="_blank" title=""&gt;例子&lt;/a&gt; 实现方法比较初级。我实际走下来看，这样还不够。我们需要原来 Gem 的 &lt;code&gt;extconf.rb&lt;/code&gt; 那套流程来自动化发布。&lt;/p&gt;

&lt;p&gt;创建类似这样的目录结构：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── Gemfile
├── Rakefile
├── auto-correct.gemspec
├── ext
│&amp;nbsp;&amp;nbsp; └── autocorrect
│&amp;nbsp;&amp;nbsp;     ├── Cargo.lock
│&amp;nbsp;&amp;nbsp;     ├── Cargo.toml
│&amp;nbsp;&amp;nbsp;     ├── extconf.rb
│&amp;nbsp;&amp;nbsp;     └── src
│&amp;nbsp;&amp;nbsp;         └── lib.rs
├── lib
│&amp;nbsp;&amp;nbsp; ├── auto-correct
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── version.rb
│&amp;nbsp;&amp;nbsp; └── auto-correct.rb

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cargo.toml 需要引入 &lt;a href="https://crates.io/crates/rb-sys" rel="nofollow" target="_blank" title=""&gt;rb-sys&lt;/a&gt;，我们需要用它来定义 Ruby Module, Function, Const 之类的东西，这个类似 C Extension 里面相同的方法。&lt;/p&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"autocorrect"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.5.11"&lt;/span&gt;

&lt;span class="nn"&gt;[lib]&lt;/span&gt;
&lt;span class="py"&gt;crate-type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"cdylib"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;rb-sys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"link-ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ruby-static"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="py"&gt;autocorrect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.5.11"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;src/lib.rs&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt; &lt;span class="n"&gt;rb_sys&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rb_sys&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="n"&gt;rb_define_module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rb_define_module_function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rb_string_value_cstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rb_utf8_str_new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ffi&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;CStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;c_long&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[inline]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;cstr_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;c_char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;CStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.to_string_lossy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.into_owned&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;pub_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_klass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 需要做类型转换，将 CString 转换成 Rust String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ruby_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cstr_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rb_string_value_cstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// 调用 autocorrect (Rust 那个 crate) 的 format 方法（名字有点混淆注意区分）&lt;/span&gt;
    &lt;span class="c1"&gt;// https://rubygems.org/gems/autocorrect&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;autocorrect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ruby_string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;c_long&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 再把 Rust String 类型转换成 CString&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result_cstring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&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;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;rb_utf8_str_new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_cstring&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 这个是 Ruby Extension 的标准流程，你需要定义一个 `Init_xxxx` 的初始化方法。&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(non_snake_case)]&lt;/span&gt;
&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;Init_autocorrect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&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="s"&gt;"AutoCorrect"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;rb_define_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fn_format_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CString&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="s"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;format_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;transmute&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pub_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 定义 AutoCorrect.format 方法&lt;/span&gt;
        &lt;span class="nf"&gt;rb_define_module_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn_format_name&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format_callback&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义 &lt;code&gt;extconf.rb&lt;/code&gt;，本身 Ruby 有提供 mkmf，但我们额外需要 &lt;a href="https://rubygems.org/gems/rb_sys" rel="nofollow" target="_blank" title=""&gt;rb_sys&lt;/a&gt; 提供的改进，所以项目需要增加依赖：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_runtime_dependency&lt;/span&gt; &lt;span class="s2"&gt;"rb_sys"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: 截止目前 rb_sys v0.1.0 编译还有 Bug，我已经提 &lt;a href="https://github.com/oxidize-rb/rb-sys/pulls?q=is%3Apr+is%3Aclosed+author%3Ahuacnlee" rel="nofollow" target="_blank" title=""&gt;PR 改进&lt;/a&gt; 了，等发布新版本。所以我那个 PR 里面用的是我自己临时发布的 &lt;a href="https://rubygems.org/gems/rb_sys1" rel="nofollow" target="_blank" title=""&gt;rb_sys1&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"mkmf"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rb_sys/mkmf"&lt;/span&gt;

&lt;span class="n"&gt;create_rust_makefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"auto-correct/autocorrect"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rakefile 改动&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rubygems"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/setup"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/gem_tasks"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rake/testtask"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rake/extensiontask"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler"&lt;/span&gt;

&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ExtensionTask&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="s2"&gt;"autocorrect"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lib_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lib/auto-correct"&lt;/span&gt;
  &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*.{rs,toml}"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样你就有 &lt;code&gt;rake compile&lt;/code&gt; 命令了，执行它，会将 Rust 代码编译成一个 &lt;code&gt;autocorrect.bundle&lt;/code&gt; 的文件，放到 &lt;code&gt;lib/auto-correct&lt;/code&gt; 目录，于是我们就可以在 Ruby 里面调用 Rust 函数了。&lt;/p&gt;
&lt;h2 id="最后效果演示和意义"&gt;最后效果演示和意义&lt;/h2&gt;
&lt;p&gt;之前 Ruby 实现的相同逻辑与 Rust 实现的版本基本上是完全一致的（正则、规则、处理流程基本完全），甚至在 Rust 实现的 AutoCorrect 版本里面后面我又增加了更多的细节。&lt;/p&gt;
&lt;h3 id="Rust 实现的性能结果"&gt;Rust 实现的性能结果&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warming up --------------------------------------
     format 50 chars     1.886k i/100ms
    format 100 chars     1.060k i/100ms
    format 400 chars   342.000  i/100ms
         format_html    85.000  i/100ms
Calculating -------------------------------------
     format 50 chars     18.842k (± 1.5%) i/s -     94.300k in   5.005815s
    format 100 chars     10.357k (± 1.8%) i/s -     51.940k in   5.016770s
    format 400 chars      3.336k (± 2.2%) i/s -     16.758k in   5.026230s
         format_html    839.761  (± 2.1%) i/s -      4.250k in   5.063225s
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="用上面方式集成 Rust 实现以后"&gt;用上面方式集成 Rust 实现以后&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warming up --------------------------------------
     format 50 chars    12.376k i/100ms
    format 100 chars     6.918k i/100ms
    format 400 chars     1.944k i/100ms
         format_html   617.000  i/100ms
Calculating -------------------------------------
     format 50 chars    120.884k (± 4.8%) i/s -    606.424k in   5.031500s
    format 100 chars     67.903k (± 3.9%) i/s -    345.900k in   5.102803s
    format 400 chars     19.024k (± 2.9%) i/s -     95.256k in   5.011560s
         format_html      6.004k (± 2.3%) i/s -     30.233k in   5.038348s
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;🎉 性能提升 6 - 10 倍&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="参考内容"&gt;参考内容&lt;/h2&gt;
&lt;p&gt;目前 Rust 社区里面似乎还没有太多 Rust 集成的例子，如果也你想这么做，可以阅读以下 auto-correct 这个 Gem 这个 PR 的变更内容。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/huacnlee/auto-correct/pull/17" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/auto-correct/pull/17&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 22 Apr 2022 16:57:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/42336</link>
      <guid>https://ruby-china.org/topics/42336</guid>
    </item>
    <item>
      <title>VS Code 插件 - AutoCorrect 自动纠正代码中的中英文空格写法</title>
      <description>&lt;p&gt;基于 Rust 编写的 CLI 工具，用于自动纠正文案，给 CJK（中文、日语、韩语）与英文混写的场景，补充正确的空格，同时尝试以安全的方式自动纠正标点符号等等。&lt;/p&gt;

&lt;p&gt;除了纯文本的自动纠正以外，AutoCorrect 基于 Parser 的方式对各种类型源代码文件支持，能自动识别文件名，并准确找到字符串、注释做自动纠正。&lt;/p&gt;

&lt;p&gt;基本能支持所有的语言，实现逻辑简单，基于语言的方言做了 Parser，只处理字符串、注释部分，其他的忽略。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AutoCorrect 的方案最早于 2013 年 出现于 Ruby China 的项目，并逐步完善规则细节，当前准确率较高（级少数异常情况），你可以放心用来辅助你完整自动纠正动作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://ruby-china.org/topics/20489" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/20489&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本来最早的时候，只是写了库，用来解决 Ruby, Go, Rust 项目里面使用，后面逐渐发现，深入一点实现，可以搞成一个 fmt 工具，自动处理，加上当前 VS Code 插件的能力，可以搞一个保存的时候自动纠正。&lt;/p&gt;
&lt;h2 id="VS Code 插件"&gt;VS Code 插件&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=huacnlee.autocorrect" rel="nofollow" target="_blank"&gt;https://marketplace.visualstudio.com/items?itemName=huacnlee.autocorrect&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AutoCorrect 设计的原则是类似 Gofmt 默认开启，在 VS Code 保存的时候会执行格式化。&lt;/p&gt;
&lt;h3 id="效果演示"&gt;效果演示&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/5518/123918476-7ed38a00-d9b6-11eb-91f7-6af7a9c49a3e.gif" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="CLI 工具安装"&gt;CLI 工具安装&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/huacnlee/autocorrect" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/autocorrect&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;希望这个小工具能帮到你改善项目中的文档、注释、文案，也期望能帮助改善中文开源社区项目的文案细节。&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Thu, 01 Jul 2021 21:56:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/41430</link>
      <guid>https://ruby-china.org/topics/41430</guid>
    </item>
    <item>
      <title>Standard - Ruby 风格标准</title>
      <description>&lt;p&gt;以前大家可能都在用 Rubocop，但实际上 Rubocop 没有标准的，并且 Rubycop 默认的规则还特别不受社区待见，比如 &lt;a href="https://github.com/rails/rails/blob/main/.rubocop.yml" rel="nofollow" target="_blank" title=""&gt;Rails&lt;/a&gt;，绝大多数项目都会又一个自己的 &lt;code&gt;.rubocop.yml&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我也是在我的各种项目里面都有单独的 &lt;code&gt;.rubocop.yml&lt;/code&gt; 来覆盖默认的 Rubocop 规则，尤其是其中“单引号”规则，及其讨厌。&lt;/p&gt;

&lt;p&gt;关于单引号 or 双引号的问题，也有过 
&lt;a href="https://github.com/rubocop/rubocop/issues/5306" rel="nofollow" target="_blank" title=""&gt;Issue 讨论&lt;/a&gt;，作者和主要贡献者认为此项依然有争议，于是问题搁置的。&lt;/p&gt;

&lt;p&gt;实际上从 &lt;a href="https://github.com/rubocop/rubocop/issues/5306#issuecomment-354449447" rel="nofollow" target="_blank" title=""&gt;统计情况&lt;/a&gt; 来看，社区的著名项目几乎（也包括各种热门 Gem 比如：Devise、Doorkeeper、CarrierWave 以及 Rails）他们都在使用双引号。&lt;/p&gt;

&lt;p&gt;于是有人认为我们可能需要一套标准了。&lt;/p&gt;
&lt;h2 id="Standard Ruby 1.0"&gt;Standard Ruby 1.0&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://blog.testdouble.com/posts/2021-03-04-announcing-standard-ruby-1.0/" rel="nofollow" target="_blank"&gt;https://blog.testdouble.com/posts/2021-03-04-announcing-standard-ruby-1.0/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Matz 还亲自发帖推荐&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/huacnlee/0a08884f-9add-4f39-8f11-b7bf9cdb16cf.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/yukihiro_matz/status/1367646746650628098" rel="nofollow" target="_blank"&gt;https://twitter.com/yukihiro_matz/status/1367646746650628098&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="项目地址"&gt;项目地址&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/testdouble/standard" rel="nofollow" target="_blank"&gt;https://github.com/testdouble/standard&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="重点特性"&gt;重点特性&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;不需要配置，以后都是统一的标准。&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 spaces – for indentation&lt;/li&gt;
&lt;li&gt;Double quotes for string literals - because pre-committing to whether you'll need interpolation in a string slows people down&lt;/li&gt;
&lt;li&gt;1.9 hash syntax - When all the keys in a hash literal are symbols, Standard enforces Ruby 1.9's {hash: syntax}&lt;/li&gt;
&lt;li&gt;Braces for single-line blocks - Require {/} for one-line blocks, but allow either braces or do/end for multiline blocks. Like using do/end for multiline blocks? Prefer {/} when chaining? A fan of expressing intent with Jim Weirich's semantic block approach? Standard lets you do you!&lt;/li&gt;
&lt;li&gt;Leading dots on multi-line method chains - chosen for these reasons.&lt;/li&gt;
&lt;li&gt;Spaces inside blocks, but not hash literals - In Ruby, the { and } characters do a lot of heavy lifting. To visually distinguish hash literals from blocks, Standard enforces that (like arrays), no leading or trailing spaces be added to pad hashes
And a good deal more&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="效果演示"&gt;效果演示&lt;/h3&gt;
&lt;p&gt;我用 Homeland 试了一下，下面是一些特别变化的地方：&lt;/p&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-        render json: { ok: 1 }
&lt;/span&gt;&lt;span class="gi"&gt;+        render json: {ok: 1}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{}&lt;/code&gt; 在 Hash 的时候，将会去掉左右的空格，这个场景，可能我需要一段时间适应，我们在 Go 里面也是左右带空格的，它这么定义估计是保持和 &lt;code&gt;[]&lt;/code&gt;、&lt;code&gt;()&lt;/code&gt; 一致。&lt;/p&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- scope :popular, -&amp;gt; {where("likes_count &amp;gt; 5")}
&lt;/span&gt;&lt;span class="gi"&gt;+ scope :popular, -&amp;gt; { where("likes_count &amp;gt; 5") }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{}&lt;/code&gt; 在 Block 的场景，左右空格依然还保留的。&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 05 Mar 2021 14:15:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/40997</link>
      <guid>https://ruby-china.org/topics/40997</guid>
    </item>
    <item>
      <title>一篇很详细的 Ruby 3.0 新特性介绍</title>
      <description>&lt;p&gt;&lt;a href="https://bigbinary.com/blog/ruby-3-features" rel="nofollow" target="_blank"&gt;https://bigbinary.com/blog/ruby-3-features&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 25 Dec 2020 23:36:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/40749</link>
      <guid>https://ruby-china.org/topics/40749</guid>
    </item>
    <item>
      <title>Ruby with Jemalloc Docker 镜像</title>
      <description>&lt;p&gt;今天花点时间基于 Docker 官方的 Ruby 打包了一个带有 Jemalloc 的 Ruby Docker Image&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hub.docker.com/r/rubychina/ruby" rel="nofollow" target="_blank"&gt;https://hub.docker.com/r/rubychina/ruby&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;This is a fork of &lt;a href="https://github.com/docker-library/ruby" rel="nofollow" target="_blank" title=""&gt;docker-library/ruby&lt;/a&gt; built with jemalloc. &lt;/p&gt;
&lt;h3 id="当前已经打包的版本:"&gt;当前已经打包的版本：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/rubychina/ruby/tags?page=1&amp;amp;name=3.0.0-rc1-slim-buster" rel="nofollow" target="_blank" title=""&gt;3.0.0-rc1-slim-buster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/rubychina/ruby/tags?page=1&amp;amp;name=3.0.0-rc1-buster" rel="nofollow" target="_blank" title=""&gt;3.0.0-rc1-buster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/rubychina/ruby/tags?page=1&amp;amp;name=2.7.2-slim-buster" rel="nofollow" target="_blank" title=""&gt;2.7.2-slim-buster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/rubychina/ruby/tags?page=1&amp;amp;name=2.7.2-buster" rel="nofollow" target="_blank" title=""&gt;2.7.2-buster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果有需要更新，可以往这个地址提及 PR&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ruby-china/ruby" rel="nofollow" target="_blank"&gt;https://github.com/ruby-china/ruby&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Tue, 22 Dec 2020 21:08:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/40730</link>
      <guid>https://ruby-china.org/topics/40730</guid>
    </item>
    <item>
      <title>Turbolinks 引入 prefetch 让你的网站速度起飞</title>
      <description>&lt;p&gt;本文分享 Turbolinks 的扩展，用于加速网页访问。&lt;/p&gt;
&lt;h2 id="前言 / 背景"&gt;前言 / 背景&lt;/h2&gt;
&lt;p&gt;最近发现了 &lt;a href="http://instantclick.io" rel="nofollow" target="_blank" title=""&gt;InstantClick&lt;/a&gt; 这种对网页预加载的小技巧，这种方式能有效的改进网站的访问速度。&lt;/p&gt;

&lt;p&gt;大概原理是当用户鼠标经过链接的时候，会提前用 Ajax 的方式将网页预先加载好存入 cache，等用户点击的时候，用之前的 cache 直接渲染。&lt;/p&gt;

&lt;p&gt;于是我还发了 Twitter 说这个事情：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/huacnlee/status/1310180334059749378" rel="nofollow" target="_blank"&gt;https://twitter.com/huacnlee/status/1310180334059749378&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/huacnlee/status/1310209510514909184" rel="nofollow" target="_blank"&gt;https://twitter.com/huacnlee/status/1310209510514909184&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rails 内置的 Turbolinks 实际上有类似的 cache 机制，在用户来回点击页面的时候会利用 cache 提前渲染，只是没有在用户鼠标 hover 的时候进行预处理。&lt;/p&gt;

&lt;p&gt;查了查，发现 Turblinks 的 Issue 里面也有讨论这个 &lt;a href="https://github.com/turbolinks/turbolinks/issues/313" rel="nofollow" target="_blank" title=""&gt;turbolinks/turbolinks#313&lt;/a&gt;，仔细看找到了一个实现 &lt;a href="https://gist.github.com/hopsoft/ab500a3b584e2878c83137cb539abb32" rel="nofollow" target="_blank" title=""&gt;参考&lt;/a&gt;，于是封装了一下，并做了改进实现了一个 Turbolinks 的扩展。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/huacnlee/turbolinks-prefetch" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/turbolinks-prefetch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同时我在实现里面额外调整了 Turbolinks 的 &lt;code&gt;visit&lt;/code&gt; 动作，如果已经有 prefetch 的动作，会直接 render 不会再次请求页面。&lt;/p&gt;

&lt;p&gt;如你所见，目前 Ruby China 已经开启了这个功能（香港服务器），在 prefetch 产生效果的时候，基本上页面打开犹如本地网页。&lt;/p&gt;
&lt;h2 id="Turbolinks Prefetch 的工作机制"&gt;Turbolinks Prefetch 的工作机制&lt;/h2&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hover --&amp;gt; [prefetch] --&amp;lt;no cache&amp;gt;--&amp;gt; [XHR fetch] -&amp;gt; [Turbolinks cache.put]
              |
          &amp;lt;exist cache / in fetching&amp;gt;
              |
            ignore

click --&amp;lt;check cache&amp;gt;-- exist --&amp;gt; [isPrefetch] -&amp;gt; [Turbolinks.visit advance] ---&amp;gt; [render page]
             |                         |                 |
             |                         |                 --async-&amp;gt; [fetch background] -&amp;gt; [render if updated]
             |                         |
             |                       &amp;lt;Yes&amp;gt;
             |                         |--- [Turbolinks.visit restore] --&amp;gt; render -&amp;gt; nothing
          No cahce
             |
             ---&amp;gt; [Turbolinks.visit]
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Installation"&gt;Installation&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add turbolinks-prefetch
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用方式"&gt;使用方式&lt;/h2&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Turbolinks&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;turbolinks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Turbolinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Turbolinks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TurbolinksPrefetch&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;turbolinks-prefetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;TurbolinksPrefetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当 Prefetch 请求的时候，将会额外发送 &lt;code&gt;Purpose: prefetch&lt;/code&gt; 的 HTTP header，如果你需要特别忽略某些动作，你可以用到它。&lt;/p&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;TopicsController&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;show&lt;/span&gt;
    &lt;span class="k"&gt;if&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;"Purpose"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"prefetch"&lt;/span&gt;
      &lt;span class="c1"&gt;# 不在 prefetch 的时候更新访问量&lt;/span&gt;
      &lt;span class="vi"&gt;@topic.increment_hit&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;h3 id="对部分链接禁用 Prefetch"&gt;对部分链接禁用 Prefetch&lt;/h3&gt;
&lt;p&gt;默认情况下，Turbolinks Prefetch 将会对所有的链接开启行为。&lt;/p&gt;

&lt;p&gt;除了下面这些情况：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;非同一个网站的链接 (Host / Origin 不一样)；&lt;/li&gt;
&lt;li&gt;有新窗口打开行为的链接 &lt;code&gt;target="_blank"&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;有 &lt;code&gt;data-remote&lt;/code&gt; 属性的链接;&lt;/li&gt;
&lt;li&gt;有 &lt;code&gt;data-method&lt;/code&gt; 属性的链接;&lt;/li&gt;
&lt;li&gt;有 &lt;code&gt;data-prefetch="false"&lt;/code&gt; 属性的链接;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;应该说大多数的默认情况下，你不需要处理，类似 Rails UJS 这样的默认行为已经在 Turbolinks Prefetch 处理好了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;于是你可以这样来对部分链接禁用 prefetch：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://google.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Google&lt;span class="nt"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/topics/123"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open in new window&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/topics/123"&lt;/span&gt; &lt;span class="na"&gt;data-method=&lt;/span&gt;&lt;span class="s"&gt;"PUT"&lt;/span&gt; &lt;span class="na"&gt;data-remote&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Put&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/topics/123"&lt;/span&gt; &lt;span class="na"&gt;data-method=&lt;/span&gt;&lt;span class="s"&gt;"DELETE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/topics/123"&lt;/span&gt; &lt;span class="na"&gt;data-prefetch=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disable by directly&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;🎊 不要犹豫了，立即在你们已经有使用 Tubrolinks 的项目里面用起来吧，基本上是无缝支持。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/huacnlee/turbolinks-prefetch" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/turbolinks-prefetch&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Sat, 10 Oct 2020 00:26:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/40471</link>
      <guid>https://ruby-china.org/topics/40471</guid>
    </item>
    <item>
      <title>远程 - 寻一个全职 Rails 工程师 1 名，待遇 25K - 30K  [已招到]</title>
      <description>&lt;p&gt;帮前同事发布招聘。&lt;/p&gt;
&lt;h2 id="项目情况"&gt;项目情况&lt;/h2&gt;
&lt;p&gt;工作是开发一个基于 Instagram 数据分析的系统，包括任务分发、网红管理等功能。业务核心是基于这个系统，需要长期维护。
工作方式是正常工作日，按需求完成项目进度，并持续推进项目迭代。&lt;/p&gt;

&lt;p&gt;项目要做的事情和这个项目有一些类似 &lt;a href="https://hypetrace.com" rel="nofollow" target="_blank"&gt;https://hypetrace.com&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;新加坡团队都是华人，交流不是问题。&lt;/p&gt;
&lt;h2 id="岗位要求"&gt;岗位要求&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;全职工作。&lt;/li&gt;
&lt;li&gt;你需要有丰富的 Rails 项目开发经验，我期望是至少 3 年以上的 Rails 实际项目经验，能独立解决问题，且要求在互联网相关行业有多年经验做过各类复杂项目。&lt;/li&gt;
&lt;li&gt;自备设备。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;此岗位适合需要内陆地区定居，当地无法找到合适 Ruby 工作的同学。目前项目处于创业期，暂时只能已远程全职的方式合作，所以在待遇里面补贴进去了（也就是说待遇实际直接报高了）。&lt;/p&gt;

&lt;p&gt;简历投递：huacnlee@gmail.com&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Mon, 07 Sep 2020 10:14:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/40373</link>
      <guid>https://ruby-china.org/topics/40373</guid>
    </item>
    <item>
      <title>Ruby China 已经完全升级到了 Webpacker</title>
      <description>&lt;p&gt;Webpacker 引入 Rails 已经很久了，实际上我已经在所有的公司项目里面用 Webpacker 来管理 Rails 项目的前端。&lt;/p&gt;

&lt;p&gt;Homeland 项目起步比较早，老东西升级一直拖着。&lt;/p&gt;

&lt;p&gt;做了两个步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CoffeeScript 转 ES6，继续用 Sprockets 跑。&lt;/li&gt;
&lt;li&gt;然后用 Webpacker 替代 Sprockets。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/ruby-china/homeland/pull/1163" rel="nofollow" target="_blank"&gt;https://github.com/ruby-china/homeland/pull/1163&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Thu, 02 Jul 2020 12:16:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/40106</link>
      <guid>https://ruby-china.org/topics/40106</guid>
    </item>
    <item>
      <title>AuditLog - 轻松构建审查日志 / 操作日志</title>
      <description>&lt;p&gt;很多的管理后台为何安全考虑，往往会要求记录所有操作记录，以便与后期检查谁做了什么。&lt;/p&gt;

&lt;p&gt;我们目前的系统后台就需要这样的功能，业务要求准确的记录每一个功能的使用情况。&lt;/p&gt;

&lt;p&gt;于是在去年我就抽象出一个简单的组件 &lt;a href="https://github.com/rails-engine/audit-log" rel="nofollow" target="_blank" title=""&gt;AuditLog&lt;/a&gt; 来实现这个功能。&lt;/p&gt;

&lt;p&gt;AuditLog 截止目前，已经在我们系统中稳定运行了一年多，并持续记录了超过两千万条操作日志。&lt;/p&gt;
&lt;h2 id="功能介绍"&gt;功能介绍&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/rails-engine/audit-log" rel="nofollow" target="_blank" title=""&gt;AuditLog&lt;/a&gt; 是一个 Rails Gem，可以让你在 Controller 里面通过 &lt;code&gt;audit!&lt;/code&gt; 方法来实现记录用户动作，便于解决后期查询操作历史的需求。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails-engine/audit-log" rel="nofollow" target="_blank"&gt;https://github.com/rails-engine/audit-log&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="安装"&gt;安装&lt;/h2&gt;
&lt;p&gt;往 Gemfile 新增&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"audit-log"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;比如下面这个 Ticket 场景，分别在查阅列表、创建、更新、审核通过、删除等动作的时候，调用 &lt;code&gt;audit!&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;TicketsController&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;audit!&lt;/span&gt; &lt;span class="ss"&gt;:list_ticket&lt;/span&gt;&lt;span class="p"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@ticket.save&lt;/span&gt;
      &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:create_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;ticket_params&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;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@ticket.save&lt;/span&gt;
      &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:update_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;ticket_params&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;:edit&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;def&lt;/span&gt; &lt;span class="nf"&gt;approve&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@ticket.approve&lt;/span&gt;
      &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:approve_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="n"&gt;ticket_params&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;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="c1"&gt;# store original attributes for destroy for keep values&lt;/span&gt;
    &lt;span class="n"&gt;audit!&lt;/span&gt; &lt;span class="ss"&gt;:delete_ticket&lt;/span&gt;&lt;span class="p"&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;@ticket.attributes&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;ticket_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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ticket&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;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除此此外，也可以在其他地方直接调用函数来记录：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audit!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:update_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ip: &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;remote_ip&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以通过撰写 I18n 配置文件来对动作进行中文描述，增加 &lt;code&gt;config/locales/audit-log.zh-CN.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;zh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="no"&gt;CN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="ss"&gt;audit_log:
    action:
      sign_in: &lt;/span&gt;&lt;span class="n"&gt;登录&lt;/span&gt;
      &lt;span class="ss"&gt;update_password: &lt;/span&gt;&lt;span class="n"&gt;修改密码&lt;/span&gt;
      &lt;span class="ss"&gt;create_address: &lt;/span&gt;&lt;span class="n"&gt;添加住址&lt;/span&gt;
      &lt;span class="ss"&gt;list_ticket: &lt;/span&gt;&lt;span class="n"&gt;查看工单列表&lt;/span&gt;
      &lt;span class="ss"&gt;create_ticket: &lt;/span&gt;&lt;span class="n"&gt;创建工单&lt;/span&gt;
      &lt;span class="ss"&gt;update_ticket: &lt;/span&gt;&lt;span class="n"&gt;更新工单&lt;/span&gt;
      &lt;span class="ss"&gt;delete_ticket: &lt;/span&gt;&lt;span class="n"&gt;删除工单&lt;/span&gt;
      &lt;span class="ss"&gt;approve_ticket: &lt;/span&gt;&lt;span class="n"&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;&lt;img src="https://l.ruby-china.com/photo/2020/3a9822a8-5a7f-48d3-afe0-96866dba98a0.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;单个记录详情界面&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2020/6ee2b09c-ed72-4884-8b2f-4b0c8df0bba0.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/rails-engine/audit-log" rel="nofollow" target="_blank"&gt;https://github.com/rails-engine/audit-log&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Thu, 21 May 2020 16:01:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/39890</link>
      <guid>https://ruby-china.org/topics/39890</guid>
    </item>
    <item>
      <title>The Practical Effects of the GVL on Scaling in Ruby</title>
      <description>&lt;p&gt;&lt;a href="https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html" rel="nofollow" target="_blank"&gt;https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 15 May 2020 11:11:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/39868</link>
      <guid>https://ruby-china.org/topics/39868</guid>
    </item>
    <item>
      <title>最轻量级的 Rails 异步任务方案</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/mperham/sidekiq" rel="nofollow" target="_blank" title=""&gt;Sidekiq&lt;/a&gt; 、&lt;a href="https://github.com/resque/resque" rel="nofollow" target="_blank" title=""&gt;Resque&lt;/a&gt; 这类 Background Job 服务当下已成为几乎所有 Rails 应用程序的标准配置。&lt;/p&gt;

&lt;p&gt;但某些更微小的应用场景我们会发现单独部署一个 Sidekiq 进程有些复杂的，而且因为 Sidekiq 我们还得部署一个 Redis，我们往往只需要简单的异步而已，这个时候我们很想像 Go、Node.js 里面那样直接开一个异步动作处理或者自己搞一个消息订阅，这样部署可以和 Web 服务在同一个进程，无需复杂的额外部署。&lt;/p&gt;

&lt;p&gt;难道 Rails 应用必须标配一个额外的后台异步服务么？ 🙅🏻 不，我们有别的选择！&lt;/p&gt;

&lt;p&gt;我分析发现，实际上 Rails ActiveJob 内置的 &lt;a href="https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html#method-c-new" rel="nofollow" target="_blank" title=""&gt;AsyncAdapter&lt;/a&gt; 是可以做到这样的。&lt;/p&gt;
&lt;h2 id="如何使用"&gt;如何使用&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsyncJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# 为了让测试好验证，我们配置 test 环境用 inline 模式，其他时候用 AsyncAdapter&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;queue_adapter&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="ss"&gt;:inline&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueueAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AsyncAdapter&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;min_threads: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;max_threads: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;Concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processor_count&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;class&lt;/span&gt; &lt;span class="nc"&gt;NotifyTopicNodeChangedJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AsyncJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="c1"&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;h2 id="需要注意点"&gt;需要注意点&lt;/h2&gt;
&lt;p&gt;关于 AsyncAdapter 的使用，Rails 官方 API 有提到&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html" rel="nofollow" target="_blank"&gt;https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is the default queue adapter. It's well-suited for dev/test since it doesn't need an external infrastructure, but it's a poor fit for production since it drops pending jobs on restart.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;翻译谁都会，我说说这句我的理解，大概意思说这个本来是 ActiveJob 默认的 Adapter，它可以用在 dev/test 环境这样可以不需要额外的架构（异步服务），但不建议在生产环境用，因为重启会导致等待中 / 进行中任务丢失。&lt;/p&gt;

&lt;p&gt;我的建议用于可以容错，能容忍微小数据丢失的场景，比如访问量 +1，重新统计数据（再次调用能修复），或者个别时候对数据不敏感的场景比如个人博客。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The adapter uses a Concurrent Ruby thread pool to schedule and execute jobs. Since jobs share a single thread pool, long-running jobs will block short-lived jobs. Fine for dev/test; bad for production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这句意思说：此 Adapter 用 &lt;a href="https://github.com/ruby-concurrency/concurrent-ruby" rel="nofollow" target="_blank" title=""&gt;Concurrent&lt;/a&gt; 这个 Gem 利用 Ruby 的线程池方式来计划和执行异步任务，因为目前 ActiveJob::AsyncAdapter 的设计是所有异步任务都用的一个线程池（一个进程里面一个线程池），如果有重的异步任务，它们未结束之前会堵塞同一进程的其他任务，包括 HTTP 服务。&lt;/p&gt;

&lt;p&gt;鉴于 Ruby &lt;a href="https://en.wikipedia.org/wiki/Global_interpreter_lock" rel="nofollow" target="_blank" title=""&gt;GIL&lt;/a&gt; 的机制，上面的情况，同一个进程内，如果有重的耗费 CPU 的动作执行期间，可能会导致这段期间这个进程无法响应普通的 HTTP 请求，从而堵塞正常的 Web 服务。&lt;/p&gt;

&lt;p&gt;在此根据我的理解，IO 类的异步任务，用于 AsyncAdapter 其实是可以的，在等待 IO 响应的期间，多线程能起作用，HTTP 服务可以继续处理，同时由于我们往往有多个 Puma worker，只要避免在 AsyncAdapter 的 Job 内出现那种非常耗费 CPU 的异步任务，对服务的影响是可以接受的。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;前面说那么多，我们到底能不能用 AsyncAdapter，什么场景用合适？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;非数据敏感场景，能接受个别任务丢失；&lt;/li&gt;
&lt;li&gt;数据不能丢，但再次调用，能重建；&lt;/li&gt;
&lt;li&gt;确保部署多个 Puma worker；&lt;/li&gt;
&lt;li&gt;个人博客，初创期的网站，需要节约内存的场景；&lt;/li&gt;
&lt;li&gt;低 CPU 开销的任务；&lt;/li&gt;
&lt;li&gt;比如采集动作、清理数据动作；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🌺本文只是告诉大家一个可选的方案，在资源足够的情况下，请用 &lt;a href="https://github.com/mperham/sidekiq" rel="nofollow" target="_blank" title=""&gt;Sidekiq&lt;/a&gt; 效果更佳！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="其他选择"&gt;其他选择&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/SuckerPunchAdapter.html" rel="nofollow" target="_blank" title=""&gt;SuckerPunchAdapter&lt;/a&gt; - 它设计用来解决把异步任务泡在 Web Server 同一个进程内，我阅读了它的实现，在重启上比 AsyncAdapter 多一些优化，它会暂停重启一段时间，让异步任务有时间可以消化完。&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>huacnlee</author>
      <pubDate>Tue, 12 May 2020 17:39:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/39860</link>
      <guid>https://ruby-china.org/topics/39860</guid>
    </item>
    <item>
      <title>SQLBuilder - 一个简单的 SQL 生成器</title>
      <description>&lt;p&gt;最近公司的 Rails 项目里面有非 ActiveRecord 的数据库需要用 SQL 查询，比如 Amazon Redshift 之类的，这些表没有 Model，实现的时候查询基本上是手写 SQL，纯字符串拼接那种。&lt;/p&gt;

&lt;p&gt;随着业务越来越复杂，SQL 拼接也越来越复杂，于是开始质疑为何不用类似 AcitveRecord 那种 DSL 的查询方式 &lt;code&gt;where("name = ?", params[:name]).where(...).limit(100)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然而我查询了 ActiveRecord 以及 Ruby 社区现成的 Gem 发现，干这个事情的居然没一个能满足的，我一开始本以为 AcitveRecord 的 Arel 可以干这个事情，结果尝试下来看，那个查询方式和 ActiveRecord 的 DSL 用法不一样，而其他社区里面做这类事情的比如 &lt;a href="https://github.com/discourse/mini_sql" rel="nofollow" target="_blank" title=""&gt;mini_sql&lt;/a&gt; 也是以执行为主的，需要依赖 SQL Connection。&lt;/p&gt;

&lt;p&gt;于是我按照我们的场景构造了一个简化的 SQL Builder，让它只干一件事情：“基于 DSL 生成 SQL 语句”&lt;/p&gt;
&lt;h2 id="功能特点"&gt;功能特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;针对纯写 SQL 的场景设计，某些复杂业务查询你可能不一定想创建出 ActiveRecord Model；&lt;/li&gt;
&lt;li&gt;仅做 SQL Builder 的事情；&lt;/li&gt;
&lt;li&gt;类似 ActiveRecord style 的 DSL；&lt;/li&gt;
&lt;li&gt;用 ActiveRecord 内置的 &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql" rel="nofollow" target="_blank" title=""&gt;Sanitize&lt;/a&gt; 函数来处理参数，确保安全；&lt;/li&gt;
&lt;li&gt;更简单的逻辑，以适应各种奇怪的 SQL 数据库，sql-builder 没有复杂的 SQL 生成，仅仅帮你做拼接简化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="安装"&gt;安装&lt;/h2&gt;
&lt;p&gt;往 Gemfile 增加：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"sql-builder"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;一个简单的查询&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;SQLBuilder&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="s2"&gt;"SELECT * FROM users"&lt;/span&gt;&lt;span class="p"&gt;)&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;"name = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="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;"status != ?"&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="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at desc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id asc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page&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="nf"&gt;per&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM users WHERE name = 'hello world' AND status != 1 ORDER BY created_at desc, id asc LIMIT 20 OFFSET 0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结合实际业务场景，我们的 SQL 条件可能会很复杂：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SQLBuilder&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="s2"&gt;"SELECT users.name, users.age, user_profiles.bio, user_profiles.avatar FROM users INNER JOIN user_profiles ON users.id = user_profiles.user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# conditions by params&lt;/span&gt;
&lt;span class="n"&gt;query&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;"age &amp;gt;= ?"&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;:age&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;unless&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;:age&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
&lt;span class="n"&gt;query&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;"status = ?"&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;:status&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;unless&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="nf"&gt;nil?&lt;/span&gt;
&lt;span class="k"&gt;if&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;:created_at_from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;:created_at_to&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;query&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;= ? and created_at &amp;lt;= ?"&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;:created_at_from&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;:created_at_to&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id desc"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;

&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"SELECT users.name, users.age, user_profiles.bio, user_profiles.avatar FROM users INNER JOIN user_profiles ON users.id = user_profiles.user_id WHERE age &amp;gt;= 18 AND status = 3 AND created_at &amp;gt;= '2020-01-03 10:54:08 +0800' and created_at &amp;lt;= '2020-01-03 10:54:08 +0800' ORDER BY id desc LIMIT 100 OFFSET 0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上面这段你可以看到，那些复杂的 SQL 依然还是你自己写的，SQLBuilder 只是在 &lt;code&gt;where&lt;/code&gt;、&lt;code&gt;order&lt;/code&gt;、&lt;code&gt;limit&lt;/code&gt; 这些场景可以帮你处理复杂逻辑，因此，它几乎不会因为内部生成了某些 SQL 数据库不支持的语法。 &lt;/p&gt;
&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/huacnlee/sql-builder" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/sql-builder&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 03 Jan 2020 11:18:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/39399</link>
      <guid>https://ruby-china.org/topics/39399</guid>
    </item>
    <item>
      <title>ActionText Lite - 我终于受不了 ActionText，做了一个 Lite 版本</title>
      <description>&lt;p&gt;关于 ActionText 的问题，我已经吐槽不止一两次了...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ActionText 内置的 HTML sanitize 带上了不少严苛的规则，比如不允许 style，就是你换了其他的所见即所得编辑器，编辑好的 HTML 输出还是会少了东西。
还是没有 API，感觉可以自己实现一个 Lite 版本的 ActionText 了，只用 ActionText 存储的部分。
&lt;a href="/huacnlee" class="user-mention" title="@huacnlee"&gt;&lt;i&gt;@&lt;/i&gt;huacnlee&lt;/a&gt; &lt;a href="https://twitter.com/huacnlee/status/1177133855288909824" rel="nofollow" target="_blank"&gt;https://twitter.com/huacnlee/status/1177133855288909824&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Action Text 的问题"&gt;Action Text 的问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Action Text 默认高度集成了 Trix，但实际情况是 Trix 在 90% 的项目情况下都不符合实际的需求，我们的用户、后台编辑人员不喜欢它。😩&lt;/li&gt;
&lt;li&gt;Active Storage 为默认的附件存储，也是高度集成，难以清理干净，可它往往也不符合我们的需求，我们正文的附件、图片往往是需要公开的 URL 地址（Active Storage 目前不支持）。&lt;/li&gt;
&lt;li&gt;Action Text 带有默认的 HTML sanitize 规则，但那个规则过于严苛，比如 style 属性不支持，图片宽度不支持等等，然而有些时候我们可能不一定需要 sanitize 因为内容可能是后台编辑自己发布的，而不是用户的（用户发布的我们可以自己做 sanitize） 🥺&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我也是坚持用 ActiveText，也尝试在 Trix 和 ActionText 的基础上尝试解决以上问题，但实际情况发现做起来很繁琐，回头来看，当初用 ActionText 不就是为了那个将 RichText 独立存储到单独表的设计么，于是阅读源代码找到实现，单独剥离出来。&lt;/p&gt;

&lt;p&gt;于是我把它整理成了 ActionText Lite。&lt;/p&gt;
&lt;h2 id="ActionText Lite 干什么事情："&gt;ActionText Lite 干什么事情：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;类似 ActiveText 的存储 API，兼容 ActionText 的 Model API，你可以保持那个好的东西；&lt;/li&gt;
&lt;li&gt;数据存储依然保持在 ActionText 那个 &lt;code&gt;action_text_rich_texts&lt;/code&gt; 表里面，所以引入它，只是会让 ActionText 没了 Trix 和 ActiveStorage 的集成；&lt;/li&gt;
&lt;li&gt;你存什么进去，就拿什么出来，不做任何 sanitize 处理，把规则交给你自己处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"actiontext-lite"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后安装 ActionText 的 Migration 命令（如果已经有 ActionText 做过，可以不用执行）&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails action_text_lite:install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来原来使用 ActiveText (Model 里面哪些功能），你可以继续保持：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_rich_text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;"Hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="vi"&gt;@post.body.to_s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="然后缺失的其他部分怎么办？"&gt;然后缺失的其他部分怎么办？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;附件、图片存储，你可以用 &lt;a href="https://github.com/carrierwaveuploader/carrierwave" rel="nofollow" target="_blank" title=""&gt;CarrierWave&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;所见即所得编辑器 - 可以尝试 &lt;a href="https://github.com/huacnlee/ckeditor5-build-rails" rel="nofollow" target="_blank" title=""&gt;ckeditor5-build-rails&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/huacnlee/actiontext-lite" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/actiontext-lite&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Thu, 10 Oct 2019 11:49:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/39130</link>
      <guid>https://ruby-china.org/topics/39130</guid>
    </item>
    <item>
      <title>Rails 默认 Session 的存储方式：CookieStore</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;This cookie-based session store is the Rails default. It is dramatically faster than the alternatives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html" rel="nofollow" target="_blank"&gt;https://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;或许很多人不知道这个默认规则！&lt;/p&gt;

&lt;p&gt;面试的时候不熟悉 Rails 的人会想当然的回答 Session 是存在服务端的，再一问存服务端哪里的，基本就蒙了 😇，有说 Redis，更有说进程内存里面。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;实际上这个是一个安全的做法，&lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html" rel="nofollow" target="_blank" title=""&gt;CookieStore&lt;/a&gt; 会基于 &lt;code&gt;secure_key_base&lt;/code&gt; 对 Session 内容进行 &lt;code&gt;aes-256-cbc&lt;/code&gt; 加密，并将最终加密以 Base64 的结果返回作为 Cookie。&lt;/p&gt;

&lt;p&gt;于是我们会看到 &lt;code&gt;__your_app_session&lt;/code&gt; 这样的 Cookie 普遍在 Rails 项目里面存在：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/ee997988-2fa2-43e7-8b6b-be0fcd34ccde.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Rails 哲学里面提倡最佳实践，CookieStore 应该算是一个，不需要依赖服务端的持久化数据库（Redis / Memcache / MySQL）既可实现。&lt;/p&gt;
&lt;h2 id="CookieStore 的优点"&gt;CookieStore 的优点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;简单有效，无需额外的后端存储，开箱即用；&lt;/li&gt;
&lt;li&gt;相对与其他存储方式，它很快，只需解密，无 IO 请求；&lt;/li&gt;
&lt;li&gt;因为 Session 加密存储在用户浏览器，所以不回因为后端存储丢失而导致 Session 失效；&lt;/li&gt;
&lt;li&gt;我们依然可以通过改变 &lt;code&gt;secure_key_base&lt;/code&gt; 的方式来使之前的 Session 失效；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="缺点"&gt;缺点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;浏览器 Cookie 限制，加密过后的内容不能超过 4K，所以我们不能在 Session 里面放过多内容；&lt;/li&gt;
&lt;li&gt;如果你的静态资源没有独立域名的话，静态页面的请求 Header 也会多余带这个 Session 的值；&lt;/li&gt;
&lt;li&gt;Session 存储过大内容到了客户端，会导致用户访问服务错误，切难以被发现；&lt;/li&gt;
&lt;li&gt;如果那种验证 code  存 Session 里面，会有重现攻击（Replay attacks）风险 🚨，参考：&lt;a href="https://github.com/huacnlee/rucaptcha#usage" rel="nofollow" target="_blank" title=""&gt;RuCaptcha 说明&lt;/a&gt;；&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="一些提示"&gt;一些提示&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flash&lt;/code&gt; 实际上也是存储在 Session 里面的；&lt;/li&gt;
&lt;li&gt;尽量在 session 里面存 user_id 即可，别把整个 user 对象放进去；&lt;/li&gt;
&lt;li&gt;因为客户端实际上能拿到 Session 也可以通过请求 set-cookie header 的方式修改 Cookie，所以 &lt;code&gt;secure_key_base&lt;/code&gt; 一定要保密，一旦发现泄漏，尽快改掉；&lt;/li&gt;
&lt;li&gt;改掉 &lt;code&gt;secure_key_base&lt;/code&gt; 的副作用是已经有 Session 会因为无法失败而被当成失效的，表现上看就是用户需要重新登录；&lt;/li&gt;
&lt;li&gt;避免敏感数据存 Session 里面，以免被重现攻击；&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🔖如果你掌控不了，或目前项目已有敏感数据在 Session 里面，图省事你可以换成 &lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Session/CacheStore.html" rel="nofollow" target="_blank" title=""&gt;CacheStore&lt;/a&gt; (Rails &amp;gt;= 5.2)，Cache 存哪里 Session 就存哪里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="参考阅读"&gt;参考阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://guides.rubyonrails.org/security.html#sessions" rel="nofollow" target="_blank" title=""&gt;Rails Guides / Security / Sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html" rel="nofollow" target="_blank" title=""&gt;Rails API / CookieStore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html" rel="nofollow" target="_blank" title=""&gt;MessageEncryptor 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://guides.rubyonrails.org/security.html#replay-attacks-for-cookiestore-sessions" rel="nofollow" target="_blank" title=""&gt;CookieStore Sessions 重现攻击&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/tonytonyjan/d71f040fe1085dfcf1d4" rel="nofollow" target="_blank" title=""&gt;Gist - 如何手工解密 Session&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>huacnlee</author>
      <pubDate>Tue, 24 Sep 2019 17:54:25 +0800</pubDate>
      <link>https://ruby-china.org/topics/39083</link>
      <guid>https://ruby-china.org/topics/39083</guid>
    </item>
    <item>
      <title>Enumize - 扩展 ActiveRecord::Enum 增加实用方法</title>
      <description>&lt;p&gt;Rails 4.2 (如果没记错）给我们带来了官方的 Enum 解决方案，目前大家都在使用它了。&lt;/p&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;Book&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="sx"&gt;%i[draft published archived]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而我们在使用的时候会发现，往往我们需要将 &lt;code&gt;status&lt;/code&gt; 字段用中文或其他的方式在界面上显示，而不是一个 &lt;code&gt;draft / published / archived&lt;/code&gt;。&lt;/p&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;Book&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="sx"&gt;%i[draft published archived]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status_name&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"draft"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&gt;"草稿"&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"published"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&gt;"已发布"&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"archived"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&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;然后随着项目的推进，你会发现你的 Model 里面大量有这种函数。&lt;/p&gt;

&lt;p&gt;为了解决这个问题，我们最初在自己的项目里面实现了一个扩展，用于代替 &lt;code&gt;enum&lt;/code&gt; 的定义，给它增加一些函数扩展，现在提取成一个 Gem，可以方便以后使用。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="使用方法"&gt;使用方法&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"enumize"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后你的原来的 enum 定义不需要改变，这个 Gem 已经覆盖了那个方法。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;img title=":rotating_light:" alt="🚨" src="https://twemoji.ruby-china.com/2/svg/1f6a8.svg" class="twemoji"&gt; 所以，你可以认为这个 Gem 的引入不会改变 ActiveRecord::Enum 的原有使用方式，你可以无缝的集成到你的项目中！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="sx"&gt;%i[draft published archived]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在你有了 &lt;code&gt;status_name&lt;/code&gt;, &lt;code&gt;status_color&lt;/code&gt;, &lt;code&gt;status_value&lt;/code&gt; 以及 &lt;code&gt;Book.status_options&lt;/code&gt; 这些方法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#{attribute}_name&lt;/code&gt; - 返回 name 的 I18n 信息，用于显示&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#{attribute}_color&lt;/code&gt; - 返回从 I18n 里面读取 color 的信息（如果你项目用不到可以忽略它）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#{attribute}_value&lt;/code&gt; - 返回原始的数据库值，用来代替 &lt;code&gt;Book.statuses[@book.status]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Book.#{attribute}_options&lt;/code&gt; - 返回一个 Array，用于 &lt;code&gt;select&lt;/code&gt; tag，比如 &lt;code&gt;&amp;lt;%= f.select :status, Book.status_options %&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配置 I18n:&lt;/p&gt;

&lt;p&gt;config/locales/en.yml&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;activerecord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enums&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;book&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="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Drafting&lt;/span&gt;
          &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Published&lt;/span&gt;
          &lt;span class="na"&gt;archived&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Archived&lt;/span&gt;
        &lt;span class="na"&gt;status_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#999999"&lt;/span&gt;
          &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green"&lt;/span&gt;
          &lt;span class="na"&gt;archived&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;config/locales/zh-CN.yml&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;zh-CN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;activerecord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enums&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;book&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="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;草稿"&lt;/span&gt;
          &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;已发布"&lt;/span&gt;
          &lt;span class="na"&gt;archived&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;归档"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是使用的演示：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&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;status: :draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.status&lt;/span&gt;
&lt;span class="s2"&gt;"draft"&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.draft&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.published&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.status_value&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.status_name&lt;/span&gt;
&lt;span class="s2"&gt;"草稿"&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@book.status_color&lt;/span&gt;
&lt;span class="s2"&gt;"#999999"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;status_options&lt;/code&gt; 方法用在 View 里面的演示：&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;form_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_options&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/huacnlee/enumize" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/enumize&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Mon, 24 Jun 2019 14:32:01 +0800</pubDate>
      <link>https://ruby-china.org/topics/38728</link>
      <guid>https://ruby-china.org/topics/38728</guid>
    </item>
    <item>
      <title> 全新 rails-settings-cached 设计 2.x 发布</title>
      <description>&lt;p&gt;如果你的项目内在使用 rails-settings-cached，升级请一定注意 2.0 的变化，否则将会无法运行。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;rails-settings-cached 2.x 全新设计了 API，新的版本会兼容老的数据（在数据库中的），但使用方式不兼容。所以在升级之前请阅读 2.x 的使用介绍，并根据需要调整你的项目使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;全新的 API 设计；&lt;/li&gt;
&lt;li&gt;不在支持 &lt;code&gt;scope&lt;/code&gt; (include RailsSettings::Extend) 的方式；&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;之前的这个支持以实际来看，不如 Rails 的 &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize" rel="nofollow" target="_blank" title=""&gt;serialize&lt;/a&gt; 功能好用，2.0 不在支持这种数据读取了，如果你的项目在使用，可以尝试 &lt;a href="https://github.com/huacnlee/rails-settings-cached#backward-compatible-to-support-0x-scoped-settings" rel="nofollow" target="_blank" title=""&gt;这样的方式兼容&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;不在需要 &lt;code&gt;YAML&lt;/code&gt; 的默认配置文件，所有都在 Setting model 指定；&lt;/li&gt;
&lt;li&gt;除非你用 &lt;code&gt;field&lt;/code&gt; 定义设置键，否则是无法使用的；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Setting&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RailsSettings&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;field&lt;/span&gt; &lt;span class="ss"&gt;:host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="s2"&gt;"http://example.com"&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:readonly_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;readonly: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:user_limits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:admin_emails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="sx"&gt;%w[admin@rubyonrails.org]&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:captcha_enable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:smtp_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s2"&gt;"foo.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="s2"&gt;"foo@bar.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s2"&gt;"123456"&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;ul&gt;
&lt;li&gt;新的设计对性能做了改进，以实际的项目应用经验来看，往往我们一个页面会有很多的 key 调用，所以 2.x 版本开始，cache 和 db 的载入是一次的，一条 SQL 或 一次缓存的访问，同一个请求周期设置信息是可以重复利用的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/huacnlee/rails-settings-cached" rel="nofollow" target="_blank"&gt;https://github.com/huacnlee/rails-settings-cached&lt;/a&gt;&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 26 Apr 2019 14:38:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/38453</link>
      <guid>https://ruby-china.org/topics/38453</guid>
    </item>
  </channel>
</rss>
