<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>heroyct (yang)</title>
    <link>https://ruby-china.org/heroyct</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>simple_form 如何根据不同的 url 使用不同的设置</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/heartcombo/simple_form" rel="nofollow" target="_blank" title=""&gt;simple_form&lt;/a&gt;可以在 config/initializers 里面进行设置，对于所有画面都使用统一的界面没什么问题。&lt;/p&gt;

&lt;p&gt;现在想根据不同的 URL 来使用不同的设置，看了下文档，button_class label_text default_wrapper 等属性好像只可以设置一个。&lt;/p&gt;

&lt;p&gt;想知道能不能这样设置，因为面向用户和面向 admin 使用的是完全不同的界面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL /user/* 使用 user 专用的设置。&lt;/li&gt;
&lt;li&gt;URL /admin/* 使用 admin 专用的设置。&lt;/li&gt;
&lt;li&gt;URL /operator/* 使用 operator 专用的设置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不知道有没有人遇到类似的问题。&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Wed, 22 Jun 2022 11:09:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/42473</link>
      <guid>https://ruby-china.org/topics/42473</guid>
    </item>
    <item>
      <title>大家怎么连接谷歌的？</title>
      <description>&lt;p&gt;感觉百度不够用，想连谷歌，都用的 VPN？
不知道有什么好的 VPN 推荐？&lt;/p&gt;

&lt;p&gt;用了个 PronVPN，功能倒还行，但愣是没找到怎么付款。。&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Fri, 31 Jan 2020 12:17:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/39473</link>
      <guid>https://ruby-china.org/topics/39473</guid>
    </item>
    <item>
      <title>Ruby 3 啥时候来了</title>
      <description>&lt;p&gt;3 大目标&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance 快 3 倍？&lt;/li&gt;
&lt;li&gt;Concurrency&lt;/li&gt;
&lt;li&gt;Static Analysis&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Thu, 09 Jan 2020 14:44:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/39422</link>
      <guid>https://ruby-china.org/topics/39422</guid>
    </item>
    <item>
      <title>如何测试 Custom Validator</title>
      <description>&lt;h2 id="Custom Validators"&gt;Custom Validators&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/active_record_validations.html#custom-validators" rel="nofollow" target="_blank" title=""&gt;具体参考这里 Rails Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;比如要验证电话号码，可以追加一个 tel_validator&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/validators/tel_validator.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TelValidator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EachValidator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&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;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tel&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;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# validate code&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="如何测试Custom Validator"&gt;如何测试 Custom Validator&lt;/h2&gt;&lt;h3 id="1. 创建一个dummy model"&gt;1. 创建一个 dummy model&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:model_class&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;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:tel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validations&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
      &lt;span class="s1"&gt;'model'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:tel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tel: &lt;/span&gt;&lt;span class="kp"&gt;true&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;每次都写比较麻烦，可以放到 helper 里面&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/support/validator_test_helper.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ValidatorHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_model_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Struct&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;attr_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validations&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
        &lt;span class="s1"&gt;'model'&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="n"&gt;attr_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_options&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="no"&gt;RSpec&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;include&lt;/span&gt; &lt;span class="no"&gt;ValidatorHelper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :validator&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. rspec"&gt;2. rspec&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/validators/tel_validator_rspec.rb&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;TelValidator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :validator&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;:model_class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;generate_model_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:tel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tel: &lt;/span&gt;&lt;span class="kp"&gt;true&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;'valid tel'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="s1"&gt;'13811112222'&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;model&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="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>heroyct</author>
      <pubDate>Thu, 18 Jul 2019 14:55:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/38852</link>
      <guid>https://ruby-china.org/topics/38852</guid>
    </item>
    <item>
      <title>使用 GitHub Actions 自动删除 merged branch</title>
      <description>&lt;p&gt;以前是 merge 了 branch 以后，自己删除，试着用 github 的新功能 github actions 进行自动化。&lt;/p&gt;

&lt;p&gt;方法这里介绍的很详细&lt;/p&gt;

&lt;p&gt;&lt;a href="https://help.github.com/en/articles/creating-a-workflow-with-github-actions#creating-a-workflow-using-the-file-editor" rel="nofollow" target="_blank"&gt;https://help.github.com/en/articles/creating-a-workflow-with-github-actions#creating-a-workflow-using-the-file-editor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这次添加下面的文件然后 push 到 github 就行了，完成以后 actions 页面可以进行查看。&lt;/p&gt;

&lt;p&gt;文件内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .github/main.workflow

workflow "on pull request merge, delete the branch" {
  on = "pull_request"
  resolves = ["branch cleanup"]
}

action "branch cleanup" {
  uses = "jessfraz/branch-cleanup-action@master"
  secrets = ["GITHUB_TOKEN"]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后 merge 一个 branch 以后，就会自动删除它。&lt;/p&gt;

&lt;p&gt;不知道大家怎么玩的 github actions&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Wed, 10 Jul 2019 16:07:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/38824</link>
      <guid>https://ruby-china.org/topics/38824</guid>
    </item>
    <item>
      <title>使用 Capybara 进行自动化测试</title>
      <description>&lt;p&gt;总结了一下如何使用 Capybara 进行 UI 自动化测试 (Feature Test)，欢迎指正，补充。&lt;/p&gt;
&lt;h2 id="什么是Capybara"&gt;什么是 Capybara&lt;/h2&gt;
&lt;p&gt;一个进行 UI 测试的框架，可以让你将浏览器进行的手动测试自动化&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open source&lt;/li&gt;
&lt;li&gt;对应多个测试框架

&lt;ul&gt;
&lt;li&gt;MiniTest RSpec Cucumber 等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可以使用多个 driver

&lt;ul&gt;
&lt;li&gt;Selenium Webkit Poltergeist 等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="开发环境"&gt;开发环境&lt;/h3&gt;
&lt;p&gt;Rails + Rspec + Capybara + SeleniumDriver&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 5.1.6.2&lt;/li&gt;
&lt;li&gt;rspec-rails 3.7.2&lt;/li&gt;
&lt;li&gt;capybara 3.9.0&lt;/li&gt;
&lt;li&gt;SeleniumDriver 3.141.59&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="安装和设置"&gt;安装和设置&lt;/h2&gt;&lt;h3 id="1. 添加Gem"&gt;1. 添加 Gem&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&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;'capybara'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'selenium-webdriver'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2. 添加docker chrome"&gt;2. 添加 docker chrome&lt;/h2&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;snode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selenium/standalone-chrome:3.141.59&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_MAX_INSTANCES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;NODE_MAX_SESSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;GRID_MAX_SESSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/shm:/dev/shm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不使用 docker 的可以使用&lt;code&gt;chromedriver-helper&lt;/code&gt;这个 Gem&lt;/p&gt;
&lt;h2 id="3. 设置Capybara"&gt;3. 设置 Capybara&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/support/capybara.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'capybara/rails'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'capybara/rspec'&lt;/span&gt;

&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_driver&lt;/span&gt; &lt;span class="ss"&gt;:selenium&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;app&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_selenium&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebDriver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Remote&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="c1"&gt;# 设置成headless&lt;/span&gt;
  &lt;span class="n"&gt;chrome_capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebDriver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Remote&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"chromeOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;args: &lt;/span&gt;&lt;span class="sx"&gt;%w(headless window-size=1400,1200)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 设置docker chrome的url&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="ss"&gt;browser: :remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s1"&gt;'http://snode:4444/wd/hub'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;http_client: &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;desired_capabilities: &lt;/span&gt;&lt;span class="n"&gt;chrome_capabilities&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Driver&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;app&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="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;javascript_driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:selenium&lt;/span&gt;
&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://app.com'&lt;/span&gt;
&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="UI测试"&gt;UI 测试&lt;/h2&gt;&lt;h3 id="登陆页面测试"&gt;登陆页面测试&lt;/h3&gt;
&lt;p&gt;一个简单的登陆页面测试&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/features/sample_login.rb&lt;/span&gt;

&lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="s1"&gt;'SampleLogin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&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;it&lt;/span&gt; &lt;span class="s1"&gt;'正确的ID和密码可以登陆'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="s1"&gt;'login'&lt;/span&gt;

      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'id'&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;'your id'&lt;/span&gt;
      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'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;'123456789'&lt;/span&gt;
      &lt;span class="n"&gt;click_on&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="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;从这个列子可以看出 UI 测试基本都是以下几步&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;访问一个页面&lt;/li&gt;
&lt;li&gt;查找元素&lt;/li&gt;
&lt;li&gt;操作元素&lt;/li&gt;
&lt;li&gt;判断元素是否存在，元素的属性是否符合预期&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="访问一个页面"&gt;访问一个页面&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;
&lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;new_user_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以使用&lt;code&gt;page&lt;/code&gt;来对当前页面进行各种操作&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Session" rel="nofollow" target="_blank" title=""&gt;page 的具体内容可以参考这里 Capybara::Session&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="查找元素"&gt;查找元素&lt;/h3&gt;&lt;h4 id="find"&gt;find&lt;/h4&gt;
&lt;p&gt;最常用的查找方式，和 jquery 的查找比较类似&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&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;"sample_id1"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sample1"&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;id=&lt;/span&gt;&lt;span class="s"&gt;"sample_id2"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sample"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:none"&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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/sample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;sample&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;data-sample=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;保存/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# find by class&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.sample1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="c1"&gt;# find by id&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#sample_id1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# find invisible element&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#sample_id2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;visible: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# find by text&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&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;'sample'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# find by attr&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'button[data-sample="test"]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="all first"&gt;all first&lt;/h4&gt;
&lt;p&gt;使用方法和 find 基本一样，只是对于多个类似的元素用 find 无法查找，比如多个相同的 class，这个时候可以使用 all 和 first&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&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;"sample_id1"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;第一&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sample_id2"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;第二&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.sample'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 获取第一个div&lt;/span&gt;
&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.sample'&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="c1"&gt;# 获取第二个div&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders" rel="nofollow" target="_blank" title=""&gt;具体可以参考这里 Capybara::Node::Finders&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="操作元素"&gt;操作元素&lt;/h3&gt;&lt;h4 id="click"&gt;click&lt;/h4&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"path/sample"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"link"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"link_class"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;链接&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#link'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt; &lt;span class="c1"&gt;# 查找元素并click&lt;/span&gt;
&lt;span class="c1"&gt;# 简便写法&lt;/span&gt;
&lt;span class="n"&gt;click_link&lt;/span&gt; &lt;span class="s1"&gt;'link'&lt;/span&gt;
&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s1"&gt;'链接'&lt;/span&gt;
&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s1"&gt;'link_class'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="textarea,text"&gt;textarea,text&lt;/h4&gt;&lt;pre class="highlight html"&gt;&lt;code&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;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/input&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'name'&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;'sample'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="select"&gt;select&lt;/h4&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;男性&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;女性&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="s1"&gt;'男性'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="s1"&gt;'sex'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="checkbox"&gt;checkbox&lt;/h4&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="nt"&gt;&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;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;男&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sex'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;uncheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sex'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="radio"&gt;radio&lt;/h4&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&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;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sex_1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;男性&lt;span class="nt"&gt;&amp;lt;/span&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;label&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;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sex"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sex_2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;女性&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sex_1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 选择男性&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="上传文件"&gt;上传文件&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'input[type="file"]'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attach_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'spec/data/upload/sample.jpg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="其他的操作"&gt;其他的操作&lt;/h4&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;double_click&lt;/td&gt;
&lt;td&gt;双击&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;drag_to(elem)&lt;/td&gt;
&lt;td&gt;拖动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hover&lt;/td&gt;
&lt;td&gt;hover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;right_click&lt;/td&gt;
&lt;td&gt;右击&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;send_keys('text')&lt;/td&gt;
&lt;td&gt;输入 text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Element" rel="nofollow" target="_blank" title=""&gt;具体参考这里 Capybara::Node::Element&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="判断"&gt;判断&lt;/h3&gt;
&lt;p&gt;判断页面的 URL&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_path&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="n"&gt;new_user_path&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;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_no_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="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_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&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;'test@sian.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'hidden'&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="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="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_css&lt;/span&gt; &lt;span class="s1"&gt;'.invalid-feedback'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不好判断的，可以查找元素，然后得到属性来判断&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;'delete'&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"删除"&lt;/span&gt; &lt;span class="na"&gt;data-confirm=&lt;/span&gt;&lt;span class="s"&gt;"确认删除吗?"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#delete'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'data-confirm'&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="s1"&gt;'确认删除吗?'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers" rel="nofollow" target="_blank" title=""&gt;具体可以参考这里 Capybara::RSpecMatchers&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="测试中的一些技巧"&gt;测试中的一些技巧&lt;/h2&gt;&lt;h3 id="within 限定操作在某一块画面"&gt;within 限定操作在某一块画面&lt;/h3&gt;&lt;pre class="highlight html"&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;"section1"&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;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sport1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sport"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;checked=&lt;/span&gt;&lt;span class="s"&gt;"checked"&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;label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sport2"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sport"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2"&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&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;"section2"&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;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sleep1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sleep"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;checked=&lt;/span&gt;&lt;span class="s"&gt;"checked"&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;label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sleep2"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sleep"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2"&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&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s1"&gt;'.section1'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;choose&lt;/span&gt; &lt;span class="s1"&gt;'是'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s1"&gt;'.section2'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;choose&lt;/span&gt; &lt;span class="s1"&gt;'否'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，如果查找太复杂的话，可以考虑给它一个 ID 或者 Class，让查找元素尽量简单&lt;/p&gt;
&lt;h3 id="对新开的窗口进行测试"&gt;对新开的窗口进行测试&lt;/h3&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/rule"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;打开新窗口&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;sample_path&lt;/span&gt;
&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s1"&gt;'打开新窗口'&lt;/span&gt;
&lt;span class="n"&gt;within_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="execute_script"&gt;execute_script&lt;/h3&gt;
&lt;p&gt;在页面直接执行 JS 代码，对于一些使用 capybara 难以模拟的操作可以使用&lt;/p&gt;

&lt;p&gt;比如用 dropzone.js 来上传图片，用 capybara 没找到如何测试&lt;/p&gt;

&lt;p&gt;这个时候可以执行 JS 来模拟图片的上传&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;drop_in_dropzone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Generate a fake input selector&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;execute_script&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;JS&lt;/span&gt;&lt;span class="sh"&gt;
    fakeFileInput = $('&amp;lt;input/&amp;gt;').attr(
      {id: 'fakeFileInput', type:'file'}
    ).appendTo('body');
&lt;/span&gt;&lt;span class="no"&gt;  JS&lt;/span&gt;
  &lt;span class="c1"&gt;# Attach the file to the fake input selector&lt;/span&gt;
  &lt;span class="n"&gt;attach_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"fakeFileInput"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Add the file to a fileList array&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;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"var fileList = [fakeFileInput.get(0).files[0]]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Trigger the fake drop event&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;execute_script&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;JS&lt;/span&gt;&lt;span class="sh"&gt;
    var e = $.Event('drop', { dataTransfer : { files : [fakeFileInput.get(0).files[0]] } });
    $('.dropzone')[0].dropzone.listeners[0].events.drop(e);
&lt;/span&gt;&lt;span class="no"&gt;  JS&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;h3 id="少用sleep"&gt;少用 sleep&lt;/h3&gt;
&lt;p&gt;比如在用 ajax 进行异步操作的时候，必须等一段时间才可以继续测试，可以&lt;code&gt;sleep 秒数&lt;/code&gt;来等待&lt;/p&gt;

&lt;p&gt;缺点是 sleep 的秒数对于不同环境是不一样的，也许自己的机子可以跑，放到其他测试环境下就不行了&lt;/p&gt;

&lt;p&gt;解决方法&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;默认的等待时间设置长一些，比如 30 秒&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_max_wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="c1"&gt;# default 2s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 find，have_selector(默认等待时间是 default_max_wait_time)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 还可以指定最大等待时间&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;wait: &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="不访问外部网络"&gt;不访问外部网络&lt;/h3&gt;
&lt;p&gt;比如你需要访问一个外部 API，可以用&lt;code&gt;webmock gem&lt;/code&gt;来模拟&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://www.example.com'&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="c1"&gt;# 会出现这样的显示&lt;/span&gt;

&lt;span class="no"&gt;WebMock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NetConnectNotAllowedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="no"&gt;Real&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;connections&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

       &lt;span class="no"&gt;You&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;stub&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="ss"&gt;snippet:

       &lt;/span&gt;&lt;span class="n"&gt;stub_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://www.example.com/"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
         &lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s1"&gt;'Accept'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'*/*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s1"&gt;'Accept-Encoding'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'gzip;q=1.0,deflate;q=0.6,identity;q=0.3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s1"&gt;'User-Agent'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'Ruby'&lt;/span&gt;
           &lt;span class="p"&gt;}).&lt;/span&gt;
         &lt;span class="nf"&gt;to_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方便的是提示的 stub_request 的代码很多时候可以直接使用&lt;/p&gt;
