<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hfpp2012 (随风)</title>
    <link>https://ruby-china.org/hfpp2012</link>
    <description>喜欢分享有趣，有用的编程技术文章和视频教程的程序员</description>
    <language>en-us</language>
    <item>
      <title>释出自己的 ruby gem 02 acts_as_avatar 给系统自动加上数种随机头像</title>
      <description>&lt;p&gt;上一节：&lt;a href="https://www.qiuzhi99.com/articles/ruby/97610.html" rel="nofollow" target="_blank" title=""&gt;释出自己的 ruby gem 01 where_streets 实现省市县镇四级联动&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们经常要给系统，比如一个后台，添加用户的头像，有时候我想先随机给每个后台用户先生成一个，那么可以用这个 gem。&lt;/p&gt;
&lt;h2 id="acts_as_avatar"&gt;acts_as_avatar&lt;/h2&gt;
&lt;p&gt;源码：&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar" rel="nofollow" target="_blank" title=""&gt;acts_as_avatar&lt;/a&gt; （欢迎 star）&lt;/p&gt;

&lt;p&gt;第二个 gem 是关于头像的，先看下效果&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2516/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这个头像不止一种。&lt;/p&gt;

&lt;p&gt;还有其他几种：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2517/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2518/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2519/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2520/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2521/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2522/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2523/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2524/2022/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这些头像是可以配置的，先用哪种，只要在配置文件里修改下就行。&lt;/p&gt;
&lt;h2 id="安装使用"&gt;安装使用&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://rubygems.org/gems/acts_as_avatar" rel="nofollow" target="_blank"&gt;https://rubygems.org/gems/acts_as_avatar&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add acts_as_avatar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成表和配置文件：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails g acts_as_avatar:install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法很简单：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# view&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= acts_as_avatar_tag(admin_user, class: "rounded-circle") %&amp;gt;

&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;acts_as_avatar_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_admin_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"rounded-circle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= acts_as_avatar_tag(current_admin_user, name: :avatar_name, class: "rounded-circle") %&amp;gt;

&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;github_avatar_tag&lt;/span&gt; &lt;span class="ss"&gt;complexity: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= github_avatar_tag size: 60, rounded_circle: true %&amp;gt;

# model
acts_as_avatar inline_svg_engine: :initial_avatar

# or

acts_as_avatar
&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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# config/initializers/acts_as_avatar.rb&lt;/span&gt;
&lt;span class="no"&gt;ActsAsAvatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random_image_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"random_image_engine"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# config.uifaces_limit       = 20&lt;/span&gt;
  &lt;span class="c1"&gt;# config.uifaces_gender      = "female"&lt;/span&gt;
  &lt;span class="c1"&gt;# config.uifaces_uri         = "https://api.uifaces.co"&lt;/span&gt;
  &lt;span class="c1"&gt;# config.uifaces_api_key     = ""&lt;/span&gt;
  &lt;span class="c1"&gt;# config.default_file_name   = "default_avatar"&lt;/span&gt;
  &lt;span class="c1"&gt;# config.upload_max_size     = 2.megabytes&lt;/span&gt;
  &lt;span class="c1"&gt;# config.avatar_size         = 60&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline_svg_engine&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"inline_svg_engine"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;avatar_name&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:avatar_name&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_type&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[AdminUser]&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github_complexity&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github_render_method&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"square"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github_rounded_circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于配置文件的各种取值，可以看这个文件：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/generators/templates/initializer.rb.tt" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/generators/templates/initializer.rb.tt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;比如 random_image_engine 的取值就是  :letter_avatar, :uifaces_avatar, :github_avatar, or :identicon_avatar&lt;/p&gt;

&lt;p&gt;你可以试下，会变成不同的图标。&lt;/p&gt;
&lt;h2 id="源码和原理"&gt;源码和原理&lt;/h2&gt;
&lt;p&gt;主要的原理就是在每个使用头像的 model 保存时，把随机头像放进去，放到一个单独的表中。&lt;/p&gt;

&lt;p&gt;这个是核心，了解下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# belongs_to :avatarable, polymorphic: true&lt;/span&gt;
&lt;span class="n"&gt;delegated_type&lt;/span&gt; &lt;span class="ss"&gt;:avatarable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;types: &lt;/span&gt;&lt;span class="no"&gt;ActsAsAvatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_type&lt;/span&gt;
&lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:avatarable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="c1"&gt;# delegate :email, to: :avatarable&lt;/span&gt;
&lt;span class="c1"&gt;# accepts_nested_attributes_for :avatarable&lt;/span&gt;
&lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:upload_avatar&lt;/span&gt;
&lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:default_avatar&lt;/span&gt;
&lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="ss"&gt;:add_default_avatar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: &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;random_image_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:upload_avatar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;less_than: &lt;/span&gt;&lt;span class="no"&gt;ActsAsAvatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_max_size&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                          &lt;span class="ss"&gt;content_type: &lt;/span&gt;&lt;span class="sr"&gt;%r{&lt;/span&gt;&lt;span class="se"&gt;\A&lt;/span&gt;&lt;span class="sr"&gt;image/.*&lt;/span&gt;&lt;span class="se"&gt;\z&lt;/span&gt;&lt;span class="sr"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="ss"&gt;:sanitize_svg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if: :svg?&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要是这个：after_create_commit&lt;/p&gt;

&lt;p&gt;表示在创建 model 里会去创建 随机的头像，&lt;/p&gt;

&lt;p&gt;这里用到了 default_avatar 和 upload_avatar，&lt;/p&gt;

&lt;p&gt;upload_avatar 表示默认的随机头像，这个 gem 还支持自定义用户自己上传头像，用 upload_avatar 来表示。（后面会演示到）&lt;/p&gt;

&lt;p&gt;这个表的结果是这样的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# == Schema Information&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Table name: act_as_avatars&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#  id              :bigint           not null, primary key&lt;/span&gt;
&lt;span class="c1"&gt;#  avatarable_type :string           not null&lt;/span&gt;
&lt;span class="c1"&gt;#  created_at      :datetime         not null&lt;/span&gt;
&lt;span class="c1"&gt;#  updated_at      :datetime         not null&lt;/span&gt;
&lt;span class="c1"&gt;#  avatarable_id   :bigint           not null&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Indexes&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#  index_avatars_on_avatarable  (avatarable_type,avatarable_id)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面是核心的，其他的都是细节，可以通过读源码知道。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/class_methods.rb" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/class_methods.rb&lt;/a&gt; model 里有使用的 method 的源码。&lt;/p&gt;

&lt;p&gt;可以用的 helper 方法放在这里：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/helper.rb" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/helper.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;可以自行找来用。&lt;/p&gt;

&lt;p&gt;其中有个头像是用了第三方的 api，原理在这里：&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/request.rb" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/request.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;值得注意的是，头像也支持 svg，我用到了 svg 的清理技术，有兴趣的可以了解下：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/scrubber.rb" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/scrubber.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;有一个头像好像是 github 头像，用到了 js，我用了 js 去调 ruby。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/github_avatar.rb" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/acts_as_avatar/blob/main/lib/acts_as_avatar/github_avatar.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;差不多是这样，有问题可以一起讨论交流。&lt;/p&gt;

&lt;p&gt;最近别忘了 follow 和 star（换了一个新的 github 号)&lt;/p&gt;

&lt;p&gt;有问题可以一起交流，wechat: qiuzhi99pro&lt;/p&gt;

&lt;p&gt;其它的等我慢慢更新。想学 ruby 和 rails 的可以看看我录制的教程哈：&lt;a href="https://www.qiuzhi99.com/playlists/ruby.html" rel="nofollow" target="_blank"&gt;https://www.qiuzhi99.com/playlists/ruby.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文链接：&lt;a href="https://www.qiuzhi99.com/articles/ruby/97611.html" rel="nofollow" target="_blank"&gt;https://www.qiuzhi99.com/articles/ruby/97611.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Wed, 07 Sep 2022 14:16:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/42634</link>
      <guid>https://ruby-china.org/topics/42634</guid>
    </item>
    <item>
      <title>释出自己的 ruby gem 01 where_streets 实现省市县镇四级联动</title>
      <description>&lt;p&gt;原文链接：&lt;a href="https://www.qiuzhi99.com/articles/ruby/97610.html" rel="nofollow" target="_blank"&gt;https://www.qiuzhi99.com/articles/ruby/97610.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近大半年做了一个新项目，把 rails 7 新特性探究得差不多，学习了很多东西，也有一些经验可以分享出来。&lt;/p&gt;

&lt;p&gt;首先要分享的是，我最近写了十几个 gem，本来打算私用的，现在改变想法，把它分享出来，希望对大家有所帮助。&lt;/p&gt;
&lt;h4 id="01. where_streets 实现省市县镇四级联动"&gt;01. &lt;a href="https://www.qiuzhi99.com/articles/ruby/97610.html" rel="nofollow" target="_blank" title=""&gt;where_streets 实现省市县镇四级联动&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="02. acts_as_avatar 给系统自动加上数种随机头像"&gt;02. &lt;a href="https://www.qiuzhi99.com/articles/ruby/97611.html" rel="nofollow" target="_blank" title=""&gt;acts_as_avatar 给系统自动加上数种随机头像&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="03. custom_trix 增强型 trix"&gt;03. custom_trix 增强型 trix&lt;/h4&gt;&lt;h4 id="04. activestorage_upyun 又拍云存储"&gt;04. activestorage_upyun 又拍云存储&lt;/h4&gt;&lt;h4 id="05. error404 自定义错误页面"&gt;05. error404 自定义错误页面&lt;/h4&gt;&lt;h4 id="06. flag_icons 国家图标"&gt;06. flag_icons 国家图标&lt;/h4&gt;&lt;h4 id="07. ip_locator ip 地址定位"&gt;07. ip_locator ip 地址定位&lt;/h4&gt;&lt;h4 id="08. niceadmin 漂亮的后台"&gt;08. niceadmin 漂亮的后台&lt;/h4&gt;&lt;h4 id="09. rails_boxicons boxicons svg 图标"&gt;09. rails_boxicons boxicons svg 图标&lt;/h4&gt;&lt;h4 id="10. ant_design_icon ant design svg 图标"&gt;10. ant_design_icon ant design svg 图标&lt;/h4&gt;&lt;h4 id="11. rails_fontawesome6 svg 图标"&gt;11. rails_fontawesome6 svg 图标&lt;/h4&gt;&lt;h4 id="12. tinymce_extended tinymce 增强"&gt;12. tinymce_extended tinymce 增强&lt;/h4&gt;&lt;h4 id="13. table_for 增强"&gt;13. table_for 增强&lt;/h4&gt;&lt;h4 id="14. rails_remixicon 增强"&gt;14. rails_remixicon 增强&lt;/h4&gt;&lt;h4 id="15. youdao_translate_all 有道云翻译 api"&gt;15. youdao_translate_all 有道云翻译 api&lt;/h4&gt;&lt;h4 id="16. helper_extended helper 个性增强"&gt;16. helper_extended helper 个性增强&lt;/h4&gt;&lt;h4 id="17. loading_svg 图标"&gt;17. loading_svg 图标&lt;/h4&gt;&lt;h4 id="18. activestorage_tencent 和 tencent_cos 腾讯 cos 云存储"&gt;18. activestorage_tencent 和 tencent_cos 腾讯 cos 云存储&lt;/h4&gt;&lt;h4 id="19. iconfont 官网图标封装"&gt;19. iconfont 官网图标封装&lt;/h4&gt;&lt;h4 id="20. where_city 通过坐标查城市位置"&gt;20. where_city 通过坐标查城市位置&lt;/h4&gt;
&lt;p&gt;除了 gem 可能还有其他经验分享，等我慢慢梳理更新。&lt;/p&gt;
&lt;h2 id="where_streets"&gt;where_streets&lt;/h2&gt;
&lt;p&gt;源码：&lt;a href="https://github.com/hfpp2012copy/where_streets" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012copy/where_streets&lt;/a&gt; （欢迎 star）&lt;/p&gt;

&lt;p&gt;实现省市县镇多级联动功能，网络上关于 ruby 的 gem 好像是有，不过有些老了，不太好用。&lt;/p&gt;

&lt;p&gt;我自己实现了一个，很简单。&lt;/p&gt;

&lt;p&gt;我会把源码和使用方法分享出来。&lt;/p&gt;

&lt;p&gt;大家可以一起学习讨论。&lt;/p&gt;

&lt;p&gt;先看功能：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2514/2022/a31faac54276ad63073ea210cc1f4600.gif" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;也可以是三级联动的，把镇去掉就行。&lt;/p&gt;

&lt;p&gt;还有下面这种形式：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2515/2022/c17661a2727466183b92dc7e8521ea54.gif" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="安装使用"&gt;安装使用&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://rubygems.org/gems/where_streets" rel="nofollow" target="_blank"&gt;https://rubygems.org/gems/where_streets&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;第一步：安装&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add where_streets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步：直接在表单中用&lt;/p&gt;

&lt;p&gt;例如：&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;bootstrap_form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;admin_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;admin_account_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: :put&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s2"&gt;"form--request ts--cities-select dropzone-uploader"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"needs-validation mt-3"&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;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:avatar&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;avatar_file_field&lt;/span&gt; &lt;span class="ss"&gt;:upload_avatar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;remove_path: &lt;/span&gt;&lt;span class="n"&gt;remove_avatar_admin_users_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:admin_profile&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;:fullname&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;rich_text_area&lt;/span&gt; &lt;span class="ss"&gt;:about&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: &lt;/span&gt;&lt;span class="s2"&gt;"height: 100px;"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&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;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;disabled: &lt;/span&gt;&lt;span class="kp"&gt;true&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;form&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;:province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_provinces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"province"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&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;:city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"city"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&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;:county&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s1"&gt;'county'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&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;:town&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_towns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;county&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s1"&gt;'town'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_button&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我用了 stimulus。&lt;/p&gt;

&lt;p&gt;我也分享出来：&lt;/p&gt;

&lt;p&gt;主要是这个 &lt;code&gt;ts--cities-select&lt;/code&gt; controller&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/controllers/ts/cities_select_controller.js&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;Controller&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/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;get&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;@rails/request.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;TomSelect&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;tom-select&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Connects to data-controller="ts--cities-select"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;province&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;county&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;town&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nf"&gt;initialize&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;province&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;county&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;town&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Value`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Target`&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&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="s2"&gt;`tomSelect&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&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="nx"&gt;name&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;name&lt;/span&gt;&lt;span class="p"&gt;]&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;TomSelect&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Target`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clear_button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;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="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;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tomSelectProvince&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;tomSelectCity&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;tomSelectCounty&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;hasTownTarget&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;tomSelectTown&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;provinceTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/cities/cities_filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;province&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;provinceValue&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;selectTarget&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;tomSelectCity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/cities/counties_filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;province&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;provinceValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&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;cityValue&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;selectTarget&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;tomSelectCounty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countyTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&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="nx"&gt;hasTownTarget&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/cities/towns_filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;province&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;provinceValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;city&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;cityValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;county&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;countyValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;selectTarget&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;tomSelectTown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectTarget&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;response&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;responseKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOptionsData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setOptionsData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;selectTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;selectTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;selectTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;render_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;div&amp;gt;
        &amp;lt;div class="text"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
        &amp;lt;div class="sub"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有用到 &lt;code&gt;tom-select&lt;/code&gt;，可以用 &lt;code&gt;yarn add tom-select&lt;/code&gt; 安装一下。&lt;/p&gt;

&lt;p&gt;查看上面的 js 代码，可以看到，这里用到了 api 查询（比如 &lt;code&gt;/admin/cities/towns_filter&lt;/code&gt;），因为每次选择位置后，都会发请求去得到数据，比如选了某个省，会把这个省的所有城市数据得到。&lt;/p&gt;