&lt;h3 id="不要什么都用feature测试"&gt;不要什么都用 feature 测试&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;不用 feature 测试就可以测试的就不用 UI 测试&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;比如分离功能出来进行单体测试、使用 controller 测试等&lt;/p&gt;
&lt;h2 id="调试"&gt;调试&lt;/h2&gt;
&lt;p&gt;写 feature 测试的时候，最麻烦的可能是不好查找问题&lt;/p&gt;

&lt;p&gt;可以使用以下的方法让调试稍微轻松些&lt;/p&gt;
&lt;h3 id="pry-byebug"&gt;pry-byebug&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'pry-byebug'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最方便的 debug 工具，大家应该都熟悉，就不多说了&lt;/p&gt;
&lt;h3 id="log"&gt;log&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; log/test.log
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="测试失败的时候自动截图"&gt;测试失败的时候自动截图&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'capybara-screenshot'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;失败的时候，会自动将 html 和 png 的文件保存下来&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTML screenshot: /www/sample/tmp/capybara/screenshot_2019-03-30-17-55-11.103.html
Image screenshot: /www/sample/tmp/capybara/screenshot_2019-03-30-17-55-11.103.png
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="显示JS的debug信息"&gt;显示 JS 的 debug 信息&lt;/h3&gt;
&lt;p&gt;想知道 JS 是否被执行了，可以在 JS 里面&lt;code&gt;console.log('message')&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'capybara-chromedriver-logger'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样 JS 的 error 和 log 都会显示出来&lt;/p&gt;
&lt;h3 id="显示浏览器画面"&gt;显示浏览器画面&lt;/h3&gt;
&lt;p&gt;chrome headless 运行的时候，不好 debug，可以按照以下的方法显示浏览器&lt;/p&gt;
&lt;h4 id="1. 添加debug用的docker"&gt;1. 添加 debug 用的 docker&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docker-compose-selenium-debug.yml

version: '3'

services:
  snode:
    image: selenium/standalone-chrome-debug:3.141.59
    ports:
      - 5900:5900
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="2. 连接vnc"&gt;2. 连接 vnc&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;open vnc://localhost:5900
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3. 执行"&gt;3. 执行&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 启动&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml:docker-compose-selenium-debug.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# 执行rspec&lt;/span&gt;
&lt;span class="nv"&gt;$ NO_HEADLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec spec/features/xxxx.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候可以直接在 vnc 服务器上看到浏览器的画面&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara" rel="nofollow" target="_blank" title=""&gt;RubyDoc - Capybara&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 01 Apr 2019 17:23:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/38334</link>
      <guid>https://ruby-china.org/topics/38334</guid>
    </item>
    <item>
      <title>用 Terraform 自动化构建基础设施</title>
      <description>&lt;p&gt;目前为了更快的开发产品，现在很多项目都对基础设施进行了代码化 (Infrastructure as Code)。&lt;/p&gt;

&lt;p&gt;现在的项目中使用 Terraform 对基础设施进行了代码化，分享下 Terraform 的使用。&lt;/p&gt;
&lt;h2 id="为什么要让基础设施代码化"&gt;为什么要让基础设施代码化&lt;/h2&gt;
&lt;p&gt;在使用 Terraform 之前，基本上是在 AWS 管理画面进行操作或者用 AWS CLI 写一些脚本来构建。&lt;/p&gt;

&lt;p&gt;主要有以下一些问题&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 AWS 的管理画面上面操作，比较费时间&lt;/li&gt;
&lt;li&gt;不容易管理每个人 AWS 的操作记录&lt;/li&gt;
&lt;li&gt;对操作的审查比较麻烦，容易误操作&lt;/li&gt;
&lt;li&gt;使用了哪些 AWS 的资源必须一个一个查看，不容易对整体进行把握&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用 Terraform 可以比较好的解决以上问题，以下是用 Terraform 修改的流程&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;把需要修改的部分用 Terraform 代码写好，然后 Pull Request&lt;/li&gt;
&lt;li&gt;相关人员进行代码审查 (Code Review)&lt;/li&gt;
&lt;li&gt;没问题的话 merge，然后在 CI 自动执行&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;好处是修改流程类似 app 修改的流程，大家很容易上手。&lt;/p&gt;

&lt;p&gt;操作时间短，所有的更改记录都进行了版本管理，容易对修改进行审查。&lt;/p&gt;

&lt;p&gt;使用了什么资源，看代码一目了然。&lt;/p&gt;
&lt;h2 id="什么是Terraform"&gt;什么是 Terraform&lt;/h2&gt;
&lt;p&gt;开发了 Vagrant 的&lt;a href="https://www.hashicorp.com/" rel="nofollow" target="_blank" title=""&gt;hashicorp 公司&lt;/a&gt;开发的用代码来管理基础设施的一个工具，支持绝大多数的平台。&lt;a href="https://www.terraform.io/docs/providers/" rel="nofollow" target="_blank" title=""&gt;查看支持的平台&lt;/a&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;&lt;a href="https://www.terraform.io/docs/providers/aws/" rel="nofollow" target="_blank" title=""&gt;开发文档可以在这里查看&lt;/a&gt;，很多 sample，很多时候稍微修改一下就可以用&lt;/p&gt;
&lt;h3 id="1. resource"&gt;1. resource&lt;/h3&gt;
&lt;p&gt;对应于 aws 的 resouce&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;建立VPC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&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="err"&gt;cidr_block&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.1.0.0/16"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;assign_generated_ipv&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;_cidr_block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;tags&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="err"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sample-vpc"&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="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;建立ecs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo"&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="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"white-hart"&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;h3 id="2. data"&gt;2. data&lt;/h3&gt;
&lt;p&gt;有时候需要从 AWS 中获取信息的时候可以使用 data&lt;/p&gt;

&lt;p&gt;比如获取目前操作中的 AWS 账号信息&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"current"&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;h3 id="3. 变量"&gt;3. 变量&lt;/h3&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;定义变量&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"region"&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="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sample-region"&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="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"access_key"&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="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret_key"&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="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;使用变量&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws"&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="err"&gt;access_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${var.access_key}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;secret_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${var.secret_key}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;region&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${var.region}"&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;h2 id="执行Terraform"&gt;执行 Terraform&lt;/h2&gt;
&lt;p&gt;Terraform 会执行当前文件夹下面的所有.tf 文件&lt;/p&gt;