&lt;p&gt;我这里可以新建一个 controller 就搞定。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Admin&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CitiesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseController&lt;/span&gt;
    &lt;span class="c1"&gt;# @route GET /admin/cities/cities_filter (admin_cities_cities_filter)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cities_filter&lt;/span&gt;
      &lt;span class="vi"&gt;@cities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&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;:province&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@cities.map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# @route GET /admin/cities/counties_filter (admin_cities_counties_filter)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counties_filter&lt;/span&gt;
      &lt;span class="vi"&gt;@counties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&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;:province&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;:city&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@counties.map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# @route GET /admin/cities/towns_filter (admin_cities_towns_filter)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;towns_filter&lt;/span&gt;
      &lt;span class="vi"&gt;@towns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_towns&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;:province&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;:city&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;:county&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@towns.map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;town&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;town&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;town&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后你把在 model 里存几个字段，比如 province, city 等存到数据库为就好。&lt;/p&gt;
&lt;h2 id="源码或原理分享"&gt;源码或原理分享&lt;/h2&gt;
&lt;p&gt;原理比较简单，主要就是读取网络上最新的 json 文件进行解析数据。&lt;/p&gt;

&lt;p&gt;提供了一些查找功能，比如找一个省下的所有城市，一个城市下的所有区等等。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"singleton"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"forwardable"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"fast_blank"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"msgpack"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WhereStreets&lt;/span&gt;
  &lt;span class="nb"&gt;autoload&lt;/span&gt; &lt;span class="ss"&gt;:VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"where_streets/version"&lt;/span&gt;

  &lt;span class="no"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MessagePack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../pcas.mp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Forwardable&lt;/span&gt;
    &lt;span class="n"&gt;def_delegators&lt;/span&gt; &lt;span class="ss"&gt;:instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:find_provinces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:find_cities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:find_counties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:find_towns&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;find_provinces&lt;/span&gt;
    &lt;span class="no"&gt;FILE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;find_cities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;province&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;province&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;handle_error&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;FILE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&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;find_counties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&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="nf"&gt;blank?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;handle_error&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;FILE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&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;find_towns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;county&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&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="nf"&gt;blank?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;handle_error&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;FILE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;province&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="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;handle_error&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backtrace&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;a href="https://github.com/msgpack/msgpack-ruby" rel="nofollow" target="_blank" title=""&gt;msgpack&lt;/a&gt; 这个库，也可以不用它，之前没有用的，可以通过代码去查之前的版本。&lt;/p&gt;

&lt;p&gt;里面还有一些测试代码，可以看看其用法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"test_helper"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WhereStreetsTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_that_it_has_a_version_number&lt;/span&gt;
    &lt;span class="n"&gt;refute_nil&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&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;test_provinces&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_provinces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&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;test_cities&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="sx"&gt;%w[广州市 韶关市 深圳市 珠海市 汕头市 佛山市 江门市 湛江市 茂名市 肇庆市 惠州市 梅州市 汕尾市 河源市 阳江市 清远市 东莞市 中山市 潮州市 揭阳市 云浮市]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&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="n"&gt;assert_equal&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="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&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="n"&gt;assert_empty&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&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="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_counties&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="sx"&gt;%w[罗湖区 福田区 南山区 宝安区 龙岗区 盐田区 龙华区 坪山区 光明区]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&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="s2"&gt;"深圳市"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="sx"&gt;%w[黄浦区 徐汇区 长宁区 静安区 普陀区 虹口区 杨浦区 闵行区 宝山区 嘉定区 浦东新区 金山区 松江区 青浦区 奉贤区 崇明区]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&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="s2"&gt;"市辖区"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_empty&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&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="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# assert_empty WhereStreets.find_counties("广东省", "海丰市")&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;test_towns&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="sx"&gt;%w[梅陇镇 小漠镇 鹅埠镇 赤石镇 鮜门镇 联安镇 陶河镇 赤坑镇 大湖镇 可塘镇 黄羌镇 平东镇 海城镇 公平镇 附城镇 城东镇]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_towns&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="s2"&gt;"汕尾市"&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="n"&gt;assert_empty&lt;/span&gt; &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_towns&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="s2"&gt;""&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="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;以后有位置数据需要更新，只要替换 json 文件就行，这样就能保证使用到最新的位置信息。&lt;/p&gt;

&lt;p&gt;哪里找最新的位置信息呢？&lt;/p&gt;

&lt;p&gt;我是在这里找的：&lt;a href="https://github.com/modood/Administrative-divisions-of-China" rel="nofollow" target="_blank"&gt;https://github.com/modood/Administrative-divisions-of-China&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;补充一下：&lt;/p&gt;

&lt;p&gt;我自己的项目关于 view 的用法被我封装了，可以用起来更简单：&lt;/p&gt;

&lt;p&gt;只要一行代码。&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&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cities_select&lt;/span&gt; &lt;span class="ss"&gt;:location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;town: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要出现镇就把 &lt;code&gt;town&lt;/code&gt; 设为 &lt;code&gt;true&lt;/code&gt;, 反之就没有。&lt;/p&gt;

&lt;p&gt;之所有这么简单是因为进行了封装，而且用了 &lt;a href="https://github.com/bootstrap-ruby/bootstrap_form" rel="nofollow" target="_blank" title=""&gt;bootstrap_form&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;可以参考了解一下（仅供参考）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializer/bootstrap_form.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;BootstrapForm&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FormBuilder&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cities_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;town: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;location_select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;province_select&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;city_select&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;county_select&lt;/span&gt;

      &lt;span class="n"&gt;location_select&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;town_select&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;town&lt;/span&gt;

      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"mb-3 row"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s2"&gt;"ts--cities-select"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;concat&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;label_col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;control_col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location_select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&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;province_select&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;select_without_bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;:province&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_provinces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"province"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"form-control"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"col"&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;city_select&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;select_without_bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_cities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"city"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"form-control"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"col"&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;county_select&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;select_without_bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;:county&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_counties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"county"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"form-control"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"col"&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;town_select&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;select_without_bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;:town&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="no"&gt;WhereStreets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_towns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;province&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;county&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"labels.please_select"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;ts__cities_select_target: &lt;/span&gt;&lt;span class="s2"&gt;"town"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"form-control"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"col"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要是这里 cities_select 这个方法。（具体自己看吧）&lt;/p&gt;

&lt;p&gt;最近别忘了 follow 和 star（换了一个新的 github 号)&lt;/p&gt;

&lt;p&gt;有问题可以一起交流，wechat: &lt;strong&gt;qiuzhi99pro&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;其它的等我慢慢更新。想学 ruby 和 rails 的可以看看我录制的教程哈：&lt;a href="https://www.qiuzhi99.com/playlists/ruby.html" rel="nofollow" target="_blank"&gt;https://www.qiuzhi99.com/playlists/ruby.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文链接：&lt;a href="https://www.qiuzhi99.com/articles/ruby/97610.html" rel="nofollow" target="_blank"&gt;https://www.qiuzhi99.com/articles/ruby/97610.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Wed, 07 Sep 2022 13:32:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/42633</link>
      <guid>https://ruby-china.org/topics/42633</guid>
    </item>
    <item>
      <title>hotwire-livereload 有人用吗，页面自动刷新的工具</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/kirillplatonov/hotwire-livereload" rel="nofollow" target="_blank"&gt;https://github.com/kirillplatonov/hotwire-livereload&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;看了下源码，原理就是去监听文件改变，然后利用 actioncable 广播到浏览器，让浏览器自动 Turbo.visit，相当于让页面修改生效&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hfpp2012/ab2fb372-a63b-4b07-a661-2b5cc4111c91.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hfpp2012/313bcd27-1972-4baf-a5c4-ecd37a412214.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Mon, 31 Jan 2022 20:37:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/42112</link>
      <guid>https://ruby-china.org/topics/42112</guid>
    </item>
    <item>
      <title>我用 Rails 7 集成了一个开源的漂亮的后台 Niceadmin</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hfpp2012/2de9f33b-690e-4f9b-9a2c-b1d3550d4601.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Niceadmin 是开源的，网络上可以下载到，我用  rails 7 把它集成了&lt;/p&gt;

&lt;p&gt;&lt;a href="https://start.rails365.net/admin" rel="nofollow" target="_blank"&gt;https://start.rails365.net/admin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;会集成各种功能，插件，主要是为了以后写代码方便。&lt;/p&gt;

&lt;p&gt;还没全部弄好，正在继续中。&lt;/p&gt;

&lt;p&gt;支持自动化部署：cap&lt;/p&gt;

&lt;p&gt;已经开源：&lt;a href="https://gitlab.qiuzhi99.com/hfpp2012/opensource_railstart" rel="nofollow" target="_blank"&gt;https://gitlab.qiuzhi99.com/hfpp2012/opensource_railstart&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Wed, 26 Jan 2022 12:49:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/42107</link>
      <guid>https://ruby-china.org/topics/42107</guid>
    </item>
    <item>
      <title>我用 homeland 搭建了一个创业者社区</title>
      <description>&lt;p&gt;一个人就是一家公司，分享独立创业者信息，远离通勤，远离 996，放下枷锁，走上创业之路&lt;/p&gt;

&lt;p&gt;&lt;a href="https://soho.qiuzhi99.com/" rel="nofollow" target="_blank"&gt;https://soho.qiuzhi99.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;分享一些创业相关的思想，观点，方法&lt;/p&gt;

&lt;p&gt;欢迎大家来玩&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hfpp2012/30481e02-2dad-4341-85ac-e931f49a2f33.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Wed, 01 Dec 2021 10:51:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/41943</link>
      <guid>https://ruby-china.org/topics/41943</guid>
    </item>
    <item>
      <title>利用好元编程重构下代码</title>
      <description>&lt;p&gt;重构前：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hfpp2012/57630e86-a21f-4104-8909-bca41181976b.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/hfpp2012/7b62b5ae-e7df-41fb-a96b-cb12f9a9c365.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 06 Apr 2021 14:46:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/41117</link>
      <guid>https://ruby-china.org/topics/41117</guid>
    </item>
    <item>
      <title>我的黑苹果之路</title>
      <description>&lt;p&gt;先上图
￼
&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2029/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;用过一台 MacBook Air（2015 年入手）还有一台 MacBook Pro（2017 年入手）之后，苹果笔记本对我来说写代码可以，但是我要剪辑视频，笔记本的散热不行（有声音），性能也够呛，所以准备入手台式机电脑，因为我平时都在家办公，放在房间里正合适。&lt;/p&gt;

&lt;p&gt;台式机的电脑比笔记本同价位的话，性能应该能强数个等级吧，又考虑到这台电脑要给老婆使用，老婆不会用 mac 也不会用触摸控，所以要装双系统：windows 10 加上最新的 Macos。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;先简单说一下安装过程：1. 购买硬件（硬件对了就基本不用怎么折腾了）2. 安装 windows 10 3. 安装黑苹果系统&lt;/p&gt;
&lt;h2 id="1. 购买硬件"&gt;1. 购买硬件&lt;/h2&gt;
&lt;p&gt;这里先说一下一个网站：&lt;a href="https://heipg.cn/tutorial/diy-hackintosh-2020.html" rel="nofollow" target="_blank"&gt;https://heipg.cn/tutorial/diy-hackintosh-2020.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;你的主要的硬件最好是在上面这个网站有列出的，可能是免驱动的。&lt;/p&gt;

&lt;p&gt;我是装机菜鸟，好几年没碰过这些硬件，所以是一边问，一边找适合的硬件，主要是要找到适合黑苹果，硬件要免驱动的，这个正好适合我这个菜鸟。&lt;/p&gt;

&lt;p&gt;硬件购买根据自己的预算和需求来，我这里大约花了&lt;strong&gt;一万四&lt;/strong&gt;左右。&lt;/p&gt;

&lt;p&gt;这里附上我的硬件：&lt;/p&gt;

&lt;p&gt;cpu ￼￼英特尔（Intel）i7-9700K 酷睿八核 盒装 CPU 处理器&lt;/p&gt;

&lt;p&gt;技嘉（GIGABYTE）Z390 AORUS PRO WIFI 主板 + 英特尔 i7 9700K 板 U 套装/主板+CPU 套装    ￥3499.00                         &lt;a href="https://item.jd.com/100001415316.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/100001415316.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;显卡：rx5700 XT 8G D6 超白金 OC 1770-1905MHz/ 14 Gbps 8GB/256bit GDDR6 DX12 游戏显卡 
           &lt;a href="https://item.jd.com/100008161504.html#crumb-wrap" rel="nofollow" target="_blank"&gt;https://item.jd.com/100008161504.html#crumb-wrap&lt;/a&gt;    ￥3299.00&lt;/p&gt;

&lt;p&gt;内存  金士顿 (Kingston) DDR4 3200 32G(16GX2) 套装 台式机内存条 骇客神条 Fury 雷电系列 ￥999.00  &lt;a href="https://item.jd.com/100007628368.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/100007628368.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;硬盘  三星（SAMSUNG）500GB SSD 固态硬盘 M.2 接口 (NVMe 协议) 970 EVO（MZ-V7E500BW） ￥599.00
&lt;a href="https://item.jd.com/7234518.html#crumb-wrap" rel="nofollow" target="_blank"&gt;https://item.jd.com/7234518.html#crumb-wrap&lt;/a&gt; 两块硬盘&lt;/p&gt;

&lt;p&gt;显示器：AOC 27 英寸 4K 高清 IPS 广视角 微框 99% sRGB 商用办公节能 低蓝光不闪旋转升降 PS4 液晶显示器 U2790PQU ￥1999.00&amp;nbsp;&lt;a href="https://item.jd.com/100001071956.html#crumb-wrap" rel="nofollow" target="_blank"&gt;https://item.jd.com/100001071956.html#crumb-wrap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;网卡驱动：黑苹果免驱 BCM94360CD 台式机 pcie 无线网卡双频 5G 1750M 蓝牙 4.0 WiFi 接收器 FV-T919(1750M 黑苹果免驱版）  ￥298.00 &lt;a href="https://item.jd.com/18967921252.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/18967921252.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;键盘：京东京造 K2 蓝牙双模机械键盘 背光 84 键双系统兼容 黑色茶轴白光无线键盘 键盘机械 蓝牙键盘 键盘无线 ¥418.00
       &lt;a href="https://item.jd.com/100012895086.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/100012895086.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;鼠标：赛睿（SteelSeries）Rival 650 无线/有线双模鼠标 游戏鼠标 RGB 鼠标 魔兽怀旧开服 鼠标 
        罗技（Logitech）M220 鼠标 无线鼠标 办公鼠标 静音鼠标 对称鼠标 灰黑色 带无线 2.4G&lt;/p&gt;

&lt;p&gt;音箱：JBL PS3300 无线蓝牙 2.0 音箱 电脑多媒体音箱/音响 桌面音箱 独立高低音炮 台式机手机音响 黑色 ￥399.00
         &lt;a href="https://item.jd.com/7536808.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/7536808.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;散热：￼￼利民（Thermalright）FS140 霜灵 双塔散热器全电镀 回流焊 8mm 热管 SFDB 风扇 6 年质保 TF7 硅脂 259&lt;/p&gt;

&lt;p&gt;电源：海韵 (SEASONIC)FOCUS GX750 750W 电源 80PLUS 金牌全模/十年质保/全日系电容/14cm 小身形/第 3 代静音风扇启停 ￥849.00  &lt;a href="https://item.jd.com/100007186412.html#crumb-wrap" rel="nofollow" target="_blank"&gt;https://item.jd.com/100007186412.html#crumb-wrap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;相箱： &lt;a href="https://item.jd.com/2917029.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/2917029.html&lt;/a&gt;  先马（SAMA）黑洞 3 黑色 全塔机箱（支持 ATX 主板/静音/宽大箱体/标配 3 把风扇/调速器/读卡器/ATX-Ⅲ结构） ￥399.00&lt;/p&gt;