&lt;p&gt;预执行，不会对 AWS 作出任何改动，可以确认下下有哪些改变&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform plan

&lt;span class="nt"&gt;------------------------------------------------------------------------&lt;/span&gt;

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + module.shared_fluentd.aws_vpc.app
      &lt;span class="nb"&gt;id&lt;/span&gt;:                               &amp;lt;computed&amp;gt;
      arn:                              &amp;lt;computed&amp;gt;
      assign_generated_ipv6_cidr_block: &lt;span class="s2"&gt;"true"&lt;/span&gt;
      cidr_block:                       &lt;span class="s2"&gt;"10.1.0.0/16"&lt;/span&gt;
      tags.Name:                        &lt;span class="s2"&gt;"sample-vpc"&lt;/span&gt;

Plan: 1 to add， 0 to change， 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际执行，这里会再次输出有哪些改变，然后输入 yes 就会在 AWS 上面进行构建&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="一些技巧总结"&gt;一些技巧总结&lt;/h2&gt;&lt;h3 id="1. 使用module"&gt;1. 使用 module&lt;/h3&gt;
&lt;p&gt;比如要同时创建 sandbox 和 production 的 fluentd 服务器，基本都是一样的，只是一些参数不同。&lt;/p&gt;

&lt;p&gt;这时候就可以使用 module 来归类类似的代码，然后传入变量来构建。&lt;/p&gt;

&lt;p&gt;比如要同时创建 sandbox 和 production 的的 fluentd 服务器。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 文件结构
terraform
├── modules
│&amp;nbsp;&amp;nbsp; ├── fluentd
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── main.tf
├── production_instance.tf
├── sandbox_instance.tf
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terraform/sandbox_instance.tf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sandbox_fluentd"&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="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./modules/fluentd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;vpc_id&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your sandbox vpc id"&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="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;terraform/production_instance.tf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production_fluentd"&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="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./modules/fluentd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;vpc_id&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your production vpc id"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan -target module.shared_fluentd
terraform plan -target module.production_fluentd
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. 按照resource分类文件"&gt;2. 按照 resource 分类文件&lt;/h3&gt;
&lt;p&gt;写在一个文件里面虽然也可以执行，但是内容多的时候，阅读起来不太容易，这个时候可以分成几个文件。&lt;/p&gt;

&lt;p&gt;比如按照 aws resource，变量，data 来分类。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── fluentd
│&amp;nbsp;&amp;nbsp; ├── cloud_watch.tf
│&amp;nbsp;&amp;nbsp; ├── data.tf
│&amp;nbsp;&amp;nbsp; ├── ecs.tf
│&amp;nbsp;&amp;nbsp; ├── security_group.tf
│&amp;nbsp;&amp;nbsp; ├── task_role.tf
│&amp;nbsp;&amp;nbsp; └── variable.tf
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 使用depends_on来指定依赖"&gt;3. 使用 depends_on 来指定依赖&lt;/h3&gt;
&lt;p&gt;用的很少，基本不用指定，只有必须当某个操作依赖于另外一个操作时才用。&lt;/p&gt;

&lt;p&gt;比如在使用 AWS Service Discovery 的时候，必须当 aws_service_discovery 构建以后，才可以获得被创建的 DNS，这个时候就可以使用 depends_on&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_route53_zone"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fluentd"&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="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${aws_service_discovery_public_dns_namespace.fluentd.name}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;depends_on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws_service_discovery_public_dns_namespace.fluentd"&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;h3 id="4. 从已有的环境生成terraform代码"&gt;4. 从已有的环境生成 terraform 代码&lt;/h3&gt;
&lt;p&gt;官方没有提供这个功能，有人做了个 GEM，用了一下，AWS 基本都可以自动生成代码&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dtan4/terraforming" rel="nofollow" target="_blank" title=""&gt;terraforming&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你已经构建好了环境，可以用这个来自动生成代码，避免打字手酸。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;简单介绍了 Terraform 的优点和基本使用方法。&lt;/p&gt;

&lt;p&gt;我觉的它最大的优点是学习成本低，使用方便。&lt;/p&gt;

&lt;p&gt;如果你想简单高效的构建你的基础设施，可以考虑使用 terraform。&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Wed, 12 Dec 2018 16:43:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/37891</link>
      <guid>https://ruby-china.org/topics/37891</guid>
    </item>
    <item>
      <title>用 Docker 构建开发环境</title>
      <description>&lt;p&gt;最近用 docker 进行了开发环境的构建&lt;/p&gt;

&lt;p&gt;本文主要介绍如何使用 docker 进行开发环境的构建&lt;/p&gt;
&lt;h2 id="为什么要用docker构建开发环境"&gt;为什么要用 docker 构建开发环境&lt;/h2&gt;
&lt;p&gt;目前比较主流的方式应该是 vagrant 和 docker，对优缺点进行了比较&lt;/p&gt;
&lt;h3 id="1. vagrant"&gt;1. vagrant&lt;/h3&gt;
&lt;p&gt;优点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;设置比较简单&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;比较耗费磁盘，内存等资源&lt;/li&gt;
&lt;li&gt;首次启动 (provision) 的时间比较长&lt;/li&gt;
&lt;li&gt;启动的时间比较慢&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. docker"&gt;2. docker&lt;/h3&gt;
&lt;p&gt;优点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;比较节省资源&lt;/li&gt;
&lt;li&gt;首次启动 (build image) 比较快

&lt;ul&gt;
&lt;li&gt;目前项目 vagrant 要用几十分钟，docker 只用几分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;启动非常快

&lt;ul&gt;
&lt;li&gt;vagrant 要几分钟，用 docker 只要几秒钟&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p style="color:#888888;"&gt;备注：消耗的时间是我目前项目的时间，每个项目有所不同&lt;/p&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;学习成本相对较高，尤其是 deploy 到生产环境的时候需要一定的学习成本&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="docker相关术语"&gt;docker 相关术语&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Image(镜像)

&lt;ul&gt;
&lt;li&gt;一个文件系统，里面包含了需要运行在 Container 的所有东西&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Container(容器)

&lt;ul&gt;
&lt;li&gt;镜像的启动实例&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dockerfile(构建镜像的配置文件)&lt;/li&gt;
&lt;li&gt;Compose(一个工具 使构建多个容器变得更加容易)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;具体说明可以在这里查看&lt;br&gt;
&lt;a href="https://docs.docker.com/v17.09/engine/userguide/storagedriver/imagesandcontainers/" rel="nofollow" target="_blank"&gt;https://docs.docker.com/v17.09/engine/userguide/storagedriver/imagesandcontainers/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="本次构建的开发环境"&gt;本次构建的开发环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;centos7&lt;/li&gt;
&lt;li&gt;ruby&lt;/li&gt;
&lt;li&gt;rails&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="准备工作"&gt;准备工作&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;准备一台 mac，windows 的话可以先用 virtualBox 开个虚拟机&lt;/li&gt;
&lt;li&gt;准备一个 rails 程序&lt;/li&gt;
&lt;li&gt;安装 docker for mac&lt;/li&gt;
&lt;li&gt;安装 docker-compose&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="构建的步骤"&gt;构建的步骤&lt;/h2&gt;
&lt;p&gt;最后的相关文件结构，下面逐步讲解每个文件的内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dockerfile/
├── app
│&amp;nbsp;&amp;nbsp; ├── Dockerfile
│&amp;nbsp;&amp;nbsp; └── scripts
│&amp;nbsp;&amp;nbsp;     └── build.sh
└── mysql
    ├── Dockerfile
    └── conf
        └── my.cnf
├── build_all.sh
docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1. web服务器镜像设置"&gt;1. web 服务器镜像设置&lt;/h3&gt;
&lt;p&gt;设置 dockerfile&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# dockerfile/app/Dockerfile&lt;/span&gt;

&lt;span class="c"&gt;# OS的镜像可以在这里查看 https://hub.docker.com/_/centos/&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; centos:centos7&lt;/span&gt;

&lt;span class="c"&gt;# set operator user env&lt;/span&gt;
&lt;span class="c"&gt;# 追加一个普通用户book来进行bundle install&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; LANG=en_US.UTF-8 OPERATOR_USER=book OPERATOR_GROUP=book&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; OPERATOR_UID=500 OPERATOR_GID=500&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; RUBY_VERSION=2.5.1&lt;/span&gt;

&lt;span class="c"&gt;# 在这里进行安装ruby等详细设置&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; scripts /tmp/scripts&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bash &lt;span class="nt"&gt;-x&lt;/span&gt; /tmp/scripts/build.sh

&lt;span class="c"&gt;# 设置进入docker以后的默认目录&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /www/ruby-book/&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="c"&gt;# dockerfile/app/scripts/build.sh&lt;/span&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# -e stop when error occurred&lt;/span&gt;
&lt;span class="c"&gt;# -x show executed command&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt;

&lt;span class="c"&gt;# add book user, book group&lt;/span&gt;
groupadd &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_GROUP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
useradd &lt;span class="nt"&gt;--create-home&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_UID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# add server folder use ruby user&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /www/ruby-book
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_UID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPERATOR_GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; /www/ruby-book

&lt;span class="c"&gt;# install package&lt;/span&gt;
yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="c"&gt;# 不是全部都需要的，可以选择安装自己想要装的工具&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git make gcc bzip2 openssl-devel readline-devel zlib-devel curl-devel mysql-devel which &lt;span class="nb"&gt;tar &lt;/span&gt;wget &lt;span class="nb"&gt;sudo &lt;/span&gt;libmysqlclient-dev

&lt;span class="c"&gt;# install rbenv ruby-build&lt;/span&gt;
git clone git://github.com/sstephenson/rbenv.git /usr/local/rbenv
git clone git://github.com/sstephenson/ruby-build.git /usr/local/rbenv/plugins/ruby-build

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export RBENV_ROOT="/usr/local/rbenv"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/profile.d/rbenv.sh
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/profile.d/rbenv.sh
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(rbenv init -)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/profile.d/rbenv.sh

&lt;span class="c"&gt;# install ruby&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; /etc/profile.d/rbenv.sh
rbenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
rbenv rehash
rbenv global &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# install bundler&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. mysql镜像设置"&gt;2. mysql 镜像设置&lt;/h3&gt;
&lt;p&gt;设置 dockerfile&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# dockerfile/mysql/Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mysql:5.7.22&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; conf/my.cnf /etc/mysql/conf.d/app.cnf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 mysql 设置文件&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# dockerfile/mysql/conf/my.cnf&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;client]
socket &lt;span class="o"&gt;=&lt;/span&gt; /tmp/mysql.sock

&lt;span class="o"&gt;[&lt;/span&gt;mysql]
default-character-set&lt;span class="o"&gt;=&lt;/span&gt;utf8mb4

&lt;span class="o"&gt;[&lt;/span&gt;mysqld]
explicit_defaults_for_timestamp &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;sql_mode &lt;span class="o"&gt;=&lt;/span&gt; TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY

character_set_server &lt;span class="o"&gt;=&lt;/span&gt; utf8mb4
datadir &lt;span class="o"&gt;=&lt;/span&gt; /var/lib/mysql
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 编译镜像"&gt;3. 编译镜像&lt;/h3&gt;
&lt;p&gt;使用 docker build 命令来进行 build 镜像文件&lt;br&gt;
因为要 build 两个镜像所以写了个脚本来一次行进行 build&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# dockerfile/build_all.sh&lt;/span&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt;
&lt;span class="nv"&gt;DIRNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DIRNAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/../
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RAILS_ROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;dockerfile/