&lt;p&gt;说一下硬件：&lt;/p&gt;

&lt;p&gt;总体而言，配置是绝对够的，而且是免驱动的，装黑苹果没啥问题&lt;/p&gt;

&lt;p&gt;为了剪视频，显卡贵了，cpu 和主板一般，内存买了两条，总共 32 g，以后不够再扩展，硬盘也是 sdd , 两条，为两个系统使用，容量不够再考虑，显示器，是入门级 4k，显示效果还可以，网卡驱动是为黑苹果使用的，还行，网速和信号比不上同级的 macbook pro 自带的网卡，键盘是机械的，也是第一次玩，茶轴，鼠标买了两个，没必要，有一个用于游戏的，好贵，要八百多块钱，另一个几十块钱，都是无线的。电源贵了，显卡供电是 8+8pin，所以弄了 750w，朋友说比较稳。音箱呢，用于听歌用。相箱好大好重。&lt;/p&gt;

&lt;p&gt;总体而言，这配置够用几年了，还是满意的。&lt;/p&gt;
&lt;h2 id="2. 安装硬件"&gt;2. 安装硬件&lt;/h2&gt;
&lt;p&gt;就先看视频，跟着视频做。&lt;/p&gt;

&lt;p&gt;这里贴个教程：&lt;a href="https://zhuanlan.zhihu.com/p/91459238" rel="nofollow" target="_blank"&gt;https://zhuanlan.zhihu.com/p/91459238&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;总体而言，就是按照安装视频来，再结合产品说明书。（因为视频的硬件跟你的可能不同，这时要按照产品说明书来）&lt;/p&gt;

&lt;p&gt;让我回忆下整个过程：&lt;/p&gt;

&lt;p&gt;先把主板放好，然后装上  CPU，涂上硅脂，再安装风扇。&lt;/p&gt;

&lt;p&gt;由于我电源是隔天才到货，我最后安装的电源，我觉得第二步应该是安装电源：&lt;/p&gt;

&lt;p&gt;接着把硬盘装上，当初好像要找螺丝柱来固定硬盘，然后装内存，再把主板接入到相箱中，接上电源，我用的全模组电源，要安装说明书来安装。要把机箱的开机重启线接好，还有各种 usb 的线也要接好，这些视频都有。&lt;/p&gt;
&lt;h2 id="3. 安装 windows 10"&gt;3. 安装 windows 10&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;有个问题可能要事先知道一下：如果你有外置的网卡，你应该把主板自带的蓝牙 网卡功能都屏蔽掉，在 bios 里处理，具体看主板说明书的&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;首先准备好一个 16 g 或 32 g 的 u 盘。&lt;/p&gt;

&lt;p&gt;我们要把这个 u 盘并写入系统镜像。&lt;/p&gt;

&lt;p&gt;用什么写呢，我用的是微 pe。&lt;/p&gt;

&lt;p&gt;下载微 pe。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.wepe.com.cn/" rel="nofollow" target="_blank"&gt;http://www.wepe.com.cn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后下载 windows 10 的最新镜像。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://msdn.itellyou.cn/" rel="nofollow" target="_blank"&gt;https://msdn.itellyou.cn/&lt;/a&gt; 这里有 windows 10 的镜像，你自己去找。&lt;/p&gt;

&lt;p&gt;然后运行微 pe，把镜像写入 u 盘，再重启设置为 u 盘启动，然后安装，这个过程简单。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;写盘成功后，会有三分分区，要把 win10 镜像解压复制到其中一个分区里去，用 u 盘启动后进行解压的镜像目录里执行 setup.exe 安装&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2023/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;这里注意的是：首先要有 windows 10 系统，不然用不了这个微 pe 工具，如果你没有 windows 系统，应该在别的系统也有别的工具来实现相同的目的，不过我有这个系统，就先这样简单安装了。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="4. 安装黑苹果"&gt;4. 安装黑苹果&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;强烈推荐这个：&lt;a href="https://github.com/shiruken/hackintosh" rel="nofollow" target="_blank"&gt;https://github.com/shiruken/hackintosh&lt;/a&gt;  (因为跟我的硬件很相似）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;只要是硬件对了，安装黑苹果就简单了，只要有两步就可以。&lt;/p&gt;

&lt;p&gt;第一步，还是跟安装 windows 系统一样，要写入镜像。&lt;/p&gt;

&lt;p&gt;准备好 u 盘，下载工具：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.balena.io/etcher/" rel="nofollow" target="_blank"&gt;https://www.balena.io/etcher/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个工具跨平台的。&lt;/p&gt;

&lt;p&gt;镜像的话，在黑果小兵里找最新的：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mp.weixin.qq.com/s/SSLHsDjDjqO7oDj_GNRGJQ" rel="nofollow" target="_blank"&gt;https://mp.weixin.qq.com/s/SSLHsDjDjqO7oDj_GNRGJQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我当初用的是这个。&lt;/p&gt;

&lt;p&gt;接下来呢，就是写入镜像。再用 u 盘启动安装程序&lt;/p&gt;

&lt;p&gt;这个过程也简单。&lt;/p&gt;

&lt;p&gt;安装完后还是无法用硬盘启动，只能用 u 盘启动。&lt;/p&gt;

&lt;p&gt;这个时候下载一个工具，解决，也是第二步。&lt;/p&gt;

&lt;p&gt;clover configurator&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.pc6.com/mac/294926.html" rel="nofollow" target="_blank"&gt;http://www.pc6.com/mac/294926.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个过程就是复制 efi 文件到硬盘中，复制完后就可以拔掉 u 盘启动，完成。&lt;/p&gt;

&lt;p&gt;这个过程可看这个教程：&lt;a href="https://youtu.be/RhAr0S12XdU" rel="nofollow" target="_blank"&gt;https://youtu.be/RhAr0S12XdU&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;几个由朋友提供的 efi 分享下：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kcunanan/Jared-PC/" rel="nofollow" target="_blank"&gt;https://github.com/kcunanan/Jared-PC/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Good0007/B365ITX-Hackintosh-OC" rel="nofollow" target="_blank"&gt;https://github.com/Good0007/B365ITX-Hackintosh-OC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Good0007/Asrock-Z390M-ITX-AC-Hackintosh-OC" rel="nofollow" target="_blank"&gt;https://github.com/Good0007/Asrock-Z390M-ITX-AC-Hackintosh-OC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;安装完 macOS 后我把睡眠功能关掉了（在设置里处理）&lt;/p&gt;

&lt;p&gt;最后看看跑分：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2030/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;￼&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2021/2020/7e60866b3b3e3700d18d9bf39218ed3a.jpeg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我的办公桌：&lt;/p&gt;

&lt;p&gt;￼&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2022/2020/286086751733aac2d47a09c2246e9ec8.jpeg" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;之前显卡我一直点不亮，我就换了个，现在一切可以了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;进去系统后，登录 App Store, 也登录 iCloud。&lt;/p&gt;

&lt;p&gt;我这边网卡 蓝牙 剪贴板 airdrop 都正常。&lt;/p&gt;

&lt;p&gt;现在还有一些问题要解决，比如鼠标滚动问题，这个能解决的，还有一些软件没安装，这个以后再说。&lt;/p&gt;
&lt;h2 id="5. 补充：黑苹果开启显卡硬解"&gt;5. 补充：黑苹果开启显卡硬解&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2026/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2027/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我对硬解的理解是这样的：&lt;/p&gt;

&lt;p&gt;充分利用 显卡，不会说看个电影啥的 cpu 占用达到 100%，有独显的就要开吧&lt;/p&gt;

&lt;p&gt;别人是这样说的：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;高分辨率视频（包括油管和各种直播网站）拖进度条秒切画面（不开硬解，CPU 能吃满）
渲染加速，节省时间
减少 CPU 使用率，温度控制等&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;参照这里解决了：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/shiruken/hackintosh#enable-the-discrete-graphics-card-with-headless-igpu" rel="nofollow" target="_blank"&gt;https://github.com/shiruken/hackintosh#enable-the-discrete-graphics-card-with-headless-igpu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;不过 bios 要设置成这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/2024/2020/d2b5ca33bd970f64a6301fa75ae2eb22.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;现在开机后会直接进 mac 系统，好像没有进 bios 页面（也许是有进入的，只是没有显示，反正就是黑屏），这个听朋友说可以用 dp 线解决，要试下才知道。&lt;/p&gt;

&lt;p&gt;这里说一下：&lt;/p&gt;

&lt;p&gt;IGFX 是系统会从内建显示功能输出，这样就可以识别核显。&lt;/p&gt;

&lt;p&gt;最后是这样解决的：&lt;/p&gt;

&lt;p&gt;我买了条 dp 线 &lt;a href="https://item.jd.com/68659990898.html" rel="nofollow" target="_blank"&gt;https://item.jd.com/68659990898.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;先把显示器的 hdmi 线接到主板上（不是显卡），然后设置 initial display output 为 pcie 1 slot（优先从显卡输出），然后关机，接着再把 hdmi 线拔出，再用 dp 线接到独显上，这样就解决了黑屏的问题。&lt;/p&gt;
&lt;h2 id="6. 黑苹果的问题"&gt;6. 黑苹果的问题&lt;/h2&gt;
&lt;p&gt;发生在这台机器的黑苹果的问题：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;无法正常关机，关机后会自动重启（两种方法解决，要么强制关机，要么重启进 windows 里关机）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;睡眠唤醒可能有问题（那么我就不睡眠了）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;亮度无法调整（好像很多人都有这问题）&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="7. 鼠标滚动解决"&gt;7. 鼠标滚动解决&lt;/h2&gt;
&lt;p&gt;黑苹果的鼠标并不能像白苹果一样平滑地滚动，而且方向也有跟其他系统不太一样。&lt;/p&gt;

&lt;p&gt;解决方法：&lt;a href="https://github.com/Caldis/Mos" rel="nofollow" target="_blank"&gt;https://github.com/Caldis/Mos&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Mon, 07 Sep 2020 16:13:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/40375</link>
      <guid>https://ruby-china.org/topics/40375</guid>
    </item>
    <item>
      <title>从 webpack 到全面拥抱 Parcel #1 探索 Parcel</title>
      <description>&lt;p&gt;原文发表于 &lt;a href="https://www.rails365.net/articles/cong-webpack-dao-quan-mian-yong-bao-parcel-1-tan-suo-parcel" rel="nofollow" target="_blank" title=""&gt;www.rails365.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近大家都在关注一个很流行的类似 &lt;a href="https://www.rails365.net/groups/webpack" rel="nofollow" target="_blank" title=""&gt;webpack&lt;/a&gt; 的前端构建工具 &lt;a href="https://github.com/parcel-bundler/parcel" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt;。这个库刚出来没多久 (好像截至目前十几天)，但是很受欢迎，看下图就知道。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/528/2017/d388957fb822e0beda409ff12815626d.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;所以值得一探！&lt;/p&gt;

&lt;p&gt;官方地址：&lt;a href="https://parceljs.org/" rel="nofollow" target="_blank"&gt;https://parceljs.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;github 地址：&lt;a href="https://github.com/parcel-bundler/parcel" rel="nofollow" target="_blank"&gt;https://github.com/parcel-bundler/parcel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍这个库之前，我们来说一下我个人觉得 &lt;a href="https://www.rails365.net/groups/webpack" rel="nofollow" target="_blank" title=""&gt;webpack&lt;/a&gt; 的一些不好的地方（相对于 Parcel）。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;需要写配置文件（webpack.config.js），可能每使用一个功能，比如加载图片或 css，都要添加配置，要维护配置文件，而 &lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 不需要。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;感觉编译或加载速度有些慢，特别是库多或项目复杂的时候，虽然有一些办法代码拆分的方法可以解决，比如 CommonsChunkPlugin 或 DLLPlugin 之类的，但这些方法有些复杂。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/530/2017/adfdca52f0340cc60b38f33f47ab59b2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;需要一定的时间去学习如何使用 webpack。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;而 &lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 有很多优点，可以不使用配置文件，也就是说你只管写代码，它会自动运行，很智能化，打个比方吧，比如在 webpack 中如果要处理 css，那得要安装和加载一个 css 的 loader，然后配置文件写上几行，可是 &lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 不需要，直接用就行。&lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 学习起来比较简单，基本上可以说 "不用学习"，只是使用就可以了。&lt;/p&gt;

&lt;p&gt;除此之外，模块热替换和代码拆分的功能，&lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 也有，还有，如果要你用 &lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 写一个 react 的运行环境，可能不需要配置任何内容，只要安装几个 react 的包就可以用起来了。&lt;/p&gt;

&lt;p&gt;说了这么多，我还是要把官方对它的特性进行概括的图片放出来：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/529/2017/e4caba8cf0a8deb8d3e9277708840cb8.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;下面我们要开始来体验 parcel 的神奇之处，请跟紧。(源码我放到 &lt;a href="https://github.com/hfpp2012/hello-parcel" rel="nofollow" target="_blank"&gt;https://github.com/hfpp2012/hello-parcel&lt;/a&gt;）&lt;/p&gt;
&lt;h2 id="1. 安装"&gt;1. 安装&lt;/h2&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install -g parcel-bundler
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后初始化一个项目。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir parcelapp
$ npm init
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2. 初体验"&gt;2. 初体验&lt;/h2&gt;
&lt;p&gt;新建 html 文件。（这个将会是 parcel 的入口文件）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width,initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"ie=edge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Joke Generator&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"wrap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Joke&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"joke"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"copy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./index.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;index.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log('Hello');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行编译命令。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ parcel index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：上面的 parcel 命令接的是 html 文件，它会读 html 文件的内容，找到 javascript 文件，进行自运处理，不用像 webpack 那样，还要指定 javascript 的入口文件啥的。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/526/2017/e79b83e5db5eed4ca2c0edc6d95abb85.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;生成了 &lt;code&gt;dist&lt;/code&gt; 目录。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist
├── index.html
└── parcelapp.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;监听在 1234 端口，浏览器效果如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/525/2017/eba648c49c12649d867020ef13443cac.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="3. CommonJS 模块语法"&gt;3. CommonJS 模块语法&lt;/h2&gt;
&lt;p&gt;新建 jokes.js 文件。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jokes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 这个 api 是公开的。&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://api.icndb.com/jokes/random&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;index.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;jokes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;joke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/527/2017/bb59bf3eae37572fdcb9ce77357ced52.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="4. ES6 模块语法"&gt;4. ES6 模块语法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;require&lt;/code&gt; 改成 &lt;code&gt;import&lt;/code&gt;，如下所示：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// const jokes = require('./jokes');&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;jokes&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="s1"&gt;./jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jokes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;joke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;joke&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;strong&gt;jokes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://api.icndb.com/jokes/random&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="5. 使用 axios 代替 fetch"&gt;5. 使用 &lt;a href="https://github.com/axios/axios" rel="nofollow" target="_blank" title=""&gt;axios&lt;/a&gt; 代替 fetch&lt;/h2&gt;
&lt;p&gt;这只是为了演示使用一些库。&lt;/p&gt;

&lt;p&gt;首先安装 &lt;a href="https://github.com/axios/axios" rel="nofollow" target="_blank" title=""&gt;axios&lt;/a&gt;。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;axios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意，这里每安装一个库，都要重新运行 parcel index.html&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jokes.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';

export const jokes = {
  getOne: function() {
    return new Promise((resolve, reject) =&amp;gt; {
      axios.get('http://api.icndb.com/jokes/random')
        .then(res =&amp;gt; {
          resolve(res.data.value.joke);
        })
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="6. 使用 jquery 代替 getElementById"&gt;6. 使用 jquery 代替 getElementById&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;jquery
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;index.js&lt;/strong&gt;&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;jokes&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="s1"&gt;./jokes&lt;/span&gt;&lt;span class="dl"&gt;'&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;$&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;jquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jokes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOne&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// document.getElementById('joke').innerHTML = joke;&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#joke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;joke&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="7. 导入 非 JavaScript 资源"&gt;7. 导入 非 JavaScript 资源&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;copyright.txt&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copyright 2018
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;index.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/copyright.txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="8. 简单处理 css"&gt;8. 简单处理 css&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;style.css&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#wrap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.12&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;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Joke Generator&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="9. 在 css 中使用 import"&gt;9. 在 css 中使用 import&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;backgrounds.css&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f4f4f4&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;strong&gt;style.css&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'./backgrounds.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="10. 使用 sass"&gt;10. 使用 sass&lt;/h2&gt;
&lt;p&gt;首先，安装 node-sass。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;node-sass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里要花费一定时间，请耐心等待&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;backgrounds.scss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：这里由 backgrounds.css 改名为 backgrounds.scss&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'./variables.scss'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;light-grey&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;strong&gt;variables.scss&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;light-grey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;#f4f4f4&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;style.css&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* 改名为 scss */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'./backgrounds.scss'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://parceljs.org/" rel="nofollow" target="_blank" title=""&gt;Parcel&lt;/a&gt; 还有很多好玩的，我们以后再说。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 19 Dec 2017 19:32:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/34733</link>
      <guid>https://ruby-china.org/topics/34733</guid>
    </item>
    <item>
      <title>如何用 GitBook 结合 markdown 写一本开源书籍</title>
      <description>&lt;p&gt;直接进入正题。&lt;/p&gt;
&lt;h2 id="1. 安装 gitbook 并写书"&gt;1. 安装 gitbook 并写书&lt;/h2&gt;
&lt;p&gt;首先，安装。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install -g gitbook-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你没有 nodejs 环境，你就去官网下载安装一下，安装完就有 npm 命令了。&lt;/p&gt;

&lt;p&gt;新建一个目录放书的内容，比如叫 &lt;code&gt;webpack&lt;/code&gt;，这个目录里是放书的内容。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;webpack
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;webpack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后初始化生成文件。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gitbook init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成了下面两个文件。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
└── SUMMARY.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;README.md&lt;/code&gt; 是放书的说明，&lt;code&gt;SUMMARY.md&lt;/code&gt; 是放书的目录。&lt;/p&gt;

&lt;p&gt;看了我的内容，你应该就会明白如何写的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;README.md&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# webpack 3 零基础入门教程&lt;/span&gt;

最详细，最简单的零基础 webpack 3 入门教程，人人都能学会。

原文发布于我的个人博客：https://www.rails365.net

源码位于：https://github.com/yinsigan/webpack-tutorial

电子版: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.gitbook.com/download/pdf/book/yinsigan/webpack-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;Mobi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;(https://www.gitbook.com/download/mobi/book/yinsigan/webpack-3) &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ePbu&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.gitbook.com/download/epub/book/yinsigan/webpack-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="gu"&gt;### 联系我:&lt;/span&gt;

email: hfpp2012@gmail.com

qq: 903279182
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SUMMARY&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Summary&lt;/span&gt;
&lt;span class="p"&gt;
*&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;0. 开始&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;README.md&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="nv"&gt;1. 介绍&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/1.md&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="nv"&gt;2. 安装&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/2.md&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="nv"&gt;3. 实现 hello world&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/3.md&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="nv"&gt;4. webpack 的配置文件 webpack.config.js&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/4.md&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="nv"&gt;5. 使用第一个 webpack 插件 html-webpack-plugin&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/5.md&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="nv"&gt;6. 使用 loader 处理 CSS 和 Sass&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/6.md&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="nv"&gt;7. 初识 webpack-dev-server&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;chapters/7.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目录结构：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── SUMMARY.md
└── chapters
    ├── 1.md
    ├── 2.md
    ├── 3.md
    ├── 4.md
    ├── 5.md
    ├── 6.md
    └── 7.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在终端运行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gitbook serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/466/2017/5e2840a2da0a246fbeaf7b33b61fa364.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样，可以用浏览器打开 &lt;code&gt;http://localhost:4000&lt;/code&gt;，一边写书，一边看效果。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/467/2017/a552f0086388c09c3a5dd78ff61ca852.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="2. 把书的源码放到 github 上"&gt;2. 把书的源码放到 github 上&lt;/h2&gt;
&lt;p&gt;现在我们准备把写好的书，做成一个 git 仓库，再放到 github 上。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git init
&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"first commit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新建一个文件 &lt;code&gt;.gitignore&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;内容如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Node rules:
## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

## Dependency directory
## Commenting this out is preferred by some people, see
## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
node_modules

# Book build output
_book

# eBook build output
*.epub
*.mobi
*.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是为了防止提交一些不必要的内容。&lt;/p&gt;

&lt;p&gt;在 github 上新建一个仓库叫 &lt;code&gt;webpack-tutorial&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git remote add origin git@github.com:yinsigan/webpack-tutorial.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin master
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3. gitbook 新建书"&gt;3. gitbook 新建书&lt;/h2&gt;
&lt;p&gt;进入 &lt;a href="https://www.gitbook.com/" rel="nofollow" target="_blank"&gt;https://www.gitbook.com/&lt;/a&gt; 网站。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/468/2017/24b1cc70c94e9195fb175d85ba1626d4.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/470/2017/5af03ab00272a0c0d5d0ec90c192d93e.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/471/2017/c9d8aa5c7deb3a226e66160b984e7015.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/473/2017/d0eebeeff005439c6545b720b57e2cbe.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/474/2017/366286fab917322f0819d585ad88be67.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/475/2017/e313a3086ecc35a5ee47ace0f306c6b5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;现在可以在线浏览了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/476/2017/2acfd1447d1c3cf7ed05f21975cbff2b.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;网址为：&lt;a href="https://www.gitbook.com/read/book/yinsigan/webpack-3" rel="nofollow" target="_blank"&gt;https://www.gitbook.com/read/book/yinsigan/webpack-3&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="4. 绑定个性域名"&gt;4. 绑定个性域名&lt;/h2&gt;
&lt;p&gt;你如果有你自己的域名也是可以绑定过来的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/477/2017/b7296c8b29758fb5a53f7607dfbafe3c.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/478/2017/b0d8b0be48ecfe7967c7f0ef86143774.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;现在就可以用 &lt;a href="http://webpack.rails365.net/" rel="nofollow" target="_blank"&gt;http://webpack.rails365.net/&lt;/a&gt; 来访问了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/479/2017/5076310c653081f343db1a92536b7b33.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;完结。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Sat, 25 Nov 2017 20:28:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/34627</link>
      <guid>https://ruby-china.org/topics/34627</guid>
    </item>
    <item>
      <title>使用 Rails 自带的 Form Builder 来重构你的 Form</title>
      <description>&lt;p&gt;原文链接： &lt;a href="https://www.rails365.net/articles/shi-yong-rails-zi-dai-de-form-builder-lai-chong-gou-ni-de-form" rel="nofollow" target="_blank" title=""&gt;使用 rails 自带的 Form Builder 来重构你的 Form&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="1. 介绍"&gt;1. 介绍&lt;/h4&gt;
&lt;p&gt;开发一个项目，你可能难以避免使用到表单，特别是后台项目的增删改查，你就可能使用得更多。&lt;/p&gt;

&lt;p&gt;这些表单，可能内容和格式都差不多，或许你就用 bootstrap 这样组件来格式化你的表单。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/189/preview_2016/422ec89e6206dbf52e273cfcf8147d2c.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;有时候，你要写好多代码，且是重复的，来看下下面的代码：&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="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@executive&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-horizontal form-label-left'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-parsley-validate"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="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="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@executive.errors.any&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error_explanation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@executive.errors.count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;个错误:"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@executive.errors.full_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-2 control-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;名称&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;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="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vi"&gt;@executive.parent_id.present&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-2 control-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;所属产品系列&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_select&lt;/span&gt; &lt;span class="ss"&gt;:parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@executives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="s2"&gt;"选择产品系列"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hr-line-dashed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-4 col-sm-offset-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'提交'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'btn btn-primary'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样大约有 31 行代码，可以把它精简成 10 行 (有空行) 那样。&lt;/p&gt;

&lt;p&gt;除去&lt;code&gt;form_for&lt;/code&gt;那行，先不看，来看第一部分，就是显示错误的，如果表单提交不成功，把错误信息显示出来。&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="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@executive.errors.any&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error_explanation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@executive.errors.count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;个错误:"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@executive.errors.full_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似这样的代码，有时候换了另一个表对象，你又要重新写一次：&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="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@category.errors.any&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error_explanation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@category.errors.count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;个错误:"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@category.errors.full_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只是把&lt;code&gt;@executive&lt;/code&gt;换成了&lt;code&gt;@category&lt;/code&gt;而已，却要写这么多行代码，就算是复制，看着也不爽，能不能把它们都简化呢？&lt;/p&gt;

&lt;p&gt;能的，像上面刚才那样的代码，我只需要一行。&lt;/p&gt;

&lt;p&gt;包括下面的代码：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-2 control-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;名称&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;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="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的代码结构都差不多，只是不同的字段在换而已。&lt;/p&gt;

&lt;p&gt;统统简化一下。&lt;/p&gt;
&lt;h4 id="2. 使用Form Builder"&gt;2. 使用 Form Builder&lt;/h4&gt;
&lt;p&gt;我们不需要使用第三方的插件，rails 默认就有这样的功能，让你重新修改系统的 tag 或重新定义自己的 tag。&lt;/p&gt;

&lt;p&gt;先在&lt;code&gt;app&lt;/code&gt;下新建一个目录叫&lt;code&gt;form_builders&lt;/code&gt;，再在里面新建一个文件叫&lt;code&gt;bootstrap_form_builder.rb&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/form_builders/bootstrap_form_builder.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BootstrapFormBuilder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FormBuilder&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:content_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :@template&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;error_messages&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s1"&gt;'error_explanation'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:h3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ul&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_safe&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;config/application.rb&lt;/code&gt;文件中加入这行配置：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autoload_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'form_builders'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;按照#10 楼的建议，在 rails 5 中这里需要把&lt;code&gt;autoload_paths&lt;/code&gt;改成&lt;code&gt;eager_load_paths&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;再到&lt;code&gt;app/helpers/application_helper.rb&lt;/code&gt;文件中添加一个方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bootstrap_form_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&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;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:builder&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BootstrapFormBuilder&lt;/span&gt;
    &lt;span class="n"&gt;form_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&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;block&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;code&gt;bootstrap_form_for&lt;/code&gt;这个方法来代替默认的&lt;code&gt;form_for&lt;/code&gt;方法了。&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;bootstrap_form_for&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@executive&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-horizontal form-label-left'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-parsley-validate"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="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;error_messages&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

 ...
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到没有，之前显示错误信息那里是好几行代码的，现在被简化成了一行代码。&lt;/p&gt;

&lt;p&gt;我们接下来把显示&lt;code&gt;名称&lt;/code&gt;和&lt;code&gt;所属产品系列&lt;/code&gt;这两个地方也简化一下。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;app/form_builders/bootstrap_form_builder.rb&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;BootstrapFormBuilder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FormBuilder&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:content_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :@template&lt;/span&gt;

  &lt;span class="sx"&gt;%w( text_field text_area url_field file_field collection_select select )&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&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="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'col-sm-2 control-label'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'col-sm-10'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;error_messages&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在就可以只用&lt;code&gt;&amp;lt;%= f.text_field :name, required: 'required', class: 'form-control' %&amp;gt;&lt;/code&gt;来代替之前的好多行代码。&lt;/p&gt;

&lt;p&gt;把那些多余的&lt;code&gt;div&lt;/code&gt;，&lt;code&gt;class&lt;/code&gt;删除掉即可。&lt;/p&gt;

&lt;p&gt;这个可以看下面最后的结果。&lt;/p&gt;

&lt;p&gt;同样的提交按钮，也能处理，在&lt;code&gt;BootstrapFormBuilder&lt;/code&gt;中增加下面这个方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'col-sm-4 col-sm-offset-2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;super&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;回到前面。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= form_for [:admin, @executive], html: { class: 'form-horizontal form-label-left', "data-parsley-validate" =&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="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="sx"&gt;%&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有好多 class，都是一样的，我也不想重复，想改成这样写。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= bootstrap_form_for [:admin, @executive] do |f| %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到 helper 方法中，修改一下。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bootstrap_form_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&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;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:class&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'form-horizontal form-label-left'&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"data-parsley-validate"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:builder&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BootstrapFormBuilder&lt;/span&gt;
  &lt;span class="n"&gt;form_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&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;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后精简后的代码可能是这样的子：&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;bootstrap_form_for&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@executive&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;error_messages&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="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&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;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;:level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vi"&gt;@executive.parent_id.present&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_select&lt;/span&gt; &lt;span class="ss"&gt;:parent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@executives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="s2"&gt;"选择产品系列"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hr-line-dashed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&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="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'提交'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'btn btn-primary'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比之前精简多了。&lt;/p&gt;

&lt;p&gt;这样的代码又不影响正常的 form_for，你还可以继续使用默认的 form_for 下的各种 tag，没有影响，当你要使用自己定义的 form_for，只要改成自己定义的就可以了。&lt;/p&gt;
&lt;h4 id="3. 加一个额外的功能"&gt;3. 加一个额外的功能&lt;/h4&gt;
&lt;p&gt;你的应用可能有好多图片编辑的功能，比如下面这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://rails365.oss-cn-shenzhen.aliyuncs.com/uploads/photo/image/190/preview_2016/9a3666f97972290b50d03722bc721dd9.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;编辑的时候需要显示图片。&lt;/p&gt;

&lt;p&gt;你可能会这样写：&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="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product.image_url.present&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-2 control-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="vi"&gt;@product.image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="s1"&gt;'100'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;height: &lt;/span&gt;&lt;span class="s1"&gt;'100'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-2 control-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;图片&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-sm-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@product.new_record&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我把它精简成了一行&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_file_field&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@product.new_record&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-control'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;按照#6 楼的建议 &lt;code&gt;@product.new_record? ? true : false&lt;/code&gt;改成&lt;code&gt;@product.new_record?&lt;/code&gt;会好点。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;参考一下我的写法。&lt;/p&gt;

&lt;p&gt;还是到&lt;code&gt;bootstrap_form_builder.rb&lt;/code&gt;文件中增加下面几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;image_file_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;image_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_url"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
  &lt;span class="n"&gt;image_cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_cache"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:image_version&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:image_version&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'form-group'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;nbsp;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_safe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'col-sm-2 control-label'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'col-sm-10'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="s1"&gt;'100'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;height: &lt;/span&gt;&lt;span class="s1"&gt;'100'&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="n"&gt;file_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hidden_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;file_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag_value&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;code&gt;image_tag&lt;/code&gt;这个方法，没关系。&lt;/p&gt;

&lt;p&gt;改一下最前面的&lt;code&gt;delegate&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;BootstrapFormBuilder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FormBuilder&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:content_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:image_tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :@template&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;缺少啥 helper 方法或 tag 方法，就在&lt;code&gt;delegate&lt;/code&gt;后面添加就好了。&lt;/p&gt;

&lt;p&gt;对了，最后你可能会发现结果表单中的字段名怎么变成英文输出了，这没关系，你加上 i18n 国际化就好了，会全中文的。&lt;/p&gt;

&lt;p&gt;完结。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 13 Dec 2016 20:06:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/31898</link>
      <guid>https://ruby-china.org/topics/31898</guid>
    </item>
    <item>
      <title>RSpec 全套测试环境搭建从零入门</title>
      <description>&lt;h4 id="1. 缘起"&gt;1. 缘起&lt;/h4&gt;
&lt;p&gt;要帮朋友的项目搭一套测试环境，选用了 rspec 作为测试环境，在搭建的过程中记录整个过程，以后让这种重复的工作简单点，不想又造轮子。&lt;/p&gt;
&lt;h4 id="2. 能学到什么"&gt;2. 能学到什么&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;各种 rspec 测试标配&lt;/li&gt;
&lt;li&gt;使用 spring 加速测试过程，也会和 guard 结合，加速 guard 的运行过程&lt;/li&gt;
&lt;li&gt;使用 guard 让文件一保存就自动测试&lt;/li&gt;
&lt;li&gt;使用 capybara 写类似于 cucumber 的 feature 测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3. 安装过程"&gt;3. 安装过程&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;由于&lt;code&gt;guard&lt;/code&gt;的安装需要高版本的 ruby 支持，建议使用 ruby 2.3.0 以上。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;首先安装&lt;code&gt;rspec&lt;/code&gt;这个 gem，我们选用的是适合 rails 项目的&lt;a href="https://github.com/rspec/rspec-rails" rel="nofollow" target="_blank" title=""&gt;rspec-rails&lt;/a&gt;这个 gem。&lt;/p&gt;
&lt;h5 id="3.1 rspec-rails"&gt;3.1 rspec-rails&lt;/h5&gt;
&lt;p&gt;在 Gemfile 文件里加入下面这几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rspec-rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 3.4'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行&lt;code&gt;bundle install&lt;/code&gt;安装。&lt;/p&gt;

&lt;p&gt;装好之后，执行下面的两行命令，生成必要的配置文件。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate rspec:install
bundle binstubs rspec-core
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="3.2 factory_girl_rails"&gt;3.2 factory_girl_rails&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/thoughtbot/factory_girl_rails" rel="nofollow" target="_blank" title=""&gt;factory_girl_rails&lt;/a&gt;是一个代替测试夹具 (Fixtures) 的工具，用它可以在测试的时候造一些实例。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'factory_girl_rails'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行&lt;code&gt;bundle install&lt;/code&gt;安装。&lt;/p&gt;

&lt;p&gt;接下来找到&lt;code&gt;spec/rails_helper.rb&lt;/code&gt;文件的下面一行，把其删除或注释掉。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_transactional_fixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;FactoryGirl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Syntax&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Methods&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再到&lt;code&gt;config/application.rb&lt;/code&gt;文件中，添加下面几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generators&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;g&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_framework&lt;/span&gt; &lt;span class="ss"&gt;:rspec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;fixtures: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;view_specs: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;helper_specs: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;routing_specs: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;request_specs: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fixture_replacement&lt;/span&gt; &lt;span class="ss"&gt;:factory_girl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dir: &lt;/span&gt;&lt;span class="s1"&gt;'spec/factories'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了方便后绪的 demo 测试，现在我们都可以生成一个夹具文件，名字叫&lt;code&gt;users.rb&lt;/code&gt;，位于&lt;code&gt;spec/factories&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;FactoryGirl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;                    &lt;span class="s1"&gt;'user@example.com'&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt;                 &lt;span class="s1"&gt;'name'&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt;                 &lt;span class="s1"&gt;'password'&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;h5 id="3.3 database_cleaner"&gt;3.3 database_cleaner&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/DatabaseCleaner/database_cleaner" rel="nofollow" target="_blank" title=""&gt;database_cleaner&lt;/a&gt;是一个自动清除数据库数据的工具，每次运行完测试用例它就会自动清除数据库。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gemfile&lt;/code&gt;中添加下面几行。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'database_cleaner'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行&lt;code&gt;bundle install&lt;/code&gt;安装。&lt;/p&gt;

&lt;p&gt;然后在&lt;code&gt;spec/rails_helper.rb&lt;/code&gt;文件中添加下面几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:suite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;DatabaseCleaner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:transaction&lt;/span&gt;
  &lt;span class="no"&gt;DatabaseCleaner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:truncation&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&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;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;DatabaseCleaner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleaning&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;p&gt;新建文件&lt;code&gt;spec/models/user_spec.rb&lt;/code&gt;，内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :model&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'#email'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'有效的邮箱'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w( user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn )&lt;/span&gt;
      &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;valid_address&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;valid_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="kp"&gt;true&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="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'空'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="kp"&gt;false&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;context&lt;/span&gt; &lt;span class="s1"&gt;'错误邮箱格式'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w{ invalid_email_format 123 $$$ () ☃ bla@bla. }&lt;/span&gt;
      &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;invalid_address&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;invalid_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="kp"&gt;false&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="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'重复'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_with_same_username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_with_same_username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用&lt;code&gt;bundle exec rspec spec/models/user_spec.rb&lt;/code&gt;来测试这个刚才写的测试案例。&lt;/p&gt;

&lt;p&gt;你会发现运行起来还是比较慢的。&lt;/p&gt;

&lt;p&gt;接下来我们使用&lt;code&gt;spring&lt;/code&gt;来加速测试的运行。&lt;/p&gt;
&lt;h5 id="3.4 spring-commands-rspec"&gt;3.4 spring-commands-rspec&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;spring&lt;/code&gt;是 rails 默认就有的，而&lt;a href="https://github.com/jonleighton/spring-commands-rspec" rel="nofollow" target="_blank" title=""&gt;spring-commands-rspec&lt;/a&gt;是让&lt;code&gt;spring&lt;/code&gt;和&lt;code&gt;rspec&lt;/code&gt;结合起来。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gemfile&lt;/code&gt;中添加下面这行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'spring-commands-rspec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group: :development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着执行&lt;code&gt;bundle exec spring binstub rspec&lt;/code&gt;这条指令。&lt;/p&gt;

&lt;p&gt;现在我们就可以使用&lt;code&gt;bundle exec spring rspec&lt;/code&gt;来加速测试的运行了。&lt;/p&gt;
&lt;h5 id="3.5 guard"&gt;3.5 guard&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/guard/guard" rel="nofollow" target="_blank" title=""&gt;guard&lt;/a&gt;是一个让你一修改测试文件，就自动跑测试的工具。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;需要注意的是&lt;code&gt;guard&lt;/code&gt;需要高版本的 ruby 支持，目前为止，它官方写的是至少需要 ruby 2.2.5 以上&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gemfile&lt;/code&gt;中添加下面几行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'guard'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'guard-rspec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'guard-bundler'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至于&lt;code&gt;guard-rspec&lt;/code&gt;和&lt;code&gt;guard-bundler&lt;/code&gt;是&lt;code&gt;guard&lt;/code&gt;的两个插件，是分别让&lt;code&gt;guard&lt;/code&gt;和&lt;code&gt;rspec&lt;/code&gt;还有&lt;code&gt;bundler&lt;/code&gt;结合。&lt;/p&gt;

&lt;p&gt;分别执行下面几行指令。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;guard init
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;guard init rspec
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;guard init bundler
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着找到&lt;code&gt;Guardfile&lt;/code&gt;文件，找到第一行未注释的代码，修改成类似下面这样。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="ss"&gt;:rspec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cmd: &lt;/span&gt;&lt;span class="s1"&gt;'spring rspec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;all_on_start: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在可以运行&lt;code&gt;bundle exec guard&lt;/code&gt;执行测试了，也能监控文件的更改，测试文件一旦有修改，也会马上运行测试。&lt;/p&gt;

&lt;p&gt;让&lt;code&gt;spring&lt;/code&gt;和&lt;code&gt;guard&lt;/code&gt;结合之后，运行测试是很快的。&lt;/p&gt;
&lt;h5 id="3.6 capybara"&gt;3.6 capybara&lt;/h5&gt;
&lt;p&gt;&lt;a href="https://github.com/jnicklas/capybara" rel="nofollow" target="_blank" title=""&gt;capybara&lt;/a&gt;是一个可以编写 feature 测试的工具，它可以编写 BDD 测试、模拟浏览器点击，填充表单的测试功能。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gemfile&lt;/code&gt;中添加下面一行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'capybara'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行&lt;code&gt;bundle install&lt;/code&gt;安装。&lt;/p&gt;

&lt;p&gt;安装完之后，找到&lt;code&gt;spec/rails_helper.rb&lt;/code&gt;文件添加下面一行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'capybara/rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们可以编写一个 feature 测试案例。&lt;/p&gt;

&lt;p&gt;文件名为&lt;code&gt;spec/features/login_spec.rb&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'登录功能'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :feature&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;new_user_session_path&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'登录'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'没有填任何信息就点登录'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'登录'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'登录账号或密码错误'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'输入邮箱成功登录'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#new_user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'登录'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt; &lt;span class="s1"&gt;'登录成功'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_current_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_path&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;it&lt;/span&gt; &lt;span class="s1"&gt;'输入用户名成功登录'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#new_user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'登录'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt; &lt;span class="s1"&gt;'登录成功'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'注销'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_current_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_path&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;it&lt;/span&gt; &lt;span class="s1"&gt;'密码错误的失败登录'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#new_user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'user_password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="s1"&gt;'wrong_password'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'登录'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt; &lt;span class="s1"&gt;'登录账号或密码错误'&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_current_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_session_path&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;p&gt;本篇完结。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 07 Jun 2016 15:42:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/30233</link>
      <guid>https://ruby-china.org/topics/30233</guid>
    </item>
    <item>
      <title>我写了 WebSocket 和 ActionCable 相关的序列文章</title>
      <description>&lt;p&gt;从 websocket 介绍开始，到 actioncable 的介绍，应用，最后到部署，一共写了 10 篇关于 websocket 的文章。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2017.09.02 把这些文章汇聚成了 gitbook 小书，地址是：&lt;a href="https://websocket.rails365.net/" rel="nofollow" target="_blank"&gt;https://websocket.rails365.net/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;下面是目录：&lt;/p&gt;
&lt;h4 id="1. websocket之入门(一)"&gt;1. &lt;a href="http://www.rails365.net/articles/websocket-zhi-ru-men-yi" rel="nofollow" target="_blank" title=""&gt;websocket 之入门 (一)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="2. websocket之客户端与服务器端的交互(二)"&gt;2. &lt;a href="http://www.rails365.net/articles/websocket-zhi-ke-hu-duan-yu-fu-wu-qi-duan-di-jiao-hu-er" rel="nofollow" target="_blank" title=""&gt;websocket 之客户端与服务器端的交互 (二)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="3. websocket之客户端详解(三)"&gt;3. &lt;a href="http://www.rails365.net/articles/websocket-zhi-ke-hu-duan-xiang-jie-san" rel="nofollow" target="_blank" title=""&gt;websocket 之客户端详解 (三)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="4. websocket之实现简易聊天室(四)"&gt;4. &lt;a href="http://www.rails365.net/articles/websocket-zhi-shi-xian-jian-yi-liao-tian-shi-si" rel="nofollow" target="_blank" title=""&gt;websocket 之实现简易聊天室 (四)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="5. websocket之用tubesock在rails实现聊天室(五)"&gt;5. &lt;a href="http://www.rails365.net/articles/websocket-zhi-yong-tubesock-zai-rails-shi-xian-liao-tian-shi-wu" rel="nofollow" target="_blank" title=""&gt;websocket 之用 tubesock 在 rails 实现聊天室 (五)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="6. websocket之message_bus(六)"&gt;6. &lt;a href="http://www.rails365.net/articles/websocket-message-bus-liu" rel="nofollow" target="_blank" title=""&gt;websocket 之 message_bus(六)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="7. websocket之actioncable入门(七)"&gt;7. &lt;a href="http://www.rails365.net/articles/websocket-zhi-actioncable-ru-men-qi" rel="nofollow" target="_blank" title=""&gt;websocket 之 actioncable 入门 (七)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="8. websocket之actioncable进阶(八)"&gt;8. &lt;a href="http://www.rails365.net/articles/websocket-zhi-actioncable-jin-jie-ba" rel="nofollow" target="_blank" title=""&gt;websocket 之 actioncable 进阶 (八)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="9. websocket之actioncable实现重新连接功能(九)"&gt;9. &lt;a href="http://www.rails365.net/articles/websocket-zhi-actioncable-shi-xian-chong-xin-lian-jie-gong-neng-jiu" rel="nofollow" target="_blank" title=""&gt;websocket 之 actioncable 实现重新连接功能 (九)&lt;/a&gt;
&lt;/h4&gt;&lt;h4 id="10. websocket之部署(十)"&gt;10. &lt;a href="http://www.rails365.net/articles/websocket-zhi-bu-shu-shi" rel="nofollow" target="_blank" title=""&gt;websocket 之部署 (十)&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;把第七篇列在 ruby-china 预览一下，如果有兴趣我也可以把每篇文章都发到 ruby-china 上。&lt;/p&gt;
&lt;h3 id="websocket之actioncable入门(七)"&gt;websocket 之 actioncable 入门 (七)&lt;/h3&gt;&lt;h4 id="1. 介绍"&gt;1. 介绍&lt;/h4&gt;
&lt;p&gt;websocket 的序列文章重点要讲的就是&lt;a href="https://github.com/rails/rails/tree/master/actioncable" rel="nofollow" target="_blank" title=""&gt;actioncable&lt;/a&gt;，之前也讲了好多关于各种方式实现聊天室的文章，相信从中，也能学到好多关于 websocket 实践的知识和经验，这节要来讲讲 actioncable。&lt;/p&gt;

&lt;p&gt;actioncable 是集成在 rails 5 中的一个功能，它能够轻易的在 rails 中使用 websocket。现在先把 actioncable 用起来，再慢慢研究其原理和特性。&lt;/p&gt;
&lt;h4 id="2. 使用"&gt;2. 使用&lt;/h4&gt;
&lt;p&gt;还是跟先前的例子一样，建立一个聊天室。&lt;/p&gt;
&lt;h5 id="2.1 聊天室界面"&gt;2.1 聊天室界面&lt;/h5&gt;
&lt;p&gt;首先，rails 的版本必须得是 5 以上，写这篇文章的时候，rails 5 正式版还没有出来，目前的版本是 5.0.0.beta4。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails new actioncable_demo
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;actioncable_demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就生成了一个新项目。&lt;/p&gt;

&lt;p&gt;接着创建 message 这个 model，存储聊天记录。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails g model message content:text
&lt;span class="nv"&gt;$ &lt;/span&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建聊天室的界面。&lt;/p&gt;

&lt;p&gt;在 config/routes.rb 中添加路由。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'rooms/show'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 controller，添加&lt;code&gt;app/controllers/rooms_controller.rb&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;RoomsController&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="vi"&gt;@messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;添加 view，添加&lt;code&gt;app/views/rooms/show.html.erb&lt;/code&gt;文件，内容如下：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Chat room&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"messages"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@messages&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Say something:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;data-behavior=&lt;/span&gt;&lt;span class="s"&gt;"room_speaker"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有&lt;code&gt;app/views/messages/_message.html.erb&lt;/code&gt;文件，内容如下：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;“message”&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到目前为止，按照之前的经验，界面都建立好了，如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://aliyun.rails365.net/uploads/photo/image/149/preview_2016/410900b255c58c5f25596b9017dec860.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h5 id="2.2 开启websocket"&gt;2.2 开启 websocket&lt;/h5&gt;
&lt;p&gt;接下来，就是要来处理 websocket 部分。&lt;/p&gt;

&lt;p&gt;先在客户端浏览器中开启 websocket 请求。&lt;/p&gt;

&lt;p&gt;actioncable 默认提供了一个文件&lt;code&gt;app/assets/javascripts/cable.coffee&lt;/code&gt;，把几行注释打开，就可以开启 websocket，内容如下：&lt;/p&gt;
&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#= require action_cable&lt;/span&gt;
&lt;span class="c1"&gt;#= require_self&lt;/span&gt;
&lt;span class="c1"&gt;#= require_tree ./channels&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="vi"&gt;@&lt;/span&gt;&lt;span class="na"&gt;App&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createConsumer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实这些 js 的内容很简单，它做的主要的事情就是前面几篇文章所讲的在客户端浏览器执行&lt;code&gt;new WebSocket&lt;/code&gt;，具体的内容可以查看其源码。&lt;/p&gt;

&lt;p&gt;还要在路由中添加下面这行，把 websocket 服务以 engine 的方式挂载起来。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/cable'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，websocket 已经开启了，可以通过 chrome 浏览器的开发者工具查看链接的信息，只要有 101 协议的信息，表示就是成功的。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://aliyun.rails365.net/uploads/photo/image/150/preview_2016/b3069a500b027d123014a5b9bf68a4e0.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h5 id="2.3 channel"&gt;2.3 channel&lt;/h5&gt;
&lt;p&gt;现在要让客户端和服务器端连接起来。&lt;/p&gt;

&lt;p&gt;actioncable 提供了一个叫做&lt;code&gt;channel&lt;/code&gt;的技术，中文名可以称为&lt;code&gt;"通道"&lt;/code&gt;。actioncable 是一种&lt;code&gt;pub/sub&lt;/code&gt;的架构，服务器通过 channel 发布消息，多个客户端通过对应的 channel 订阅消息，服务器能够广播消息给客户端，从而实现客户端和服务器端的交互。&lt;/p&gt;

&lt;p&gt;先新建一个 channel。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails g channel room speak
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;app/channels/room_channel.rb&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;RoomChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s2"&gt;"room_channel"&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;unsubscribed&lt;/span&gt;
    &lt;span class="c1"&gt;# Any cleanup needed when channel is unsubscribed&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;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ActionCable.server.broadcast "room_channel", message: data['message']&lt;/span&gt;
    &lt;span class="no"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="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;code&gt;subscribed&lt;/code&gt;，&lt;code&gt;unsubscribed&lt;/code&gt;，&lt;code&gt;speak&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subscribed&lt;/code&gt;和&lt;code&gt;unsubscribed&lt;/code&gt;方法是默认就生成的，而&lt;code&gt;speak&lt;/code&gt;是我们自己定义的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subscribed&lt;/code&gt;表示的是当客户端连接上来的时候使用的方法。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unsubscribed&lt;/code&gt;表示的是当客户端与服务器失去连接的时候使用的方法。&lt;/p&gt;

&lt;p&gt;还有，&lt;code&gt;app/assets/javascripts/channels/room.coffee&lt;/code&gt;文件，内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="s2"&gt;"RoomChannel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;connected: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# Called when the subscription is ready for use on the server&lt;/span&gt;

  &lt;span class="ss"&gt;disconnected: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# Called when the subscription has been terminated by the server&lt;/span&gt;

  &lt;span class="ss"&gt;received: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#messages'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Called when there's incoming data on the websocket for this channel&lt;/span&gt;

  &lt;span class="ss"&gt;speak: &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="vi"&gt;@perform&lt;/span&gt; &lt;span class="s1"&gt;'speak'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt; &lt;span class="s1"&gt;'keypress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[data-behavior~=room_speaker]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if&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;keyCode&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="c1"&gt;# return = send&lt;/span&gt;
    &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&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;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;App.room&lt;/code&gt;里定义了四个方法，除了&lt;code&gt;speak&lt;/code&gt;，&lt;code&gt;connected&lt;/code&gt;、&lt;code&gt;disconnected&lt;/code&gt;、&lt;code&gt;received&lt;/code&gt;都是 actioncable 定义的。&lt;/p&gt;

&lt;p&gt;这几个方法可以和&lt;code&gt;RoomChannel&lt;/code&gt;里的方法对应起来，比如：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;connected&lt;/code&gt;和&lt;code&gt;subscribed&lt;/code&gt;对应，表示客户端和服务器端连接之后的情况。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;disconnected&lt;/code&gt;和&lt;code&gt;unsubscribed&lt;/code&gt;对应，表示客户端和服务器端失去连接之后的情况。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;received&lt;/code&gt;表示从服务器接收到信息之后的情况。因为服务器总是要向客户端推送信息的，接收完信息之后，就可以在这里进行一些页面上的操作，比如 DOM 更新等。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;room.coffee&lt;/code&gt;文件中有重要的一行&lt;code&gt;App.room.speak event.target.value&lt;/code&gt;，当键入聊天信息，一按回车键后，就会通过这行代码，把聊天信息，发送到后端服务器，并且会被&lt;code&gt;room_channel.rb&lt;/code&gt;中的&lt;code&gt;speak&lt;/code&gt;接收，执行&lt;code&gt;Message.create! content: data['message']&lt;/code&gt;命令。&lt;/p&gt;
&lt;h5 id="2.4 activejob"&gt;2.4 activejob&lt;/h5&gt;
&lt;p&gt;现在还没真正完成，还差一部分。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;room.coffee&lt;/code&gt;文件中有一个&lt;code&gt;received&lt;/code&gt;方法，它有一行指令&lt;code&gt;$('#messages').append data['message']&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这个表示当聊天信息发出时，会在聊天信息展示界面上添加聊天的内容。&lt;/p&gt;

&lt;p&gt;现在来处理这个，我们通过 activejob 来处理，还记得之前的&lt;code&gt;app/views/messages/_message.html.erb&lt;/code&gt;文件吗，现在要发挥它的作用。&lt;/p&gt;

&lt;p&gt;先建立一个 job。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails g job message_broadcast
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;app/jobs/message_broadcast_job.rb&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;MessageBroadcastJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt; &lt;span class="s1"&gt;'room_channel'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;render_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="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;render_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;ApplicationController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'messages/message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还要在一个地方执行这个 job，是当创建完 message 的时候。&lt;/p&gt;

&lt;p&gt;修改&lt;code&gt;app/models/message.rb&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;Message&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;after_create_commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;MessageBroadcastJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;做完这一切，重启一下服务器。&lt;/p&gt;

&lt;p&gt;现在来看下效果：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://aliyun.rails365.net/uploads/photo/image/152/preview_2016/1aca1d6b091697d829b99a2129b84209.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本篇完结。&lt;/p&gt;

&lt;p&gt;下一篇： &lt;a href="http://www.rails365.net/articles/websocket-zhi-actioncable-jin-jie-ba" rel="nofollow" target="_blank" title=""&gt;websocket 之 actioncable 进阶 (八)&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Thu, 05 May 2016 21:58:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/29927</link>
      <guid>https://ruby-china.org/topics/29927</guid>
    </item>
    <item>
      <title>CORS 系列文章 - 从跨域到 CORS (一)</title>
      <description>&lt;p&gt;&lt;a href="http://www.rails365.net/articles/cong-kua-yu-dao-cors-yi" rel="nofollow" target="_blank" title=""&gt;原文链接&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="1. 介绍"&gt;1. 介绍&lt;/h4&gt;
&lt;p&gt;跨域，可能很多前端开发者都会遇到过，也可能知道有 jsonp，iframe 之类的跨域方法。不过要说这些方法之前，先得来说说什么叫跨域，为什么要跨域。&lt;/p&gt;

&lt;p&gt;所谓跨域，顾名思义，跨到了另外的域，域不仅仅指的是不同的域名网站，可能同一个域名不同的端口号也算不同的域。浏览器是有规则的，只要协议、域名、端口有任何一个不同，都被当作是不同的域。协议指的是 http，或者 https 等。&lt;/p&gt;

&lt;p&gt;下面给出一个列表，指出不同域的情况：&lt;/p&gt;

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

&lt;p&gt;这个叫浏览器的同源策略 (same-origin policy)，为什么要这样规定呢，原因之一就是为了安全。&lt;/p&gt;

&lt;p&gt;假如你在 A 网站，用一段 js 脚本就能访问 B 网站，B 网站凭什么让你访问，为什么浏览器能让你随便访问别的网站呢，说不定 B 网站存在着各种危险，比如盗取你的密码，跨域攻击 (CSRF) 等，一切都不太合理。&lt;/p&gt;

&lt;p&gt;但也是有例外的情况，有些情况是要访问到别的网站的，比如加载一张图片，这张图片可能在别的网站，还有加载一个 js 文件，也是有可能在别的网站的，还有嵌入一个 frame 元素，也可以访问到别的网站的内容。&lt;/p&gt;

&lt;p&gt;所以跨域正是利用了上面几点，比如 jsonp 就是利用的加载 js 文件的功能，比如在&lt;code&gt;&amp;lt;script src="..."&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;中的 src 指定为目标网站的 js，同理，还有跨域攻击也可以利用&lt;code&gt;&amp;lt;image src="..." /&amp;gt;&lt;/code&gt;的功能。还有 iframe 更能直接加载别的网站的内容到自己的网站里来。&lt;/p&gt;

&lt;p&gt;这前面几种可以说是技巧，说句不好听的就是浏览器的漏洞，浏览器并未从正面上支持跨域，而且上面的几种跨域方法也是有各种局限，比如 jsonp 的方法，只能用 GET 方法，iframe 方法是能直接加载内容到网站上，但是本网站和 iframe 的数据交互也是一个头疼的地方。&lt;/p&gt;

&lt;p&gt;毕竟从 iframe 加载内容到本站后，是存在着数据交互的，可以用&lt;code&gt;document.domain&lt;/code&gt;，&lt;code&gt;window.name&lt;/code&gt;，不过这些方法都能用，且有效，不过总不尽完美，存在着各种各样的局限。&lt;/p&gt;

&lt;p&gt;然而 HTML5 引进了一个叫 window.postMessage 方法来跨域传送数据，这个倒是不错，不过也是利用了 iframe。&lt;/p&gt;

&lt;p&gt;除此之外，还有 flash，服务器代理等跨域方法，但是本章要介绍的是浏览器或服务器的跨域方法，它的名字叫 CORS。&lt;/p&gt;
&lt;h4 id="2. CORS"&gt;2. CORS&lt;/h4&gt;
&lt;p&gt;CORS 全称是 Cross-Origin Resource Sharing，跨域资源共享，这是浏览器的标准，也算是协议，基本上现代浏览器都支持，除了奇葩浏览器，例如 IE8、IE9，只支持部分特性。&lt;/p&gt;

&lt;p&gt;使用它，需要服务器端和客户端两方面的准备，服务器端我们选择 nginx 作为测试，客户端只是 js 罢了。&lt;/p&gt;

&lt;p&gt;nginx 服务监听在 localhost 的 8080 端口，而现在有一个网站运行 localhost 的 3000 端口，需要跨域到 nginx 那台服务器。&lt;/p&gt;

&lt;p&gt;现在开始测试之旅，要在浏览器模拟跨域请求，只需三行 js 代码。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;xhttp&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;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;xhttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;xhttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我使用的浏览器是 chrome，打开它的开发者工具，在&lt;code&gt;console&lt;/code&gt;里运行上面的代码，效果如下：&lt;/p&gt;

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

&lt;p&gt;上面的报错已经提示得很明显了，主要是下面这句话：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XMLHttpRequest cannot load http://localhost:8080/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
VM162:4 XHR failed loading: GET "http://localhost:8080/".
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误中说&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;这个头信息不存在于被请求的服务器中，来源域&lt;code&gt;http://localhost:3000&lt;/code&gt;是不允许访问该服务器的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;XMLHttpRequest&lt;/code&gt;这个可能很多开发者都明白，那是 ajax 请求利用的对象，利用它能发起 ajax 请求，但它的功能不仅仅是发起 ajax 请求，还能用于跨域，还有设置时限，FormData 对象管理表单数据，文件上传等功能，具体可以自行搜索相关的资料，在这里，它能发起跨域请求就可以了。&lt;/p&gt;

&lt;p&gt;我们来看看这个请求相关的信息。&lt;/p&gt;

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

&lt;p&gt;具体的头信息如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Request&lt;/span&gt; &lt;span class="no"&gt;Headers&lt;/span&gt;
&lt;span class="no"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="sr"&gt;/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
Connection:keep-alive
Host:localhost:8080
Origin:http:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;
&lt;span class="no"&gt;Referer&lt;/span&gt;&lt;span class="ss"&gt;:http:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="ss"&gt;:Mozilla&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Macintosh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="no"&gt;Intel&lt;/span&gt; &lt;span class="no"&gt;Mac&lt;/span&gt; &lt;span class="no"&gt;OS&lt;/span&gt; &lt;span class="no"&gt;X&lt;/span&gt; &lt;span class="mi"&gt;10_10_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;AppleWebKit&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;537.36&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;KHTML&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt; &lt;span class="no"&gt;Gecko&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="no"&gt;Chrome&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;48.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;2564.97&lt;/span&gt; &lt;span class="no"&gt;Safari&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;537.36&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最重要的是&lt;code&gt;Origin:http://localhost:3000&lt;/code&gt;这一行，它标明的是来源的域，这是请求头信息，会传给服务器。&lt;/p&gt;

&lt;p&gt;我们来看下服务器。&lt;/p&gt;

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

&lt;p&gt;可见，服务器还是正常响应 200 状态的。也就是说，服务器端该怎么应答就怎么应答，只是被浏览器给阻止了。&lt;/p&gt;

&lt;p&gt;浏览器是如何阻止的呢。主要是看响应头部的信息。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Response Headers
Accept-Ranges:bytes
Connection:keep-alive
Content-Length:612
Content-Type:text/html
Date:Mon, 01 Feb 2016 07:17:54 GMT
ETag:"56988bc6-264"
Last-Modified:Fri, 15 Jan 2016 06:03:50 GMT
Server:nginx/1.8.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它没有发现有发现&lt;code&gt;Origin:http://localhost:3000&lt;/code&gt;这个来源域名是被服务器所允许的。&lt;/p&gt;

&lt;p&gt;我们在服务器端设置一下，让&lt;code&gt;Origin:http://localhost:3000&lt;/code&gt;这个来源域被允许访问。&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;location&lt;/span&gt; / {
  &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="s1"&gt;'Access-Control-Allow-Origin'&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`&lt;code&gt;add_header 'Access-Control-Allow-Origin' '*'&lt;/code&gt;表示允许任何来源域访问 nginx 这台服务器。&lt;/p&gt;

&lt;p&gt;用&lt;code&gt;sudo nginx -s reload&lt;/code&gt;重新加载服务器配置。&lt;/p&gt;

&lt;p&gt;再来看下效果。&lt;/p&gt;

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

&lt;p&gt;果然成功了，不再提示错误。&lt;/p&gt;

&lt;p&gt;来看下响应的信息。&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;
&lt;span class="n"&gt;Accept&lt;/span&gt;-&lt;span class="n"&gt;Ranges&lt;/span&gt;:&lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="n"&gt;Access&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt;-&lt;span class="n"&gt;Allow&lt;/span&gt;-&lt;span class="n"&gt;Origin&lt;/span&gt;:*
&lt;span class="n"&gt;Connection&lt;/span&gt;:&lt;span class="n"&gt;keep&lt;/span&gt;-&lt;span class="n"&gt;alive&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;-&lt;span class="n"&gt;Length&lt;/span&gt;:&lt;span class="m"&gt;612&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt;:&lt;span class="n"&gt;text&lt;/span&gt;/&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="n"&gt;Date&lt;/span&gt;:&lt;span class="n"&gt;Mon&lt;/span&gt;, &lt;span class="m"&gt;01&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="m"&gt;2016&lt;/span&gt; &lt;span class="m"&gt;07&lt;/span&gt;:&lt;span class="m"&gt;25&lt;/span&gt;:&lt;span class="m"&gt;25&lt;/span&gt; &lt;span class="n"&gt;GMT&lt;/span&gt;
&lt;span class="n"&gt;ETag&lt;/span&gt;:&lt;span class="s2"&gt;"56988bc6-264"&lt;/span&gt;
&lt;span class="n"&gt;Last&lt;/span&gt;-&lt;span class="n"&gt;Modified&lt;/span&gt;:&lt;span class="n"&gt;Fri&lt;/span&gt;, &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="n"&gt;Jan&lt;/span&gt; &lt;span class="m"&gt;2016&lt;/span&gt; &lt;span class="m"&gt;06&lt;/span&gt;:&lt;span class="m"&gt;03&lt;/span&gt;:&lt;span class="m"&gt;50&lt;/span&gt; &lt;span class="n"&gt;GMT&lt;/span&gt;
&lt;span class="n"&gt;Server&lt;/span&gt;:&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应信息中多了这一行&lt;code&gt;Access-Control-Allow-Origin:*&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;原来，浏览器在用&lt;code&gt;XMLHttpRequest&lt;/code&gt;发起跨域请求的时候，它在请求头带了&lt;code&gt;Origin&lt;/code&gt;这个项，而服务器，在响应头信息中是有响应&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;这项的，两者比较一下，如果匹配，则请求成功，不匹配就不成功，不过，服务器那边还是照常执行。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在测试的时候需要注意的事，有可能会发现改了 nginx 的配置，浏览器发出的跨域请求却没生效，这可能是因为浏览器 cache 的原因，只要清除浏览器的 cache，再重新发起请求就好了。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;下一节&lt;a href="http://www.rails365.net/articles/cors-jin-jie-zhi-preflight-qing-qiu-er" rel="nofollow" target="_blank" title=""&gt;CORS 进阶之 Preflight 请求 (二)&lt;/a&gt;会介绍 CORS 更高阶的内容，比如&lt;code&gt;Preflight请求&lt;/code&gt;，&lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt;，&lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt;等。&lt;/p&gt;

&lt;p&gt;完结。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 16 Feb 2016 10:37:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/28998</link>
      <guid>https://ruby-china.org/topics/28998</guid>
    </item>
    <item>
      <title>Redis 实现自动输入完成</title>
      <description>&lt;p&gt;原文链接： &lt;a href="http://www.rails365.net/articles/2015-11-09-redis-shi-xian-zi-dong-shu-ru-wan-cheng-ba" rel="nofollow" target="_blank"&gt;http://www.rails365.net/articles/2015-11-09-redis-shi-xian-zi-dong-shu-ru-wan-cheng-ba&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="1. 介绍"&gt;1. 介绍&lt;/h4&gt;
&lt;p&gt;当我们在京东商城的搜索框，输入想要搜索的内容，比如你想要搜索"热水瓶"，刚输入一个"热"字，就会出现一个下拉框，列出了很多以"热"字开头的可供选择的条目，比如"热水器"、"热水袋"、”热水瓶"等，如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/55199a6f2307f74643d222ad49b50bc9.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这种技术就叫做自动输入完成，当输入想要搜索的首字符或其中被包含的字符时，就会出现可供选择的条目，用户可以选择其中的条目来完成此次搜索，避免了用户输入全部的字符，改善了用户体验。这种技术很常用，比如在一个社交网站添加联系人时，就是可以通过输入来联想匹配到联系人的。&lt;/p&gt;
&lt;h4 id="2. 功能分析"&gt;2. 功能分析&lt;/h4&gt;
&lt;p&gt;要实现这样的功能，需要前端和后端一起配合，前端部分需要监听搜入框内容的改变，当内容改变时把内容作为参数传递给服务器，服务器再请求后端的数据库，再把数据返回给客户端，前端只要把结果渲染出来即可。&lt;/p&gt;

&lt;p&gt;这个数据库我们选择的是内存数据库 redis，而不用存储在磁盘的关系型数据库，毕竟这个功能对实时性要求比较高，频繁地请求数据库肯定是用性能高和速度快的 redis 好。&lt;/p&gt;

&lt;p&gt;我们有一个实例网站，是存放文章的，每篇文章都有自己的标签 (tag)，整个网站是具有全文检索功能的，也就是说，可以根据文章的标题、内容、标签来搜索文章。现在需要在输入框加一个自动完成的功能，当用户在输入框输入标签的头一个或几个字符的时候，会自动联想到标签。比如，有"ruby"，"postgresql"两个标签，当用户输入"ru"的时候，会自动出现"ruby"作为可选的项。&lt;/p&gt;

&lt;p&gt;首先，被检索的标签的名称事先存放到 redis 中，每个标签都是唯一的，所以可选择 redis 的集合作为数据库，但是，有可以出现很多个以"ru"开头的标签，所以需要一个权重算法，那就是排序，标签被使用得频繁的越排在前面，所以最终是用排序集合 (sortedset) 来存储标签数据。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# rails console&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ActsAsTaggableOn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;消息队列&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="ss"&gt;:id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"消息队列"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;:taggings_count&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="ss"&gt;:id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ruby on rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;:taggings_count&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="ss"&gt;:id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"websocket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;:taggings_count&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="ss"&gt;:id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;:taggings_count&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;taggings_count&lt;/code&gt;就是标签被使用的次数，只要把&lt;code&gt;tag&lt;/code&gt;的&lt;code&gt;name&lt;/code&gt;按照&lt;code&gt;taggings_count&lt;/code&gt;作为排序标准存到 redis 的&lt;code&gt;sort set&lt;/code&gt;中，就可以了。下面我们会说如何去存储这些数据。&lt;/p&gt;
&lt;h4 id="3. soulmate使用"&gt;3. soulmate 使用&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/seatgeek/soulmate" rel="nofollow" target="_blank" title=""&gt;soulmate&lt;/a&gt;是一个结合 redis 实现自动输入完成的功能强大的 gem。&lt;/p&gt;

&lt;p&gt;先安装这个 gem。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;soulmate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;soulmate 要求你的数据存成一种特定格式的 json，再把 json 导出到 redis 中，格式是类似这样的。&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"devise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;id&lt;/code&gt;是唯一的标识，&lt;code&gt;term&lt;/code&gt;就是搜索的条目，&lt;code&gt;score&lt;/code&gt;就是排序的项，这三项是必须要带上的，而&lt;code&gt;data&lt;/code&gt;是可选的，里面可以存自己想要的数据，比如可把 tag 的描述信息加上。&lt;/p&gt;

&lt;p&gt;很简单，在我们的案例中，把上面的 tag 的&lt;code&gt;name&lt;/code&gt;换成&lt;code&gt;term&lt;/code&gt;，&lt;code&gt;taggings_count&lt;/code&gt;换成&lt;code&gt;score&lt;/code&gt;即可。&lt;/p&gt;
&lt;h5 id="3.1 导入tags数据"&gt;3.1 导入 tags 数据&lt;/h5&gt;
&lt;p&gt;我写了一个方法可以将所有的 tag 导出到一个 json 文件。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tags.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"w+"&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="no"&gt;ActsAsTaggableOn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;tag_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;term: &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;score: &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggings_count&lt;/span&gt;
    &lt;span class="p"&gt;}&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;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tag_json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&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;输出的 tags.json 类似下面这样：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"devise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&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="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&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="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ruby on rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在可以先这个 tags.json 导入到 redis 数据库中，soulmate 这个 gem 也提供了相关的命令行工具。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;soulmate load tag &lt;span class="nt"&gt;--redis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://localhost:6379/0 &amp;lt; tags.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会发现 redis 中增加了很多的数据，我们先不管，等下再来分析那些数据。&lt;/p&gt;
&lt;h5 id="3.2 添加单个tag到redis"&gt;3.2 添加单个 tag 到 redis&lt;/h5&gt;
&lt;p&gt;每次都用这种导入的方式来在 redis 增加数据很不方便的，毕竟以后我们随时要增加 tag。你总不可能为了增加一个 tag 再导一次 json 吧，这不太科学。所以我们需要在增加或删除 tag 的时候自动把数据添加到 redis 中。&lt;/p&gt;

&lt;p&gt;通过查看 soulmate 的源码，发现它是提供了相应的方法的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/seatgeek/soulmate/blob/master/lib/soulmate/loader.rb#L29&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:skip_duplicate_check&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Items must specify both an id and a term"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# kill any old items with this id&lt;/span&gt;
  &lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:skip_duplicate_check&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipelined&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# store the raw data in a separate key to reduce memory usage&lt;/span&gt;
    &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;MultiJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aliases"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prefixes_for_phrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# remember this prefix in a master set&lt;/span&gt;
      &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base&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="nb"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# store the id of this term in the index&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于&lt;code&gt;add&lt;/code&gt;方法要接一个 hash 作为参数，所以需要对 tag 这个 model 作一些加工。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/tag.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ActsAsTaggableOn&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tag&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:create_soulmate&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_hash&lt;/span&gt;
      &lt;span class="n"&gt;tag_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;id: &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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;term: &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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;score: &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;taggings_count&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag_json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&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;create_soulmate&lt;/span&gt;  
        &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Loader&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;"tag"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&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;to_hash&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;我们拿其中一个 tag 试一下，看看它究竟生成了怎样的 redis 数据 (如果在开发环境，做这个之前可以用 redis 的 flushdb 指令先清除数据)。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActsAsTaggableOn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_hash&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s2"&gt;"term"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"devise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"score"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Loader&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;"tag"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 redis 生成了如下的数据：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;redis-cli
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; keys &lt;span class="k"&gt;*&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag:devise"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag"&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag:devis"&lt;/span&gt;
4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag:dev"&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag:de"&lt;/span&gt;
6&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-index:tag:devi"&lt;/span&gt;
7&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"soulmate-data:tag"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有的 key 分为三大类，分别是&lt;code&gt;"soulmate-index:tag"&lt;/code&gt;、&lt;code&gt;"soulmate-data:tag"&lt;/code&gt;，剩下的以 tag 的 name 为 devise 的前缀开头的 key。结合 add 方法的源码，也可以知道这三种 key 的类型分别为 set, hash, sortedset，可以自己用 redis 的 type 命令打印出来。现在打印出这些 key 的值。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;redis-cli
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; smembers soulmate-index:tag
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"devis"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"devi"&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"de"&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"devise"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hgetall soulmate-data:tag
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:5,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;term&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;devise&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:3}"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; zrange &lt;span class="s2"&gt;"soulmate-index:tag:de"&lt;/span&gt; 0 &lt;span class="nt"&gt;-1&lt;/span&gt; WITHSCORES
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; zrange &lt;span class="s2"&gt;"soulmate-index:tag:dev"&lt;/span&gt; 0 &lt;span class="nt"&gt;-1&lt;/span&gt; WITHSCORES
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;soulmate-index:tag&lt;/code&gt;存的是集合 (set)，将"devise"分成一个个前缀，以后按照这些前缀就能搜出"devise"这个 tag。&lt;code&gt;soulmate-data:tag&lt;/code&gt;存的是一个哈希 (hash)，健为标签 (tag) 的 id，值为 tag 的所有内容，就是那个标签 (ActsAsTaggableOn::Tag) model 中&lt;code&gt;to_hash&lt;/code&gt;方法的内容，如果有存 data，它的数据也是会在这里出现。&lt;code&gt;soulmate-index:tag:de&lt;/code&gt;等存的是排序后的集合，排序的健是&lt;code&gt;score&lt;/code&gt;，也就是标签 (tag) 的&lt;code&gt;taggings_count&lt;/code&gt;，值为标签 (tag) 的&lt;code&gt;id&lt;/code&gt;。&lt;/p&gt;
&lt;h5 id="3.3 测试效果"&gt;3.3 测试效果&lt;/h5&gt;
&lt;p&gt;现在来实现服务端的逻辑，我们不用自己写 controller 端的代码，soulmate 为我们提供好了这一切。&lt;/p&gt;

&lt;p&gt;先安装这个 gem。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;rack&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;contrib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行以下这个命令。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;soulmate-web &lt;span class="nt"&gt;--foreground&lt;/span&gt; &lt;span class="nt"&gt;--no-launch&lt;/span&gt; &lt;span class="nt"&gt;--redis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://localhost:6379/0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会开启一个服务并且监听在 5678 端口。&lt;/p&gt;

&lt;p&gt;现在我们可以用 curl 工具或 chrome 浏览器插件 postman 来测试这个服务。下面是 curl 工具的使用例子。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET  http://localhost:5678/search &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"types[]=tag&amp;amp;term=de"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"de"&lt;/span&gt;,&lt;span class="s2"&gt;"results"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"tag"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:5,&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"devise"&lt;/span&gt;,&lt;span class="s2"&gt;"score"&lt;/span&gt;:3&lt;span class="o"&gt;}]}}&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET  http://localhost:5678/search &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"types[]=tag&amp;amp;term=devis"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"devis"&lt;/span&gt;,&lt;span class="s2"&gt;"results"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"tag"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:5,&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"devise"&lt;/span&gt;,&lt;span class="s2"&gt;"score"&lt;/span&gt;:3&lt;span class="o"&gt;}]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 postman 请求&lt;code&gt;http://localhost:5678/search?types[]=tag&amp;amp;term=de&lt;/code&gt;的例子如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/bdd6b3c4b01745064c17b3514da643dc.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;现在再来查看 redis 的数据，会发现多了两个 key。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;redis-cli
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; zrange &lt;span class="s2"&gt;"soulmate-cache:tag:de"&lt;/span&gt; 0 &lt;span class="nt"&gt;-1&lt;/span&gt; WITHSCORES
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; zrange &lt;span class="s2"&gt;"soulmate-cache:tag:devis"&lt;/span&gt; 0 &lt;span class="nt"&gt;-1&lt;/span&gt; WITHSCORES
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4. 页面上的实现"&gt;4. 页面上的实现&lt;/h4&gt;
&lt;p&gt;我们要用 soulmate 配合前端在 rails 项目里实现自动输入完成的功能。&lt;/p&gt;

&lt;p&gt;先把 soulmate 安装进 rails 项目里。&lt;/p&gt;
&lt;h5 id="4.1 挂载soulmate服务"&gt;4.1 挂载 soulmate 服务&lt;/h5&gt;
&lt;p&gt;把下面的代码添加到 Gemfile 文件，然后执行&lt;code&gt;bundle&lt;/code&gt;命令。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rack-contrib'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'soulmate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:require&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'soulmate/server'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;config/routes.rb&lt;/code&gt;文件中添加一行路由。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:at&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/sm"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;config/initializers&lt;/code&gt;目录下添加 soulmate.rb 文件。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'redis://127.0.0.1:6379/0'&lt;/span&gt;
&lt;span class="c1"&gt;# or you can asign an existing instance of Redis, Redis::Namespace, etc.&lt;/span&gt;
&lt;span class="c1"&gt;# Soulmate.redis = $redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启服务器。现在可以这样来测试。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET  http://localhost:3000/sm/search &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"types[]=tag&amp;amp;term=dev"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"dev"&lt;/span&gt;,&lt;span class="s2"&gt;"results"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"tag"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:5,&lt;span class="s2"&gt;"term"&lt;/span&gt;:&lt;span class="s2"&gt;"devise"&lt;/span&gt;,&lt;span class="s2"&gt;"score"&lt;/span&gt;:3&lt;span class="o"&gt;}]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="4.2 soulmate.js"&gt;4.2 soulmate.js&lt;/h5&gt;
&lt;p&gt;至于前端部分，我们使用官方推荐的&lt;a href="https://github.com/mcrowe/soulmate.js" rel="nofollow" target="_blank" title=""&gt;soulmate.js&lt;/a&gt;这个库。&lt;/p&gt;

&lt;p&gt;相关的代码是这样的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_tag&lt;/span&gt; &lt;span class="s2"&gt;"/articles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: &lt;/span&gt;&lt;span class="s2"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"navbar-form navbar-left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Search"&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:search&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;presence&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="n"&gt;autocomplete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// Make the input field autosuggest-y.&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Selected &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;soulmate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sm/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;renderCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;selectCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;minQueryLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="mi"&gt;5&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;具体的 css 和 js 效果可以自己处理。&lt;/p&gt;
&lt;h4 id="5. 源码解析"&gt;5. 源码解析&lt;/h4&gt;
&lt;p&gt;现在有个美中不足的地方，就是必须要输入两个字符以上才能开始自动输入完成。而 readme 文档没有相关的解决方法，我们只能从源码入手。&lt;/p&gt;

&lt;p&gt;上面的例子中，"devise"这个单词会被分解成一个个前缀，比如"de"，"dev”等。我们来看下中文词组是如何被分解的。&lt;/p&gt;

&lt;p&gt;上面有提到那个&lt;code&gt;add&lt;/code&gt;方法的源码，其中调用了&lt;code&gt;prefixes_for_phrase&lt;/code&gt;这个方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/seatgeek/soulmate/blob/ead5d6c2b6d698a5c49294a73a4f7536a5013f01/lib/soulmate/helpers.rb#L3&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Soulmate&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Helpers&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prefixes_for_phrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reject&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;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min_complete&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&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;执行&lt;code&gt;rails console&lt;/code&gt;进入终端来看一下这个方法是如何分解中文词组的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;
&lt;span class="no"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BasicObject&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefixes_for_phrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"前端构建与部署工具"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部署"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部署工"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;中文词组还是能够正常处理，可是至少要输入两个字符。通过分析&lt;code&gt;prefixes_for_phrase&lt;/code&gt;方法，可以发现关键在于&lt;code&gt;Soulmate.min_complete&lt;/code&gt;这个变量。&lt;/p&gt;

&lt;p&gt;通过搜索源码，发现了定义&lt;code&gt;Soulmate.min_complete&lt;/code&gt;变量的地方。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/seatgeek/soulmate/blob/ead5d6c2b6d698a5c49294a73a4f7536a5013f01/lib/soulmate/config.rb#L11&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'uri'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Soulmate&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Config&lt;/span&gt;

    &lt;span class="nb"&gt;attr_writer&lt;/span&gt; &lt;span class="ss"&gt;:min_complete&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_complete&lt;/span&gt;
      &lt;span class="vi"&gt;@min_complete&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_MIN_COMPLETE&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;redis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;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;还记得上文"挂载 soulmate 服务"部分提到&lt;code&gt;config/initializers/soulmate.rb&lt;/code&gt;这个文件吗，里面就是定义了&lt;code&gt;Soulmate.redis&lt;/code&gt;这个变量，那同样的道理，也把&lt;code&gt;min_complete&lt;/code&gt;定义在里面。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/soulmate.rb&lt;/span&gt;
&lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'redis://127.0.0.1:6379/0'&lt;/span&gt;
&lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min_complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Soulmate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;
&lt;span class="no"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BasicObject&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefixes_for_phrase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"前端构建与部署工具"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部署"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="s2"&gt;"前端构建与部署工"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;完结。&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Tue, 10 Nov 2015 23:21:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/28015</link>
      <guid>https://ruby-china.org/topics/28015</guid>
    </item>
    <item>
      <title>Redis 实现 Cache 系统实践</title>
      <description>&lt;h4 id="1. 介绍"&gt;1. 介绍&lt;/h4&gt;
&lt;p&gt;rails 中就自带有 cache 功能，不过它默认是用文件来存储数据的。我们要改为使用 redis 来存储。而且我们也需要把 sessions 也存放到 redis 中。关于 rails 实现 cache 功能的源码可见于这几处：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache.rb" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache.rb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/tree/master/activesupport/lib/active_support/cache" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/tree/master/activesupport/lib/active_support/cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/cache_helper.rb" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/cache_helper.rb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2. 使用"&gt;2. 使用&lt;/h4&gt;
&lt;p&gt;我们一步步在 rails 中使用 cache 实现我们的需求。&lt;/p&gt;
&lt;h5 id="2.1 开启cache模式"&gt;2.1 开启 cache 模式&lt;/h5&gt;
&lt;p&gt;首先第一步我们要来开启 cache 模式。默认情况下，production 环境是开启的，但是 development 没有，所以要开启它。&lt;/p&gt;

&lt;p&gt;进入&lt;code&gt;config/environments/development.rb&lt;/code&gt;文件中，把&lt;code&gt;config.action_controller.perform_caching&lt;/code&gt;设为 true。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_caching&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完，记得重启服务器。&lt;/p&gt;
&lt;h5 id="2.2 使用html片断cache"&gt;2.2 使用 html 片断 cache&lt;/h5&gt;
&lt;p&gt;为了方便测试和了解整个原理，我们先不使用 redis 来存放 cache 数据，只使用默认的文件来存放数据。&lt;/p&gt;

&lt;p&gt;以本站为例，我们要把首页的"最近的文章"那部分加上 html 片断的 cache。&lt;/p&gt;

&lt;p&gt;使用 html 片断 cache，rails 提供了一个 helper 方法可以办到，很简单，只需要把需要的 html 用 cache 包起来。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;row&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;
          &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;最近的文章&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
          &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@articles.each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearfix&lt;/span&gt;
              &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;
              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先在页面刷新一下，然后通过日志来观察。&lt;/p&gt;

&lt;p&gt;先发现访问起来比平时慢一点点，因为它在把 cache 存到文件中，具体的 log 是下面这样的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Started GET "/" for 127.0.0.1 at 2015-10-30 16:19:27 +0800
Processing by HomeController#index as HTML

  Cache digest for app/views/home/index.html.slim: 8e89c7a7d1da1d9719fca4639859b19d

Read fragment views/localhost:4000//8e89c7a7d1da1d9719fca4639859b19d (0.3ms)

  Article Load (2.0ms)  SELECT  "articles"."title", "articles"."created_at", "articles"."published", "articles"."group_id", "articles"."slug", "articles"."id" FROM "articles" WHERE "articles"."published" = $1  ORDER BY id DESC LIMIT 10  [["published", "t"]]
  Group Load (3.5ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" IN (1, 5, 3, 4)
  Article Load (0.9ms)  SELECT  "articles"."title", "articles"."created_at", "articles"."published", "articles"."group_id", "articles"."slug", "articles"."id", "articles"."visit_count" FROM "articles" WHERE "articles"."published" = $1  ORDER BY visit_count DESC LIMIT 10  [["published", "t"]]
  Group Load (2.3ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" IN (1, 3, 4)
  Group Load (4.4ms)  SELECT "groups".* FROM "groups"

Write fragment views/localhost:4000//8e89c7a7d1da1d9719fca4639859b19d (41.7ms)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要就是&lt;code&gt;Cache digest&lt;/code&gt;、&lt;code&gt;Read fragment&lt;/code&gt;和&lt;code&gt;Write fragment&lt;/code&gt;部分。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cache digest&lt;/code&gt;是产生一个 md5 码，这个码来标识 html 的片断，会很有用，我们等下再来细说。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Read fragment&lt;/code&gt;是读取 html 片断 (以文件形式存储)，根据之前产生的 md5 标识，发现不存在，就会生成一个 html 片断并存起来，就是&lt;code&gt;Write fragment&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;默认情况下，产生的 html 片断文件是存在/tmp/cache 目录里的，如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/codes/rails365 (master) $ tree tmp/cache/
tmp/cache/
├── 20B
│&amp;nbsp;&amp;nbsp; └── 6F1
│&amp;nbsp;&amp;nbsp;     └── views%2Flocalhost%3A4000%2F%2F8e89c7a7d1da1d9719fca4639859b19d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开&lt;code&gt;views%2Flocalhost%3A4000%2F%2F8e89c7a7d1da1d9719fca4639859b19d&lt;/code&gt;这个文件，就会发现里面存储的就是 html 的片断。&lt;/p&gt;

&lt;p&gt;现在我们在刷新一遍页面，再来看看日志。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Started GET "/" for 127.0.0.1 at 2015-10-30 16:53:18 +0800
Processing by HomeController#index as HTML
  Cache digest for app/views/home/index.html.slim: 8e89c7a7d1da1d9719fca4639859b19d
Read fragment views/localhost:4000//8e89c7a7d1da1d9719fca4639859b19d (0.3ms)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就会发现&lt;code&gt;Write fragment&lt;/code&gt;没有了，也不查询数据库了，数据都从 html 片断 cache 取了。&lt;/p&gt;

&lt;p&gt;这样还不算完成。我们要考虑一个问题，就是我们改了数据或 html 的内容的时候，cache 会自动更新吗？&lt;/p&gt;
&lt;h5 id="2.3 Cache digest"&gt;2.3 Cache digest&lt;/h5&gt;
&lt;p&gt;先来说更改 html 片断代码本身的情况。&lt;/p&gt;

&lt;p&gt;我们把"最近的文章"改成”最新的文章"，然后我们来观察是否会生效。&lt;/p&gt;

&lt;p&gt;最终通过查看日志，发现还是产生了&lt;code&gt;Write fragment&lt;/code&gt;，说明是生效的。&lt;/p&gt;

&lt;p&gt;这个原理是什么呢？&lt;/p&gt;

&lt;p&gt;我们找到 cache 这个 helper 方法的&lt;a href="https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/cache_helper.rb" rel="nofollow" target="_blank" title=""&gt;源码&lt;/a&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&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;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:perform_caching&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;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_caching&lt;/span&gt;
    &lt;span class="n"&gt;safe_concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_fragment_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;options&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;block&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现关键在&lt;code&gt;cache_fragment_name&lt;/code&gt;这个方法里，顺应地找到下面两个方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_fragment_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="ss"&gt;skip_digest: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;virtual_path: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skip_digest&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;fragment_name_with_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;virtual_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fragment_name_with_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;virtual_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:&lt;/span&gt;
  &lt;span class="n"&gt;virtual_path&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="vi"&gt;@virtual_path&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;virtual_path&lt;/span&gt;
    &lt;span class="nb"&gt;name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&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="nf"&gt;last&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Digestor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;virtual_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;finder: &lt;/span&gt;&lt;span class="n"&gt;lookup_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependencies: &lt;/span&gt;&lt;span class="n"&gt;view_cache_dependencies&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;name&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;code&gt;fragment_name_with_digest&lt;/code&gt;这个方法里，它会找到 cache 的第一个参数，然后用这个参数的内容生成 md5 码，我们刚才改变了 html 的内容，也就是参数改变了，md5 自然就变了，md5 一变就得重新生成 html 片断。&lt;/p&gt;

&lt;p&gt;所以 cache 方法的第一个参数是关键，它的内容是判断重不重新产生 html 片断的依据。&lt;/p&gt;

&lt;p&gt;改变 html 片断代码之后，是会重新生成 html 片断的，但如果是在 articles 中增加一条记录呢？通过尝试发现不会重新生成 html 片断的。&lt;/p&gt;

&lt;p&gt;那我把&lt;a href="/artilces" class="user-mention" title="@artilces"&gt;&lt;i&gt;@&lt;/i&gt;artilces&lt;/a&gt;作为第一个参数传给 cache 方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;row&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;
          &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;最近的文章&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
          &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@articles.each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearfix&lt;/span&gt;
              &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;
              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现生成了&lt;code&gt;Write fragment&lt;/code&gt;，说明是可以的，页面也会生效。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache digest for app/views/home/index.html.slim: 1c628fa3d96abde48627f8a6ef319c1c

Read fragment views/articles/15-20151027051837664089000/articles/14-20151030092311065810000/articles/13-20150929153356076334000/articles/12-20150929144255631082000/articles/11-20151027064325237540000/articles/10-20150929153421707840000/articles/9-20150929123736371074000/articles/8-20150929144346413579000/articles/7-20150929144324012954000/articles/6-20150929144359736164000/1c628fa3d96abde48627f8a6ef319c1c (0.1ms)

Write fragment views/articles/15-20151027051837664089000/articles/14-20151030092311065810000/articles/13-20150929153356076334000/articles/12-20150929144255631082000/articles/11-20151027064325237540000/articles/10-20150929153421707840000/articles/9-20150929123736371074000/articles/8-20150929144346413579000/articles/7-20150929144324012954000/articles/6-20150929144359736164000/1c628fa3d96abde48627f8a6ef319c1c (75.9ms)

Article Load (2.6ms)  SELECT  "articles"."title", "articles"."created_at", "articles"."updated_at", "articles"."published", "articles"."group_id", "articles"."slug", "articles"."id", "articles"."visit_count" FROM "articles" WHERE "articles"."published" = $1  ORDER BY visit_count DESC LIMIT 10  [["published", "t"]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，除此之外，还有 sql 语句生成，而且以后的每次请求都有，即使命中了 cache，因为&lt;a href="/articles" class="user-mention" title="@articles"&gt;&lt;i&gt;@&lt;/i&gt;articles&lt;/a&gt;作为第一个参数，它的内容是要通过数据库来查找的。&lt;/p&gt;

&lt;p&gt;那有一个解决方案是这样的：把&lt;a href="/articles" class="user-mention" title="@articles"&gt;&lt;i&gt;@&lt;/i&gt;articles&lt;/a&gt;的内容也放到 cache 中，这样就不用每次都查找数据库了，而一旦有 update 或 create 数据的时候，就让&lt;a href="/articles" class="user-mention" title="@articles"&gt;&lt;i&gt;@&lt;/i&gt;articles&lt;/a&gt;过期或者重新生成。&lt;/p&gt;

&lt;p&gt;为了方便测试，我们先把 cache 的存储方式改为用 redis 来存储数据。&lt;/p&gt;

&lt;p&gt;添加下面两行到 Gemfile 文件，执行&lt;code&gt;bundle&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'redis-namespace'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'redis-rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;config/application.rb&lt;/code&gt;中添加下面这一行。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;namespace: &lt;/span&gt;&lt;span class="s2"&gt;"rails365"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@articles&lt;/code&gt;的内容要改为从 redis 获得，主要是读 redis 中健为&lt;code&gt;articles&lt;/code&gt;的值。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@articles&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt; &lt;span class="s2"&gt;"articles"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;except_body_with_default&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;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&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;创建或生成一条 article 记录，都要让 redis 的数据无效。&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;Admin::ArticlesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;
  &lt;span class="o"&gt;...&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;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&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;article_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="s2"&gt;"articles"&lt;/span&gt;
    &lt;span class="o"&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;这样再刷新两次以上，就会发现不再查数据库了，除非添加或修改了文章 (article)。&lt;/p&gt;

&lt;p&gt;完结&lt;/p&gt;

&lt;p&gt;原文链接：&lt;a href="http://www.rails365.net/articles/2015-10-30-redis-shi-xian-cache-xi-tong-shi-jian-liu" rel="nofollow" target="_blank"&gt;http://www.rails365.net/articles/2015-10-30-redis-shi-xian-cache-xi-tong-shi-jian-liu&lt;/a&gt;&lt;/p&gt;</description>
      <author>hfpp2012</author>
      <pubDate>Wed, 04 Nov 2015 09:53:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/27939</link>
      <guid>https://ruby-china.org/topics/27939</guid>
    </item>
  </channel>
</rss>