&lt;span class="nv"&gt;IMAGE_REPOSITORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;book/book
&lt;span class="nb"&gt;cd &lt;/span&gt;app
&lt;span class="nv"&gt;APP_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"7_20180531"&lt;/span&gt;
docker build &lt;span class="nt"&gt;--force-rm&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_REPOSITORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:app-centos-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; ../mysql
docker build &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; mysql:5.7.22 &lt;span class="nb"&gt;.&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;./build_all.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 docker images 来查看生成的镜像文件&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker images

REPOSITORY          TAG                     IMAGE ID            CREATED             SIZE
mysql               5.7.22                  1da5a40f1204        15 minutes ago      372MB
book/book           app-centos-7_20180531   6004010a3f19        16 minutes ago      614MB
centos              centos7                 75835a67d134        4 days ago          200MB
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. 设置docker-compose"&gt;4. 设置 docker-compose&lt;/h3&gt;
&lt;p&gt;设置 docker-compose 来启动 docker container&lt;br&gt;&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RAILS_DATABASE_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;

    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;book/book:app-centos-7_20180531&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./:/www/ruby-book:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-bundle:/www/ruby-book/vendor/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-tmp:/www/ruby-book/tmp&lt;/span&gt;

    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7.22&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ALLOW_EMPTY_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/mysql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-log:/var/log/mysql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./dockerfile/mysql/conf/my.cnf:/etc/mysql/conf.d/app.cnf&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3306:3306&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-tmp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;镜像重新 build 以后还希望保留的文件都可以设置成 volumes、这样镜像重新 build 以后也会保留下来&lt;/p&gt;

&lt;p&gt;可以使用 volume 命令来查看和删除&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker volume ls
$ docker volume rm {volume name}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="5. how to use"&gt;5. how to use&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# build all images
$ cd dockerfile/
$ ./build_all.sh

# create container run backgroud
$ docker-compose -f docker-compose.yml up -d

# 进入web服务器
$ docker-compose exec --user book app bash  # book用户
$ docker-compose exec app bash # root用户

# bundle install , create db
$ bundle install --path vendor/bundle
$ bundle exec rake db:create
$ bundle exec rake db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="测试"&gt;测试&lt;/h2&gt;
&lt;p&gt;访问以下页面呈现欢迎界面就说明完成了&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:3000/" rel="nofollow" target="_blank"&gt;http://localhost:3000/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="参考文章"&gt;参考文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.devteam.space/blog/docker-vs-vagrant-which-is-better-for-development/" rel="nofollow" target="_blank" title=""&gt;Docker vs Vagrant: Which is Better for Development?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.masterzendframework.com/docker-development-environment/" rel="nofollow" target="_blank" title=""&gt;How To Build a Local Development Environment Using Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nrempel.com/guides/docker-development-environment/" rel="nofollow" target="_blank" title=""&gt;How to Create a Docker Development Environment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 15 Oct 2018 18:33:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/37628</link>
      <guid>https://ruby-china.org/topics/37628</guid>
    </item>
    <item>
      <title>修改 Ruby 核心类和模块的方法</title>
      <description>&lt;p&gt;ruby 的类和模块是开放的，这是 ruby 的重要特征之一&lt;/p&gt;

&lt;p&gt;ruby 核心类的扩展是非常容易的&lt;br&gt;
总结了一下修改 ruby 核心类的一些常用方法以及可能面临的风险&lt;/p&gt;
&lt;h2 id="1. 追加一个新功能"&gt;1. 追加一个新功能&lt;/h2&gt;
&lt;p&gt;最常见的方法就是添加一个新的不存在的方法&lt;/p&gt;

&lt;p&gt;好处是不会和其他函数冲突，坏处是你无法判断是否会和 ruby 的下一本版本的同名函数冲突&lt;/p&gt;

&lt;p&gt;比较经典的有 active support 对 ruby 核心类的扩展&lt;/p&gt;

&lt;p&gt;比如这个函数返回单词的复数形式&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/string/inflections.rb#L33&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pluralize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:en&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&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;Symbol&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;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="nb"&gt;dup&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Inflector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluralize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&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;h2 id="2. 保持已有函数的接口，只是添加一些新的行为"&gt;2. 保持已有函数的接口，只是添加一些新的行为&lt;/h2&gt;
&lt;p&gt;比如添加日志，调试信息等等&lt;/p&gt;

&lt;p&gt;下面演示了在 ruby 的方法中添加一个输出&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;_ruby_chars&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chars&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'char method done'&lt;/span&gt;
    &lt;span class="n"&gt;_ruby_chars&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chars&lt;/span&gt;
&lt;span class="c1"&gt;# 输出&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;感觉这种相对安全，但是实际中用的不太多&lt;/p&gt;
&lt;h2 id="3. 扩展已有的函数"&gt;3. 扩展已有的函数&lt;/h2&gt;
&lt;p&gt;比如 ActiveSupport 对 Time 的 to_s 方法的扩展&lt;br&gt;
通过传递不同的参数来进行扩展&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 一部分源代码&lt;/span&gt;
&lt;span class="c1"&gt;# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/time/conversions.rb#L6&lt;/span&gt;
&lt;span class="no"&gt;DATE_FORMATS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;db: &lt;/span&gt;&lt;span class="s2"&gt;"%Y-%m-%d %H:%M:%S"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;number: &lt;/span&gt;&lt;span class="s2"&gt;"%Y%m%d%H%M%S"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_formatted_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:default&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;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DATE_FORMATS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;formatter&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;:call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&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;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&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;to_default_s&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;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:to_default_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:to_s&lt;/span&gt;
&lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:to_formatted_s&lt;/span&gt;

&lt;span class="c1"&gt;# 得到扩展后的结果&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="ss"&gt;:db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2018-09-29 12:47:10"&lt;/span&gt;

&lt;span class="c1"&gt;## 得到ruby默认的结果&lt;/span&gt;
&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2018-09-29 12:47:36 +0000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种扩展也是有风险的，你无法知道 ruby 的下个版本是否也有相同的扩展导致其中一个被覆盖&lt;/p&gt;
&lt;h2 id="4. 扩展独立的对象"&gt;4. 扩展独立的对象&lt;/h2&gt;
&lt;p&gt;这可以说是比较安全的扩展的方法，影响的范围也要小很多&lt;/p&gt;

&lt;p&gt;比如要添加一个移除空格的方法到 string 对象，注意不是扩展到 String 类&lt;br&gt;
可以通过 extend 方法来实现&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;StringExtension&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remove_space&lt;/span&gt;
    &lt;span class="nb"&gt;gsub&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="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'　'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'hello world'&lt;/span&gt;
&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;StringExtension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_space&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"helloworld"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有时候我们需要覆盖 Gem 已经存在的方法，也可以使用这个方法来不破坏 Gem 并且实现我们的目的&lt;/p&gt;

&lt;p&gt;如果你使用 devise 的话，在重设密码之后会默认发送邮件&lt;br&gt;
可以对 user 对象覆盖它默认的方法，进行单独处理&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_reset_password_instructions_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# do something&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="5. refinement"&gt;5. refinement&lt;/h2&gt;
&lt;p&gt;扩展核心类最大的风险在于影响范围非常大&lt;br&gt;
如果我们可以减小范围那么风险就小了很多&lt;br&gt;
ruby2.1 开始提供了 refinement 来解决这个问题&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;StringExtension&lt;/span&gt;
  &lt;span class="n"&gt;refine&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remove_space&lt;/span&gt;
      &lt;span class="nb"&gt;gsub&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="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'　'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 直接使用的话会出现一个error&lt;/span&gt;
&lt;span class="s1"&gt;'hello world'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_space&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;NoMethodError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt; &lt;span class="sb"&gt;`remove_space' for "hello world":String

# 使用using在指定的类里面使用
class Sample
  using StringExtension
  'hello world'.remove_space
end
=&amp;gt; "helloworld"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;总结了修改 rubu core 的一些方法和其中的风险&lt;br&gt;&lt;/p&gt;

&lt;p&gt;比较经典的要数 Active Support 对 ruby 的扩展，可以看到很多例子&lt;br&gt;
&lt;a href="https://github.com/rails/rails/tree/master/activesupport/lib/active_support/core_ext" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/tree/master/activesupport/lib/active_support/core_ext&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Active Support 因为非常有名，所以在一定程度上降低了风险&lt;br&gt;
在自己项目里面进行修改的话应该慎重考虑风险以后在进行&lt;/p&gt;
&lt;h2 id="参考文章"&gt;参考文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://borgs.cybrilla.com/tils/ruby-core-ext/" rel="nofollow" target="_blank" title=""&gt;Extend Core Classes in Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://guides.rubyonrails.org/active_support_core_extensions.html" rel="nofollow" target="_blank" title=""&gt;Active Support Core Extensions&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-doc.org/core-2.4.0/doc/syntax/refinements_rdoc.html" rel="nofollow" target="_blank" title=""&gt;Refinements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 08 Oct 2018 18:54:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/37598</link>
      <guid>https://ruby-china.org/topics/37598</guid>
    </item>
    <item>
      <title>cell gem 默认不进行 html escape 问题</title>
      <description>&lt;p&gt;现在的项目使用了 cell 这个 gem&lt;br&gt;
&lt;a href="https://github.com/trailblazer/cells" rel="nofollow" target="_blank"&gt;https://github.com/trailblazer/cells&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="4.0以前"&gt;4.0 以前&lt;/h2&gt;
&lt;p&gt;在 4.0 以前的时候使用的是 rails 的 render，默认会进行 html escape&lt;/p&gt;

&lt;p&gt;比如下面的代码是没有问题的&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;@text = '&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;'
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@text&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="4.0以后"&gt;4.0 以后&lt;/h2&gt;
&lt;p&gt;目前打算升级到 4.0&lt;/p&gt;

&lt;p&gt;但是 4.0 以后，cell 采用了其他的 render 方法，默认不进行 html escape, 必须像下面这样传递 escape 过后的值&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;@text = '&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;'
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="vi"&gt;@text&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="http://trailblazer.to/gems/cells/cells4.html" rel="nofollow" target="_blank"&gt;http://trailblazer.to/gems/cells/cells4.html&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Cells per default does not escape HTML. However, you may run into problems when using Rails helpers. Internally, those helpers often blindly escape. This is not Cells’ fault but a design flaw in Rails.
Everything related to #capture will cause problems - check this as an example. As you can see, this is Rails swinging the escape hammer. Please don’t blame us for escapes where they shouldn’t be. Rather open an issue on Rails and tell them to make their code better overrideable for us.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;但是每个字段自己写代码 escape 感觉很麻烦，而且万一忘记了，会出现安全问题&lt;/p&gt;
&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;目前把需要 escape 的都加了 html_escape 方法暂时对应了 (估计有漏的地方)&lt;/p&gt;

&lt;p&gt;没找到其他的好的方法，有遇到相同的问题的吗？&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 01 Oct 2018 19:05:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/37582</link>
      <guid>https://ruby-china.org/topics/37582</guid>
    </item>
    <item>
      <title>写 Responsive 页面的总结</title>
      <description>&lt;p&gt;最近用 responsive web design 对目前的网站一部分页面进行了重写&lt;br&gt;
总结了下对应 responsive web design 中遇到的问题和解决方法&lt;/p&gt;
&lt;h2 id="什么是responsive页面"&gt;什么是 responsive 页面&lt;/h2&gt;
&lt;p&gt;百度百科是这么定义的&lt;/p&gt;

&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;响应式网站设计是一种网络页面设计布局，其理念是：集中创建页面的图片排版大小，可以智能地根据用户行为以及使用的设备环境进行相对应的布局&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;我的理解是不管你是什么设备，我只管你的画面大小，根据你的画面给你最合适的显示&lt;/p&gt;
&lt;h2 id="multi-device support"&gt;multi-device support&lt;/h2&gt;
&lt;p&gt;现在的各种设备越来越多，你不得不对各个设备进行显示优化&lt;/p&gt;

&lt;p&gt;比如常见的有 pc、tablet、mobile，将来估计会不断增加&lt;/p&gt;

&lt;p&gt;尤其是手机 (mobile)，现在手机访问网站越来越成为主流，让开发人员不得不优化手机页面&lt;/p&gt;

&lt;p&gt;具体实现的技术有很多，大概总结了一下&lt;/p&gt;
&lt;h3 id="1. 做一个手机专用的网站(URL分开)"&gt;1. 做一个手机专用的网站 (URL 分开)&lt;/h3&gt;
&lt;p&gt;比如像这样&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PC  &lt;a href="http://xxxx.com/" rel="nofollow" target="_blank"&gt;http://xxxx.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;mobile  &lt;a href="http://xxxx.com/sp/" rel="nofollow" target="_blank"&gt;http://xxxx.com/sp/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;优点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以完全独立进行开发、管理&lt;/li&gt;
&lt;li&gt;可以设计成为完全不同的风格&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;相同的内容必须两边都要更新&lt;/li&gt;
&lt;li&gt;URL 不同，不利于 SEO&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. 做一个手机专用的网站(URL一样)"&gt;2. 做一个手机专用的网站 (URL 一样)&lt;/h3&gt;
&lt;p&gt;根据终端的 user agent 来判断类型&lt;br&gt;
然后返回不同的 HTML 文件&lt;/p&gt;

&lt;p&gt;优点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以设计成为完全不同的风格&lt;/li&gt;
&lt;li&gt;同一个 URL，对 SEO 比较有利&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对于相同的内容，必须更新多个 HTML 文件 (Rails 里面的 view)&lt;/li&gt;
&lt;li&gt;出现新的设备或者设备增加以后，需要更新设备的判定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3. responsive web design"&gt;3. responsive web design&lt;/h3&gt;
&lt;p&gt;优点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;同一个 URL，对 SEO 比较有利&lt;/li&gt;
&lt;li&gt;HTML 是同一个文件，更新起来比较方便&lt;/li&gt;
&lt;li&gt;google 推荐的实现方式&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;因为 CSS 会包含其他终端用的内容，有可能让 CSS 变大导致读取时间变长&lt;/li&gt;
&lt;li&gt;前端开发可能刚开始不太适应，会降低开发效率&lt;/li&gt;
&lt;li&gt;PC、mobile 的 UI 在设计的时候不能差异太大&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="为什么用responsive web design"&gt;为什么用 responsive web design&lt;/h2&gt;
&lt;p&gt;目前的项目用的是第二种办法，对 user agent 进行判断，然后返回不同的 HTML 文件&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pc 的时候 render index.html.erb&lt;/li&gt;
&lt;li&gt;sp 的时候 render index_sp.html.erb&lt;/li&gt;
&lt;li&gt;tablet 的时候 render index_tablet.html.erb&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;维护一段时间以后发现一些不方便的地方&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PC,mobile,tablet 的 view 文件基本差不多&lt;/li&gt;
&lt;li&gt;JS,CSS 也有很多重复的内容&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于各个页面的 pc、tablet、mobile 的内容是基本相同的&lt;br&gt;
所以采用了 responsive web design 来实现&lt;/p&gt;
&lt;h2 id="开发的大体流程"&gt;开发的大体流程&lt;/h2&gt;&lt;h3 id="决定断点"&gt;决定断点&lt;/h3&gt;
&lt;p&gt;决定最基本的断点&lt;br&gt;
这个可以根据你的网站访问的设备的大小来进行判定，每个网站应该会有些不同&lt;br&gt;
比如我是这样定义的&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;320px ～ 480px mobile&lt;/li&gt;
&lt;li&gt;481px ～ 768px tablet&lt;/li&gt;
&lt;li&gt;768px ～ 900px pc small&lt;/li&gt;
&lt;li&gt;900px ～       pc wide(1280px 以上的话固定为 1280px)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@uiuxlab/the-most-used-responsive-breakpoints-in-2017-of-mine-9588e9bd3a8a" rel="nofollow" target="_blank" title=""&gt;The Most Used Responsive Breakpoints in 2017 Of Mine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://uxplanet.org/responsive-design-best-practices-c6d3f5fd163b" rel="nofollow" target="_blank" title=""&gt;Responsive Design Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="CSS设计"&gt;CSS 设计&lt;/h3&gt;
&lt;p&gt;目前采用的是 ITCSS + BEM，具体可以参见 &lt;a href="https://ruby-china.org/topics/37440" title=""&gt;CSS 设计之 ITCSS&lt;/a&gt; 这篇文章&lt;/p&gt;

&lt;p&gt;因为网站的 mobile 访问量非常多&lt;br&gt;
所以先写 mobile 用的 CSS，然后在用 media-query 覆盖 mobile 的设定来写 tablet、PC&lt;/p&gt;
&lt;h3 id="1. 添加一个media-query的mixin"&gt;1. 添加一个 media-query 的 mixin&lt;/h3&gt;&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nt"&gt;max-screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="nt"&gt;screen&lt;/span&gt; &lt;span class="nt"&gt;and&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max-width&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@content&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;@mixin&lt;/span&gt; &lt;span class="nt"&gt;min-screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="nt"&gt;screen&lt;/span&gt; &lt;span class="nt"&gt;and&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;min-width&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@content&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;@mixin&lt;/span&gt; &lt;span class="nt"&gt;screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point-min&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point-max&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="nt"&gt;screen&lt;/span&gt; &lt;span class="nt"&gt;and&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;min-width&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point-min&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;and&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max-width&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;break-point-max&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@content&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;h3 id="2. 定义断点"&gt;2. 定义断点&lt;/h3&gt;&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$breakpoint-pc-min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;900px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$breakpoint-pc-small-max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;899px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$breakpoint-pc-small-min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;769px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$breakpoint-tablet-max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$breakpoint-tablet-min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;481px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$breakpoint-sp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;480px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 像下面这样先写mobile的，然后在分段写(当然不一定都要用到)"&gt;3. 像下面这样先写 mobile 的，然后在分段写 (当然不一定都要用到)&lt;/h3&gt;&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* 320px ~ 480px */&lt;/span&gt;
&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* 481px ~ 768px */&lt;/span&gt;
&lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nt"&gt;min-screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;breakpoint-tablet-min&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.container&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="cm"&gt;/* 769px ~ 899px */&lt;/span&gt;
&lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nt"&gt;min-screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;breakpoint-pc-small-min&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.container&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="cm"&gt;/* 900px ~  */&lt;/span&gt;
&lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nt"&gt;min-screen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;breakpoint-pc-min&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="遇到的问题"&gt;遇到的问题&lt;/h2&gt;&lt;h3 id="需要大脑里模拟画面变大变小以后的画面"&gt;需要大脑里模拟画面变大变小以后的画面&lt;/h3&gt;
&lt;p&gt;我觉的这是刚开始写的时候最困难的地方&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;设计人员一个页面准备各个断点的画面&lt;/li&gt;
&lt;li&gt;先写 320px ~ 480px 的 CSS，然后 tablet、PC 分段来写对应的 CSS&lt;/li&gt;
&lt;li&gt;练习，练习，再练习 我写了一个多月才基本适应这种思考方式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="组件的微调"&gt;组件的微调&lt;/h3&gt;
&lt;p&gt;在 ITCSS 中，用 Components 来进行组件的重用&lt;br&gt;
但是在 responsive web design 里面，同一个组件在每个页面大小，边距等也许会有所不同&lt;/p&gt;

&lt;p&gt;解决方法&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;共用的控件不写 media query&lt;/li&gt;
&lt;li&gt;加入了 page layout, 里面放每个页面专有的 CSS，在里面用 media query 进行具体的调整&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;大概是这样&lt;/p&gt;
&lt;pre class="highlight sass"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;"./components/parts/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;"./components/projects/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;"./pages/page1/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;"./pages/page2/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;"./pages/page3/*"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;components/parts  放 button，link 等通用的组件&lt;/li&gt;
&lt;li&gt;components/projects  放 book_card, author_card 等项目专用的组件&lt;/li&gt;
&lt;li&gt;pages 用来放每个页面专用的 CSS，用 media query 进行微调&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="相同的HTML对样式变化太大的地方CSS实现困难"&gt;相同的 HTML 对样式变化太大的地方 CSS 实现困难&lt;/h3&gt;
&lt;p&gt;比如 menu 做成这个网站这样&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://3.7designs.co/blog/2007/12/advanced-css-menu-trick/" rel="nofollow" target="_blank"&gt;http://3.7designs.co/blog/2007/12/advanced-css-menu-trick/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;pc 的时候&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/7888713/45208231-0eb75f00-b2c5-11e8-973d-9a4c884ce19e.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;mobile 的时候&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/7888713/45208606-f431b580-b2c5-11e8-9933-3cba3a54877d.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;解决方法&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;没找到太好的解决办法&lt;/li&gt;
&lt;li&gt;目前在 HTML 里面写了两个 menu 的 html，在用 media-query 来控制显示和隐藏

&lt;ul&gt;
&lt;li&gt;pc 的时候显示 pc 用的 html，mobile 的时候显示 mobile 用的 html&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;responsive web design 对于差别太大的 UI 实现起来不容易&lt;br&gt;
所以 UI 设计会尽可能的不要太大的差别&lt;br&gt;
如果你的项目 PC 和 mobile 的 UI 差别很大的话，不建议使用 responsive design&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;总结了 responsive web design 的优缺点和开发中的一些问题&lt;br&gt;
有好的意见请回复我&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Sun, 30 Sep 2018 12:37:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/37575</link>
      <guid>https://ruby-china.org/topics/37575</guid>
    </item>
    <item>
      <title>CSS 设计之 ITCSS</title>
      <description>&lt;h2 id="ITCSS简介"&gt;ITCSS 简介&lt;/h2&gt;
&lt;p&gt;CSS 设计有很多方法论，比如比较常见的&lt;a href="http://oocss.org/" rel="nofollow" target="_blank" title=""&gt;OOCSS&lt;/a&gt;，&lt;a href="http://getbem.com/introduction/" rel="nofollow" target="_blank" title=""&gt;BEM&lt;/a&gt;，&lt;a href="https://smacss.com/" rel="nofollow" target="_blank" title=""&gt;SMACSS&lt;/a&gt;等等&lt;/p&gt;

&lt;p&gt;目前的项目中使用了 ITCSS + BEM，感觉不错，这里做一下介绍&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itcss.io/" rel="nofollow" target="_blank" title=""&gt;ITCSS&lt;/a&gt;，这是由&lt;a href="https://csswizardry.com/" rel="nofollow" target="_blank" title=""&gt;csswizardry&lt;/a&gt;提倡的一个 CSS 设计方法论，他可以让你更好的管理、维护你的项目的 CSS&lt;/p&gt;

&lt;p&gt;ITCSS 的相关资料&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?reload=9&amp;amp;v=1OKZOV-iLj4" rel="nofollow" target="_blank" title=""&gt;youtube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/dafed/managing-css-projects-with-itcss" rel="nofollow" target="_blank" title=""&gt;PPT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于如何管理好项目的 CSS，ITCSS 认为需要做到以下几点&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;sane environment&lt;/strong&gt; that is accessible to lots of people. &lt;/li&gt;
&lt;li&gt;To &lt;strong&gt;tame and manage&lt;/strong&gt; source order and the cascade. &lt;/li&gt;
&lt;li&gt;To create &lt;strong&gt;a place for everything&lt;/strong&gt; to live (new and old). &lt;/li&gt;
&lt;li&gt;To &lt;strong&gt;reduce&lt;/strong&gt; waste and redundancy. &lt;/li&gt;
&lt;li&gt;To &lt;strong&gt;end the Specificity Wars&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://speakerdeck.com/dafed/managing-css-projects-with-itcss" rel="nofollow" target="_blank"&gt;https://speakerdeck.com/dafed/managing-css-projects-with-itcss&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;具体采用以下的解决办法&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manages&lt;/strong&gt; source order. &lt;/li&gt;
&lt;li&gt;Filters &lt;strong&gt;explicitness&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tames&lt;/strong&gt; the cascade. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanitises&lt;/strong&gt; inheritance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://speakerdeck.com/dafed/managing-css-projects-with-itcss" rel="nofollow" target="_blank"&gt;https://speakerdeck.com/dafed/managing-css-projects-with-itcss&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;大概意思是使用 ITCSS 可以帮助你&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;管理 CSS 代码的书写顺序&lt;/li&gt;
&lt;li&gt;过滤明确性？估计是说分层来明确每层 CSS 的作用&lt;/li&gt;
&lt;li&gt;驯服 CSS cascade(权重)&lt;/li&gt;
&lt;li&gt;安全的使用继承&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="layers"&gt;layers&lt;/h2&gt;
&lt;p&gt;ITCSS 把 CSS 分成了以下的几层&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;项目使用的全局变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;mixin，function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;td&gt;最基本的设定 normalize.css，reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;type selector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Objects&lt;/td&gt;
&lt;td&gt;不经过装饰 (Cosmetic-free) 的设计模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Components&lt;/td&gt;
&lt;td&gt;UI 组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trumps&lt;/td&gt;
&lt;td&gt;helper 唯一可以使用 important! 的地方&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/7888713/44897228-f039ec80-ad35-11e8-91bd-a521345ef2ec.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;可以看出这是一个倒三角形&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;越往下就越具体，越局限于使用在某个具体的画面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="layer的增减"&gt;layer 的增减&lt;/h3&gt;
&lt;p&gt;这些 layer 你不需要都使用，根据你项目的情况可以对 layer 进行增减&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;比如你想像 OOCSS 一样的话，可以删除掉 Objects layer&lt;/li&gt;
&lt;li&gt;想向 SMACSS 那样添加一个 Themes 的话，可以在 Components layer 下面追加一个 Themes layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;github 有非常多的 ITCSS 的项目，有兴趣的朋友可以研究下&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/search?l=CSS&amp;amp;q=itcss&amp;amp;type=Repositories&amp;amp;utf8=%E2%9C%93" rel="nofollow" target="_blank" title=""&gt;ITCSS github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;下面介绍下每个 layer 的具体内容&lt;/p&gt;

&lt;p&gt;sample code 用的是 sass，命名用的是&lt;a href="http://getbem.com/introduction/" rel="nofollow" target="_blank" title=""&gt;BEM&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Settings"&gt;Settings&lt;/h2&gt;
&lt;p&gt;全局变量，比如颜色，字体大小等等&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$yellow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#FAAF00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$yellow-bright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#FAF7F0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Tools"&gt;Tools&lt;/h2&gt;
&lt;p&gt;mixin，function 等等&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nt"&gt;sample-mixin&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到 Tools 为止，不会生成具体的 css&lt;/p&gt;
&lt;h2 id="Generic"&gt;Generic&lt;/h2&gt;
&lt;p&gt;reset，normalize 等等&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="err"&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;before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;*::&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&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="Base"&gt;Base&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors" rel="nofollow" target="_blank" title=""&gt;type selector&lt;/a&gt;
比如 link, p 等等，只有这一层才使用 type selector&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;line-height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&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;h2 id="Objects"&gt;Objects&lt;/h2&gt;
&lt;p&gt;Cosmetic-free，不使用比如 color、border-color、background-color 之类的&lt;br&gt;
使用这个 CSS 你在浏览器上面只可以看一片空白&lt;/p&gt;

&lt;p&gt;主要用来做画面的 layout&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.o-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&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="Components"&gt;Components&lt;/h2&gt;
&lt;p&gt;UI 组件&lt;/p&gt;

&lt;p&gt;到这个部分，可以和搞设计的商量下具体有哪些组件需要实现，可以分给多个人来同时实现&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nn"&gt;#&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="err"&gt;组件&lt;/span&gt;

&lt;span class="nc"&gt;.c-btn&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-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="nc"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--primary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ff5959&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="mh"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--large&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&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;16px&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nc"&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;HTML 类似这样&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"c-btn c-btn--primary"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;sample&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"c-btn c-btn--primary c-btn--large"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;sample&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="细化组件"&gt;细化组件&lt;/h3&gt;
&lt;p&gt;在实际开发中，由于是用的 responsive web design&lt;br&gt;
所以加了一层来放每个页面专用的部分&lt;br&gt;
因为每个 component 在不同的页面需要不同的 media-query 来进行具体调整&lt;/p&gt;

&lt;p&gt;文件结构&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── components
│&amp;nbsp;&amp;nbsp; ├── _button.scss
│&amp;nbsp;&amp;nbsp; ├── _grid.scss
├── pages
│&amp;nbsp;&amp;nbsp; └── _top.scss
│&amp;nbsp;&amp;nbsp; └── _guide.scss
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Trumps"&gt;Trumps&lt;/h2&gt;
&lt;p&gt;放各种 helper&lt;br&gt;
最主要的作用是用在不适合或者不容易放在 Component 的时候&lt;br&gt;
比如 margin，很可能不应该放 Component，这时候可以用 Trumps 来微调&lt;br&gt;
这样可以防止你的 Component 变得非常大&lt;/p&gt;

&lt;p&gt;只有这一层才可以使用!important，以此来避免多个!important 的混乱局面&lt;br&gt;&lt;/p&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.u-color&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--white&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="nv"&gt;$white&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&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="nc"&gt;.u-hidden&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="nb"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="文件结构"&gt;文件结构&lt;/h2&gt;
&lt;p&gt;目前发现两种文件结构&lt;/p&gt;
&lt;h3 id="1. 每个layer一个文件夹"&gt;1. 每个 layer 一个文件夹&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/ahmadajmi/itcss" rel="nofollow" target="_blank"&gt;https://github.com/ahmadajmi/itcss&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="2. 名字的前缀用layer的名字"&gt;2. 名字的前缀用 layer 的名字&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/itcss/itcss-netmag/tree/master/css" rel="nofollow" target="_blank"&gt;https://github.com/itcss/itcss-netmag/tree/master/css&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前采用的是第二种，感觉 CSS 多了以后相对容易管理&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/dafed/managing-css-projects-with-itcss" rel="nofollow" target="_blank" title=""&gt;Managing CSS Projects with ITCSS // Speaker Deck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/" rel="nofollow" target="_blank" title=""&gt;ITCSS: Scalable and Maintainable CSS Architecture - Xfive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@jordankoschei/how-i-shrank-my-css-by-84kb-by-refactoring-with-itcss-2e8dafee123a#.901o4jivx" rel="nofollow" target="_blank" title=""&gt;How I Shrank my CSS by 84kb by Refactoring with ITCSS — Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ahmadajmi/awesome-itcss" rel="nofollow" target="_blank" title=""&gt;awesome-itcss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Wed, 05 Sep 2018 09:51:25 +0800</pubDate>
      <link>https://ruby-china.org/topics/37440</link>
      <guid>https://ruby-china.org/topics/37440</guid>
    </item>
    <item>
      <title>总结了下 routes 的使用</title>
      <description>&lt;p&gt;总结了一下目前开发中写 routes 的时候遇到的各种情况以及如何解决的&lt;/p&gt;
&lt;h2 id="routes简介"&gt;routes 简介&lt;/h2&gt;&lt;h3 id="什么是routes"&gt;什么是 routes&lt;/h3&gt;
&lt;p&gt;简单来说就是把 HTTP Request 匹配到对应的 action(定义在 controller 里)&lt;/p&gt;
&lt;h3 id="如何定义"&gt;如何定义&lt;/h3&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;resource&lt;/span&gt; &lt;span class="ss"&gt;:books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&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;h3 id="如何查看已经定义好的routes"&gt;如何查看已经定义好的 routes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;启动了 rails server 以后，通过 URL 查看

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://localhost:3000/rails/info/routes" rel="nofollow" target="_blank"&gt;http://localhost:3000/rails/info/routes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用命令行查看&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake routes
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="根据subdomain来分配路由"&gt;根据 subdomain 来分配路由&lt;/h2&gt;
&lt;p&gt;比如网站管理画面希望用 &lt;a href="http://admin.your-site/" rel="nofollow" target="_blank"&gt;http://admin.your-site/&lt;/a&gt; 来访问&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="ss"&gt;subdomain: &lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;module: &lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:books&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;这样只有 subdomain 是 admin 的时候才会匹配 admin 下面对应的路由&lt;/p&gt;
&lt;h2 id="根据domain来分配路由"&gt;根据 domain 来分配路由&lt;/h2&gt;&lt;h3 id="1. 定义domain的匹配"&gt;1. 定义 domain 的匹配&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#lib/constraints/domain_constraint.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Constraints::DomainConstraint&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;flatten&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;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@domain.include&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. 在config/routes.rb里面定义对应的routes"&gt;2. 在 config/routes.rb 里面定义对应的 routes&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb:&lt;/span&gt;

&lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="no"&gt;DomainConstraint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app1'&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:app1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="no"&gt;DomainConstraint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app2'&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:app2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="redirect"&gt;redirect&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/to_url'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;注意 routes 里面定义的 redirect 的 http status 是 301，而 controller 里面的 direct 是 302&lt;/li&gt;
&lt;li&gt;因为对 SEO 有一定影响，如果是永久性的转移建议定义在 routes 或者 nginx 之类的 web 服务器上面&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="用routes定义好的名字"&gt;用 routes 定义好的名字&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'book'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;books_path&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'book'&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;"books"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s2"&gt;"index"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 bechmark 来测试的话，或发现第一种写法要快上一倍&lt;br&gt;
尽量使用定义好的 url_path，也可以用 as 来自己定义名字&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/books'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'books#index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: &lt;/span&gt;&lt;span class="s1"&gt;'books'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="对url的参数进行限制"&gt;对 url 的参数进行限制&lt;/h2&gt;
&lt;p&gt;比如 id 只想要整数，可以这样进行限制&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'books/:id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraints: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="sr"&gt;/[1-9][0-9]*/&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 ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'books/:type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraints: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;Regexp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;union&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'type1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'type2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'type3'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="限制用户的权限"&gt;限制用户的权限&lt;/h2&gt;
&lt;p&gt;使用 devise 的话，可以像下面这样限制 admin 权限的用户才可以访问 sidekiq 管理画面&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;authenticate&lt;/span&gt; &lt;span class="ss"&gt;:operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&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;mount&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/sidekiq'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="将routes分割为多个文件"&gt;将 routes 分割为多个文件&lt;/h2&gt;
&lt;p&gt;当 routes.rb 太大的时候，我们可能会想要分离为多个文件来方便管理&lt;/p&gt;
&lt;h3 id="1. config/routes里面进行如下定义"&gt;1. config/routes里面进行如下定义&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb:&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routes_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;instance_eval&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;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="s2"&gt;"config/routes/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;routes_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.rb"&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;draw&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

  &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="ss"&gt;subdomain: &lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s1"&gt;'*path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'application#render_404'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;via: :all&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. 在config/routes/ 文件夹下面添加routes定义文件"&gt;2. 在 config/routes/ 文件夹下面添加 routes 定义文件&lt;/h3&gt;&lt;h4 id="文件结构"&gt;文件结构&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config/routes/
├── admin.rb
└── user.rb
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="对应的文件"&gt;对应的文件&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes/user.rb&lt;/span&gt;
&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:books&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes/admin.rb&lt;/span&gt;

&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:authors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>heroyct</author>
      <pubDate>Thu, 30 Aug 2018 11:40:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/37414</link>
      <guid>https://ruby-china.org/topics/37414</guid>
    </item>
    <item>
      <title>使用 docker-sync 让 mac 和 docker 之间的文件同步变快</title>
      <description>&lt;p&gt;最近，在 mac 上面安装了 docker，然后让开发环境运行在 docker 上面&lt;/p&gt;

&lt;p&gt;总的来说感觉不错&lt;br&gt;
但是同一个页面，在 linux 上面的时候需要 4 秒，在 mac for docker 上面需要 25 秒&lt;/p&gt;

&lt;p&gt;调查了一下原因&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076/158" rel="nofollow" target="_blank"&gt;https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076/158&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;大概是说 docker 采用的 osxfs 导致文件的读取比较慢&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;osxfs 的介绍，有兴趣的可以研究下&lt;br&gt;
&lt;a href="https://docs.docker.com/docker-for-mac/osxfs/#performance-issues-solutions-and-roadmap" rel="nofollow" target="_blank"&gt;https://docs.docker.com/docker-for-mac/osxfs/#performance-issues-solutions-and-roadmap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;搜索了一下解决办法&lt;/p&gt;
&lt;h2 id="1. vagrant -&gt; virtualbox -&gt; linux -&gt; docker"&gt;1. vagrant -&amp;gt; virtualbox -&amp;gt; linux -&amp;gt; docker&lt;/h2&gt;
&lt;p&gt;试了下用 vagrant 安装 linux，然后 docker 跑在 linux 上面，用 nfs 来同步&lt;br&gt;
速度确实变快了，但是多安装了一层总感觉有点麻烦&lt;br&gt;
所以调查了一下其他的解决办法&lt;/p&gt;
&lt;h2 id="2. docker-sync"&gt;2. docker-sync&lt;/h2&gt;
&lt;p&gt;在 docker-for-mac 的网站和 github 的 issue 里面有介绍&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/docker/for-mac/issues/77" rel="nofollow" target="_blank"&gt;https://github.com/docker/for-mac/issues/77&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076" rel="nofollow" target="_blank"&gt;https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;试了下感觉不错，总结下如何使用的&lt;/p&gt;
&lt;h3 id="1. 在mac上面安装docker-sync"&gt;1. 在 mac 上面安装 docker-sync&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gem install docker-sync
$ brew install fswatch
$ brew install unison
$ brew install eugenmayer/dockersync/unox
$ docker-sync start
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. 添加docker-sync.yml文件"&gt;2. 添加 docker-sync.yml 文件&lt;/h3&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2'&lt;/span&gt;

&lt;span class="na"&gt;syncs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sync-folder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;
    &lt;span class="na"&gt;sync_strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unison'&lt;/span&gt;
    &lt;span class="c1"&gt;# 同步时的用户ID，不指定的话为root&lt;/span&gt;
    &lt;span class="na"&gt;sync_userid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
    &lt;span class="na"&gt;sync_excludes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vendor/bundle/'&lt;/span&gt;&lt;span class="nv"&gt;， 'node_modules'， 'tmp'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体设置可以看这里&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/EugenMayer/docker-sync/wiki/2.-Configuration" rel="nofollow" target="_blank"&gt;https://github.com/EugenMayer/docker-sync/wiki/2.-Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3. 添加docker-compose-dev.yml文件"&gt;3. 添加 docker-compose-dev.yml 文件&lt;/h3&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sync-folder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sync-folder:/www/sample_project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把 mac 的当前文件夹和 docker 服务器里面的/www/sample_project 进行同步&lt;/p&gt;
&lt;h3 id="4. docker-compose up"&gt;4. docker-compose up&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker-compose -f docker-compose.yml -f docker-compose-dev.yml up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后无论在 mac 还是在 docker 里面进行文件修改，都会自动进行同步&lt;br&gt;
有时候不知道什么原因无法同步的时候可以重启 docker-sync&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker-sync clean
$ docker-sync start
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;mac for docker 的文件同步很慢，目前发现两种解决方法&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;vagrant -&amp;gt; virtualbox -&amp;gt; linux -&amp;gt; docker&lt;/li&gt;
&lt;li&gt;docker-sync&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;个人比较喜欢第二种，有更好的解决办法请回复我&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 06 Aug 2018 18:55:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/37289</link>
      <guid>https://ruby-china.org/topics/37289</guid>
    </item>
    <item>
      <title>在 Rails 中使用 HTTP 缓存</title>
      <description>&lt;p&gt;为了改善页面的响应速度，在 rails 里面使用了 HTTP 缓存，做下总结&lt;/p&gt;
&lt;h2 id="什么是HTTP 缓存"&gt;什么是 HTTP 缓存&lt;/h2&gt;
&lt;p&gt;这里讲的很清楚了，就不多说了&lt;br&gt;
&lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ" rel="nofollow" target="_blank"&gt;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="rails的HTTP 缓存"&gt;rails 的 HTTP 缓存&lt;/h2&gt;&lt;h2 id="默认缓存是开启的"&gt;默认缓存是开启的&lt;/h2&gt;
&lt;p&gt;随便打开一个页面，在 chrome 的 Network 里面查看 request 和 response，可以看到下面的内容&lt;/p&gt;

&lt;p&gt;response header&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Cache-Control: max-age&lt;span class="o"&gt;=&lt;/span&gt;0, private, must-revalidate
ETag: W/&lt;span class="s2"&gt;"25200a77a0ae51ac9511df4ac1e9a0d5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;request header&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;If-None-Match: W/&lt;span class="s2"&gt;"64a30229a112c1819d754ef8a7ad8876"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大概意思是，进行 (私有) 浏览器缓存，每次都会验证请求的状态&lt;br&gt;
比较他们的 etag 的值，如果是一样的就返回 304&lt;/p&gt;
&lt;h2 id="手动指定Cache的时间"&gt;手动指定 Cache 的时间&lt;/h2&gt;
&lt;p&gt;可以看到默认的 max-age 是 0，我们可以手动指定这个时间&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expires_in 1.hour
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 chrome 里面确认，这个时候要注意的是你如果直接更新页面的话无法确认结果&lt;/p&gt;

&lt;p&gt;新打开一个 tab, 在 URL 栏里面点击 enter 进入才可以确认&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/7888713/41016930-5cb219b0-698d-11e8-97e4-278df3696674.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;这种比较适合静态页面或者是一段时间不更新也没有任何问题的页面&lt;/p&gt;
&lt;h3 id="如何强制更新"&gt;如何强制更新&lt;/h3&gt;
&lt;p&gt;比如上面设置了 1 个小时的更新，但是你想立刻更新页面的时候&lt;br&gt;
可以在 URL 后面加上时期之类的来更新，比如？20180810120000&lt;/p&gt;
&lt;h2 id="etag是如何生成的"&gt;etag 是如何生成的&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ETag'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出是根据每次返回的 response body 来生成的，如果你每次返回的 body 都一样，ETAG 也会一样&lt;/p&gt;
&lt;h3 id="API"&gt;API&lt;/h3&gt;
&lt;p&gt;一个 API 返回 json 的时候，如果每次返回的值都一样，就会返回 304&lt;/p&gt;
&lt;h3 id="HTML页面"&gt;HTML 页面&lt;/h3&gt;
&lt;p&gt;默认启用了 csrf, csrf-token 的值每次都会变化，所以基本都是 200&lt;/p&gt;
&lt;h2 id="自定义ETAG"&gt;自定义 ETAG&lt;/h2&gt;
&lt;p&gt;每次都从 response body 来生成 ETAG，明显不是一个高效的方法&lt;br&gt;
因为还是必须去 render html，生成 response body，&lt;br&gt;
虽然可以减少传送的内容，但是服务器端的 response time 没有太大改善&lt;br&gt;&lt;/p&gt;

&lt;p&gt;实际应用中，我们很可能需要的是某个或者多个数据未改变，不用去 render html，直接返回 304&lt;br&gt;
这个时候就可以使用 fresh_when&lt;/p&gt;
&lt;h3 id="fresh_when"&gt;fresh_when&lt;/h3&gt;
&lt;p&gt;比如有个 book mode，我们希望 book 没有更新对话就返回 304，可以在 controller 里面这样写&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="ss"&gt;last_modified: &lt;/span&gt;&lt;span class="vi"&gt;@book.updated_at.utc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;etag: &lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt;
  &lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="vi"&gt;@book&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的话 book 没有更新的话，就会返回 304&lt;/p&gt;
&lt;h3 id="多个变量生成ETAG"&gt;多个变量生成 ETAG&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&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="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;OR&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TopController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;etag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&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="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;fresh_when&lt;/span&gt; &lt;span class="vi"&gt;@book&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;book 没有更新并且是相同用户的话，才会返回 304&lt;/p&gt;
&lt;h3 id="从model如何生成etag"&gt;从 model 如何生成 etag&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="no"&gt;Etag&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@book.cache_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;model 的 cache_key 类似这样&lt;code&gt;books/13-20180517145513000000000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;model name&amp;gt;/&amp;lt;id&amp;gt;-&amp;lt;updated_at&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="从自定义的类如何生成etag"&gt;从自定义的类如何生成 etag&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;etag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@etag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ETAG&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%("&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&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;retrieve_cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;key&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;:cache_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_key&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;key&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;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;key&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;element&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;retrieve_cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_param&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;key&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;:to_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;retrieve_cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&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;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_param&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;to_s&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出最简单的实现自定义类的 etag 生成的方法就是实现 cache_key 方法&lt;br&gt;
比如你有一个类来管理看过的书，看过的书有增加了就生成新的 etag&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;BookHistory&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_key&lt;/span&gt;
    &lt;span class="c1"&gt;# return read book ids&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;有时候我们需要禁止缓存，可以这样&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;expires_now&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;禁止缓存的情况感觉不多，感觉一般没必要用 &lt;br&gt;
我在 React(flux + immutable js) 的项目中，API 如果返回 304 的话 &lt;br&gt;
在 safari 里面有时候会取不到返回值，暂时采取了 safari 的话就禁止缓存&lt;br&gt;&lt;/p&gt;
&lt;h2 id="使用fresh_when需要注意的地方"&gt;使用 fresh_when 需要注意的地方&lt;/h2&gt;
&lt;p&gt;影响页面变动的变量比较少的时候，我会采用 fresh_when&lt;br&gt;
但是影响页面变动的变量多的话，用 fresh_when 会有一些问题&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;维护困难，必须搞清楚这个页面的所有变量，把他们都传到 fresh_when 里面去

&lt;ul&gt;
&lt;li&gt;比如你可能在页面里面你使用了 book.author.name 来显示相关书的作者&lt;/li&gt;
&lt;li&gt;book 的 author 变化了你需要更新页面，但你可能忘了把 author 也传给 fresh_when&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;上线后不容易发现问题，因为你看到没问题，某个用户可能看到的画面就有可能有问题&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不知道大家使用时有没有什么好的方法来解决这些问题&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;使用 HTTP 缓存可以很好的提高服务器的响应速度&lt;/li&gt;
&lt;li&gt;如果是在 RestAPI 中，因为返回的内容基本是某个 model 的内容，比较推荐使用&lt;/li&gt;
&lt;li&gt;在一般的 html 页面中，影响页面的变量少的时候可以使用 fresh_when，变量多的时候慎重使用&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="参考文章"&gt;参考文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://mohanraj-nagasamy.github.io/blog/2014/02/22/browser-cache-how-etags-works-in-rails-3-and-rails-4/" rel="nofollow" target="_blank" title=""&gt;Browser Cache: How ETags Works in Rails 3 and Rails 4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/caching_with_rails.html#conditional-get-support" rel="nofollow" target="_blank" title=""&gt;Rails Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://takehiromouri.com/Rails-Caching-How-ETags-and-fresh_when-work.html" rel="nofollow" target="_blank" title=""&gt;Caching in Rails - How ETags and fresh_when work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/mirakui/high-performance-rails-long-edition" rel="nofollow" target="_blank" title=""&gt;High Performance Rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>heroyct</author>
      <pubDate>Sun, 03 Jun 2018 21:46:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/36889</link>
      <guid>https://ruby-china.org/topics/36889</guid>
    </item>
    <item>
      <title>如何使用 Sidekiq 进行异步处理</title>
      <description>&lt;h2 id="什么是sidekiq"&gt;什么是 sidekiq&lt;/h2&gt;
&lt;p&gt;提供了异步处理功能的&lt;a href="https://github.com/mperham/sidekiq" rel="nofollow" target="_blank" title=""&gt;gem&lt;/a&gt;&lt;br&gt;
这里简单了归纳了一下如何在 rails 的项目里面使用 sidekiq&lt;/p&gt;
&lt;h2 id="准备工作"&gt;准备工作&lt;/h2&gt;
&lt;p&gt;异步处理的数据放在 redis 里面，所以需要安装 redis&lt;br&gt;
如果用的是 mac 的话，可以用&lt;code&gt;brew install redis&lt;/code&gt;安装&lt;/p&gt;
&lt;h2 id="添加Gem"&gt;添加 Gem&lt;/h2&gt;
&lt;p&gt;在 Gemfile 里面添加，然后&lt;code&gt;bundle install --path vender/bundle&lt;/code&gt;进行安装&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'sidekiq'
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="设置"&gt;设置&lt;/h2&gt;
&lt;p&gt;创建一个文件 config/initializers/sidekiq.rb&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;'sidekiq/web'&lt;/span&gt;

&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_server&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;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Settings&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;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Settings&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;sidekiq_db&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="ss"&gt;namespace: &lt;/span&gt;&lt;span class="no"&gt;Settings&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;sidekiq_namespace&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;server_middleware&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;chain&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Middleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RetryJobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max_retries: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_client&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;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Settings&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;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Settings&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;sidekiq_db&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="ss"&gt;namespace: &lt;/span&gt;&lt;span class="no"&gt;Settings&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;sidekiq_namespace&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;开发环境和实际的服务器环境的 redis 的设置不同，所以放在 settings 里面&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;redis:
  url: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= ENV['REDIS_URL'] || 'redis://127.0.0.1:6379' %&amp;gt;
  db: '0'
  sidekiq_db: '1'
  sidekiq_namespace: sidekiq&amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="发送邮件"&gt;发送邮件&lt;/h2&gt;
&lt;p&gt;在 application.rb 里面添加以下代码&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;active_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_adapter&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后发送邮件的时候使用 deliver_later 就可以异步发送&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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="nf"&gt;deliver_later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Worker"&gt;Worker&lt;/h2&gt;
&lt;p&gt;进行异步处理的 worker 类都放在 app/workers 文件夹下面&lt;/p&gt;

&lt;p&gt;比如要对用户数据进行排名
app/workers/use_ranking_worker.rb&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;UserRankingWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;queue: :event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;retry: &lt;/span&gt;&lt;span class="mi"&gt;3&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;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&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;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
    &lt;span class="c1"&gt;## do something&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="把人物加入Queue(队列)"&gt;把人物加入 Queue(队列)&lt;/h2&gt;
&lt;p&gt;使用 perform_async 方法来加入队列&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;UserService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ranking&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="no"&gt;UserRankingWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&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;id&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;也可以使用 perform_in 在指定时间后执行&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;UserService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ranking&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="no"&gt;UserRankingWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_in&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;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@event.id&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="sidekiq起動"&gt;sidekiq 起動&lt;/h2&gt;&lt;h3 id="设置"&gt;设置&lt;/h3&gt;
&lt;p&gt;config/sidekiq.yml&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# thread数&lt;/span&gt;
&lt;span class="ss"&gt;:concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;
&lt;span class="ss"&gt;:pidfile&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="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pids&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;
&lt;span class="ss"&gt;:logfile&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="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;
&lt;span class="ss"&gt;:daemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="c1"&gt;# 对需要立刻处理的任务指定优先级&lt;/span&gt;
&lt;span class="ss"&gt;:queues&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;mailers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="启动sidekiq"&gt;启动 sidekiq&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec sidekiq -C config/sidekiq.yml -d
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="发布"&gt;发布&lt;/h2&gt;
&lt;p&gt;需要注意的是，新的代码更新后，sidekiq 必须重新启动才可以&lt;br&gt;
现在的项目用的是 capistrano 来进行发布，添加&lt;a href="https://github.com/seuros/capistrano-sidekiq" rel="nofollow" target="_blank" title=""&gt;capistrano-sidekiq&lt;/a&gt;这个 GEM 后，在 config/deploy.rb 添加一下代码后，可以在每次发布后重新启动
config/deploy.rb&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:worker&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq_config&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;current_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/sidekiq.yml"&lt;/span&gt;
&lt;span class="no"&gt;SSHKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiq&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'bundle exec sidekiq'&lt;/span&gt;
&lt;span class="no"&gt;SSHKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiqctl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bundle exec sidekiqctl"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了不影响 web 服务器，所以新建了一个 worker 服务器，专门用来运行 sidekiq，可以根据实际情况进行调整&lt;/p&gt;
&lt;h2 id="管理画面"&gt;管理画面&lt;/h2&gt;
&lt;p&gt;sidekiq 自带了一个管理画面，按照下面的方法可以很容易的添加&lt;br&gt;
可以看到正在进行的任务，失败的任务，成功的任务&lt;br&gt;
失败的任务还可以选择以后从画面上重启&lt;br&gt;&lt;/p&gt;

&lt;p&gt;添加 Gem&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'sinatra', require: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 routes.rb 追加 routing
config/routes.rb&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;'sidekiq/web'&lt;/span&gt;

&lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&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;authenticate&lt;/span&gt; &lt;span class="ss"&gt;:operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;developer?&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;mount&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/sidekiq'&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;因为这个画面不能让任何人都看到，所以设置成在管理画面里面并且是开发人员才可以看到
然后/sidekiq 就可以看到了&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;在提高服务器的应答速度的时候，可以进行异步处理的部分都可以用 sidekiq 来处理，这样可以有效提高服务器应答速度&lt;br&gt;
发送邮件之类的，由于网络原因或者邮件服务器原因一时无法发送的时候可以重新执行任务，非常简单实用&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 14 May 2018 19:03:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/36771</link>
      <guid>https://ruby-china.org/topics/36771</guid>
    </item>
    <item>
      <title>如何用 Rails 开发一个页面</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;分享下开发一个页面的基本流程，有不足等地方请指正&lt;br&gt;&lt;/p&gt;
&lt;h2 id="开发环境，代码"&gt;开发环境，代码&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;开发环境：mac&lt;/li&gt;
&lt;li&gt;浏览器：chrome&lt;/li&gt;
&lt;li&gt;为了方便分享，以电商网站为列进行说明

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/heroyct/yang-shop" rel="nofollow" target="_blank"&gt;https://github.com/heroyct/yang-shop&lt;/a&gt; (开发中)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="rails开发一个页面的基本流程"&gt;rails 开发一个页面的基本流程&lt;/h2&gt;
&lt;p&gt;rails 框架也是一个 mvc 框架，下面是从浏览器接到一个请求后，服务器端最基本的处理流程&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/02263b6e-8425-461d-b755-90d0e0e2c436.png!large" width="815" height="450"&gt;&lt;/p&gt;

&lt;p&gt;根据这个流程对每个处理进行解说，有不足的地方请补充&lt;/p&gt;
&lt;h2 id="Router"&gt;Router&lt;/h2&gt;
&lt;p&gt;实际开发中路由是很重要的部分，rails 的路由自由度很高，想指定成什么样都没有太大问题&lt;br&gt;
但是如果设计风格不统一的话，你的路由就会非常的混乱，不容易维护&lt;br&gt;
下面来讨论下比较常用的一些路由的写法&lt;br&gt;&lt;/p&gt;
&lt;h3 id="routes是干什么的"&gt;routes 是干什么的&lt;/h3&gt;
&lt;p&gt;简单来说当一个 URL 发送到服务器的时候，这时候需要分配到某个 controller 来处理这个请求&lt;br&gt;
具体如何分配就是通过 routes 来实现的&lt;/p&gt;
&lt;h3 id="路由在哪儿定义"&gt;路由在哪儿定义&lt;/h3&gt;
&lt;p&gt;config/routes.rb文件&lt;/p&gt;
&lt;h3 id="如何查看路由"&gt;如何查看路由&lt;/h3&gt;
&lt;p&gt;在浏览器中输入以下的 PATH&lt;br&gt;
&lt;a href="http://localhost:3000/routes/info" rel="nofollow" target="_blank"&gt;http://localhost:3000/routes/info&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="资源路由"&gt;资源路由&lt;/h3&gt;
&lt;p&gt;这是 rails 默认的风格，也是最近流行的 REST 风格&lt;/p&gt;
&lt;h4 id="REST"&gt;REST&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%B7%E8%B1%A1%E7%8A%B6%E6%80%81%E4%BC%A0%E8%BE%93" rel="nofollow" target="_blank" title=""&gt;wiki 上面的 REST 定义&lt;/a&gt;&lt;br&gt;
最近越来越流行的设计风格，很多网站都开始采用这种设计手段&lt;/p&gt;

&lt;p&gt;对于数据的一个表的最基本操作是 CRUD(增删改查)，REST 也一样，提供对某个实体的 CRUD 功能&lt;/p&gt;

&lt;p&gt;比如我们有个网店的网站，对于商品的实体，我们可以按照下面的方法提供它的路由&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources :products
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在浏览器中看下生成的 routes，我们可以看到根据 REST 的风格自动生成了增删改查的路径
&lt;img src="https://l.ruby-china.com/photo/2018/df980fe3-aa49-4104-b158-b77e142aa794.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h4 id="控制器命名空间"&gt;控制器命名空间&lt;/h4&gt;
&lt;p&gt;对于商品来说，一般用户只需要给他们提供单个商品的显示和商品的一览的路由&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scope module: 'users' do
  resources :products, only: [:index, :show]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候的 routes 是这样的
&lt;img src="https://l.ruby-china.com/photo/2018/e91a6b35-bd65-455b-ba95-87aa56485208.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;需要注意的是需要在 app/controllers/下面建立 users 文件夹，然后把对应的 controllers 文件放在 users 文件下面&lt;/p&gt;
&lt;h4 id="根据subdomain来分配路由"&gt;根据 subdomain 来分配路由&lt;/h4&gt;
&lt;p&gt;一般用户我们用/yang-shops.com/来访问&lt;br&gt;
管理员我们用/admins.yang-shops.com/来访问&lt;br&gt;
这时候可以这样写&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constraints subdomain: '' do
  scope module: 'users' do
    resources :products, only: [:index, :show]
  end
end

constraints subdomain: 'admins' do
  scope module: 'admins' do
    resources :products
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="未完"&gt;未完&lt;/h4&gt;&lt;h2 id="参考文献"&gt;参考文献&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ruby-china.github.io/rails-guides/routing.html" rel="nofollow" target="_blank"&gt;https://ruby-china.github.io/rails-guides/routing.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>heroyct</author>
      <pubDate>Mon, 08 Jan 2018 17:02:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/34850</link>
      <guid>https://ruby-china.org/topics/34850</guid>
    </item>
  </channel>
</rss>
