<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>yue (zhouj)</title>
    <link>https://ruby-china.org/yue</link>
    <description>maker of clean code and verbose doc</description>
    <language>en-us</language>
    <item>
      <title>[深圳] 2018-06-30 Ruby 技术交流活动 [活动报名]</title>
      <description>&lt;h4 id="活动时间"&gt;活动时间&lt;/h4&gt;
&lt;p&gt;2018-06-30 (周六) 下午 14:30 - 16:30&lt;/p&gt;
&lt;h4 id="地点"&gt;地点&lt;/h4&gt;
&lt;p&gt;广东省深圳市南山区高新南四道 18 号创维半导体设计大厦西座 14 层 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.amap.com/place/B0FFG27DCF" rel="nofollow" target="_blank"&gt;https://www.amap.com/place/B0FFG27DCF&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="主题"&gt;主题&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stimulus.js - 后端友好的前端框架&lt;/strong&gt; by Rei.Huang&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ActiveStorage 技术原理剖析&lt;/strong&gt; by 亚飞 - 先从用户需求分析 AtiveStorage 使用方法，通过源代码静态分析和动态分析研究分析它的设计和优点，最后总结文件上传的解决方案&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;互联网部门安全培训实践之白帽成长记&lt;/strong&gt; by 陈于康，介绍互联网部门做安全培训的思路，安全培训系统的设计和实现，以及常见的 Ruby 安全漏洞案例分析&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="讲师征集"&gt;讲师征集&lt;/h4&gt;
&lt;p&gt;截止时间：2018-06-22【周五】&lt;/p&gt;

&lt;p&gt;联系：周建平 (zjp472059)&lt;/p&gt;

&lt;p&gt;欢迎 Ruby/Rails 和相关工程实践主题，包括但不限安全、前后端结合、dockers &amp;amp; k8s 等等，
都可以给大家分享，促进学习，扩大你的影响力~&lt;img title=":beers:" alt="🍻" src="https://twemoji.ruby-china.com/2/svg/1f37b.svg" class="twemoji"&gt; &lt;/p&gt;
&lt;h4 id="报名链接"&gt;报名链接&lt;/h4&gt;
&lt;p&gt;报名请戳：&lt;a href="https://wx.duohui.co/event/4778" rel="nofollow" target="_blank"&gt;https://wx.duohui.co/event/4778&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="活动微信群"&gt;活动微信群&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/c555cc61-b6d2-4734-a544-2a2c0c4f1b5b.jpeg!large" width="300px" alt=""&gt;&lt;/p&gt;
&lt;h4 id="往期活动"&gt;往期活动&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/654c8dd5-791f-4d5b-8ccb-54b8310de1e7.jpg!large" width="500px" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/fb10db17-f757-4dd0-aa3b-73c38cc4cfe2.jpeg!large" width="500px" alt=""&gt;&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Wed, 06 Jun 2018 22:23:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/36913</link>
      <guid>https://ruby-china.org/topics/36913</guid>
    </item>
    <item>
      <title>AWS 使用小记－ Beanstalk + Rails + Passenger + PostgreSQL</title>
      <description>&lt;p&gt;如题。图片有点多，就不费力迁移到 ruby-china 了（图片要重新上传粘贴），请移步我的博客。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cocacolacat.github.io/2016/03/04/setup-aws-beanstalk-rails-pg.html" rel="nofollow" target="_blank" title=""&gt;AWS 使用小记－ Beanstalk + Rails + Passenger + PostgreSQL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最后粘迁徙的一些感想：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;整个迁徙过程很不顺利，一是自己不熟悉 AWS 的服务和环境，二是 AWS 入中国后一些服务受到限制，文档不齐全等，算是摸黑完成了这个过程。这个文章远不算不上完整和正确，因为文中采用的解决方案并不是最佳实践，只是一种 Walk Around。
&lt;strong&gt;还有一点，AWS 80，8080 端口在中国区是默认不可用的，需要联系客服把自己的网站设置为 Exception。这里花费了不少时间，一直以为是网络配置错误。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>yue</author>
      <pubDate>Wed, 16 Mar 2016 17:59:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/29366</link>
      <guid>https://ruby-china.org/topics/29366</guid>
    </item>
    <item>
      <title>[已解决] 有人能安装 rails v5.0.0.beta1 吗？</title>
      <description>&lt;p&gt;想要试试 rails action cable，但是 rails 5 死活装不上。不报错，bundle install 一直停在 &lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Fetching https://github.com/rails/rails.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下是 Gemfile.&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;

&lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="s1"&gt;'2.3.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;git: &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/rails/rails.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tag: &lt;/span&gt;&lt;span class="s1"&gt;'v5.0.0.beta1'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sqlite3'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sass-rails'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'uglifier'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'coffee-rails'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'jquery-rails'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'turbolinks'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'jbuilder'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 2.0'&lt;/span&gt;
&lt;span class="c1"&gt;# gem 'sdoc', '~&amp;gt; 0.4.0',          group: :doc&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'spring'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="ss"&gt;group: :development&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'puma'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;求有经验的同学指点。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Fri, 08 Jan 2016 18:05:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/28662</link>
      <guid>https://ruby-china.org/topics/28662</guid>
    </item>
    <item>
      <title>微信用户访问统计及渠道推广力统计</title>
      <description>&lt;h2 id="背景和需求"&gt;背景和需求&lt;/h2&gt;
&lt;p&gt;加入访问统计模块，完成用户行为的分析，是面向 C 端用户必不可少的功能。现在市面上也有不少现成的访问监控统计工具，如百度统计和 Google 分析，都提供了很不错的功能。不过短板在于，百度统计定制化程度低，展示数据不够直接，不能直接满足市场运维人员的查看分析需要。谷歌问题是被墙，访问不方便。因此有必要内部开发这样的模块。分析下来，原理和实现都不困难，获得的效果也很显著。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;需求如下：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;针对不同的功能，分别统计该功能下的推广页（首页）PV，UV，新用户 UV，以及其他跳转页面的访问情况&lt;/li&gt;
&lt;li&gt;统计不同功能的用户转发，分享，取消转发情况&lt;/li&gt;
&lt;li&gt;便于给新功能添加访问统计模块&lt;/li&gt;
&lt;li&gt;提供可视化界面来实时查看各功能下的用户访问情况&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="设计和技术选型"&gt;设计和技术选型&lt;/h2&gt;
&lt;p&gt;按照上面提出的需求，可以转化为下面的用例 (User Story)：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;记录用户的每次访问，需要知道用户访问是什么功能，访问时间，上游链接，访问参数&lt;/li&gt;
&lt;li&gt;记录用户对自有公众号的关注情况（仅针对微信用户）&lt;/li&gt;
&lt;li&gt;实现统计的算法和统计数据的展示&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于功能都限制在微信浏览器下使用，因此借助 &lt;a href="http://cocacolacat.github.io/2015/04/26/wechat-base_auth.html" rel="nofollow" target="_blank" title=""&gt;微信授权机制&lt;/a&gt; 来创建系统用户。在这里需要定义用户模型：visitor，这个模型目前仅保存了用户的微信 openid，后期有需要可以添加更多属性。&lt;/p&gt;

&lt;p&gt;那么如何保存用户的访问记录呢？可以使用 &lt;a href="https://github.com/charlotte-ruby/impressionist" rel="nofollow" target="_blank" title=""&gt;impressionist&lt;/a&gt;。它把访问数据存到一个叫做 impression 的模型中，属性包括了：controller_name，action_name（这两个属性可以用来确定用户访问的是哪个功能），request_hash，session_hash，ip_address，message，referrer，params（访问的参数）。满足了目前我们保存数据的需求。不过它也有短板，在后面会提到。
&lt;img src="https://l.ruby-china.com/photo/2015/08e42bdd2afc428bd87ec793ee71a6da.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;界面的展示借助了 &lt;a href="https://github.com/activeadmin/activeadmin" rel="nofollow" target="_blank" title=""&gt;active admin&lt;/a&gt; ，定制化程度高，上手快，view DSL 够用。下图是最后出来的效果。统计了整体的访问情况和最近一周的数据。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/538b6c9b5338b1b75b9dea7310e4d5f5.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="实现 controller 层逻辑"&gt;实现 controller 层逻辑&lt;/h2&gt;
&lt;p&gt;考虑到这是一个通用功能，是属于 before_action 的行为，所以把逻辑提出到一个 controller concern 中。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 在控制器中调用&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureController&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Concerns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ImpressionConcern&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:checkin_wechat_visitor&lt;/span&gt; &lt;span class="c1"&gt;# 识别当前微信用户&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:trace_wechat_visitor&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="c1"&gt;# 仅记录 index 的访问&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是在 concern 中的逻辑，主要是实现微信用户识别和记录访问请求。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controllers/concern/impression_concern.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checkin_wechat_visitor&lt;/span&gt; &lt;span class="c1"&gt;# 需要通过微信用户的 openid 来识别用户&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:openid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:subscribe&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;wechat_authorize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authentication_check_subscription_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;checkin&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;trace_wechat_visitor&lt;/span&gt; &lt;span class="c1"&gt;# 记录用户访问情况&lt;/span&gt;
  &lt;span class="n"&gt;impressionist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;impression_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&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;visitor&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checkin&lt;/span&gt;
    &lt;span class="vi"&gt;@visitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_create_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;openid: &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:openid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@subscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subscribe&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;h2 id="实现 active admin 逻辑"&gt;实现 active admin 逻辑&lt;/h2&gt;
&lt;p&gt;首先使用 active admin &lt;a href="https://github.com/activeadmin/activeadmin/blob/master/docs/12-arbre-components.md" rel="nofollow" target="_blank" title=""&gt;Arbre&lt;/a&gt; 中的 panel 和 table_for 来绘制统计表格。在这里 FeatureStatistician 是实现统计算法的模块。通过调用 calculate 方法返回过去 7 的各个指标下的访问情况。再传递到 table_for 中实现遍历输出。当有其他的功能模块需要统计的时候，就是添加一个新的 panel 和对应的 Statistician 就行。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models/admin/dashboard.rb&lt;/span&gt;
&lt;span class="n"&gt;panel&lt;/span&gt; &lt;span class="s2"&gt;"速查统计"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;statistician&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Utilities&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FeatureStatistician&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;stat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistician&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;
  &lt;span class="n"&gt;table_for&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="s2"&gt;"日期"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
      &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:captain&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;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:captain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:captain&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%Y-%-m-%d"&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;column&lt;/span&gt; &lt;span class="s2"&gt;"PV"&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;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page_view&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;column&lt;/span&gt; &lt;span class="s2"&gt;"UV"&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;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:wechat_visitor&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;column&lt;/span&gt; &lt;span class="s2"&gt;"新用户(未关注)"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:unsubscribe_visitor&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;column&lt;/span&gt; &lt;span class="s2"&gt;"点击收藏"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:click_collect_count&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;column&lt;/span&gt; &lt;span class="s2"&gt;"查看收藏夹"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:click_collection_count&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;column&lt;/span&gt; &lt;span class="s2"&gt;"转发朋友"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:friend_share_count&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;column&lt;/span&gt; &lt;span class="s2"&gt;"转发朋友圈"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:circle_share_count&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;column&lt;/span&gt; &lt;span class="s2"&gt;"取消转发"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:share_cancel_count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="实现 Statistician 逻辑"&gt;实现 Statistician 逻辑&lt;/h2&gt;
&lt;p&gt;Statistician 中的逻辑很直接，就是提取做数据库的聚合。比如说计算过去 7 天的 PV：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/utilities/admin/feature_statistician.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pv_in_last_week&lt;/span&gt;
  &lt;span class="no"&gt;Impression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;controller_name: &lt;/span&gt;&lt;span class="s1"&gt;'feature'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action_name: &lt;/span&gt;&lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at::date &amp;gt;= ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"to_char(created_at, 'YYYY-MM-dd')"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"to_char(created_at, 'YYYY-MM-dd') as date, count(id) as count"&lt;/span&gt;&lt;span class="p"&gt;)&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;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="设计渠道推广力统计功能（省略实现）"&gt;设计渠道推广力统计功能（省略实现）&lt;/h2&gt;
&lt;p&gt;功能会有不同的渠道合作伙伴进来推广，针对不同的推广方式，需要查看此方式带来的用户访问量。建模是定义了 channel 和 advertisement 两个表格。上面的 UML 图就修改为下图。后期传播出去的 url 都会带上推广方式标示，这样就能识别进入系统是通过哪个渠道。
&lt;img src="https://l.ruby-china.com/photo/2015/79e3e477baa05e2524148b2f6537fee1.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="impression 的问题"&gt;impression 的问题&lt;/h2&gt;
&lt;p&gt;Impression 增加了 params 字段，可以保存请求携带的参数。在统计过程中往往需要匹配参数。可是 gem 把 params 定义为 text 类型，然后仅是存了序列化的 ActiveSupport::HashWithIndifferentAccess，这样就不能借助 sql 自带的查找方法来做聚合，只能通过 ruby 逻辑。&lt;/p&gt;

&lt;p&gt;PostgreSQL 提供了 hstore 机制，可以在 hash 中做查找。若 impressionist 能针对这个做改进会更好。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Sun, 22 Nov 2015 15:10:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/28147</link>
      <guid>https://ruby-china.org/topics/28147</guid>
    </item>
    <item>
      <title>[已解决] 求助，Sidekiq Default Queue 任务莫名消失</title>
      <description>&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt;
假设创建 7 个同样任务，都是 30 秒后执行，但是最终不是全部执行，可能只执行 5 个，不一定。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;查看 web UI，可以看到 7 个任务都成功进入列队。&lt;/li&gt;
&lt;li&gt;查看 log，却只有部分的任务被执行了。其他莫名其妙的就消失了。&lt;/li&gt;
&lt;li&gt;Google, 因为 sidekiq/testing 导致了冲突 &lt;a href="http://stackoverflow.com/questions/12816226/sidekiq-worker-not-getting-triggered" rel="nofollow" target="_blank" title=""&gt;文档链接&lt;/a&gt;。 (但是这个原因被排除)&lt;/li&gt;
&lt;li&gt;本地开发环境下任务全部完成，但是 staging/production 会有遗失现象。&lt;/li&gt;
&lt;li&gt;本地 redis 版本是 2.8.17，服务器版本是 2.8.4 (查看 redis release log 没看到问题，同时任务插入没有问题，可以排除版本问题)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;debug 很久都没有结果，请问大家有没有碰到过。&lt;/p&gt;

&lt;p&gt;以下是其他信息：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sidekiq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3.4.2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sidekiq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sidekiq/web'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sidekiq-failures'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sidetiq'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'capistrano-sidekiq'&lt;/span&gt;
&lt;span class="k"&gt;end&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;'rspec-sidekiq'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#sidekiq.yml 配置文件&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;5&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;:queues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;
&lt;span class="ss"&gt;development:
  :verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;true&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;5&lt;/span&gt;
&lt;span class="ss"&gt;staging:
  :concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="ss"&gt;production:
  :concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/sidekiq.rb&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:secret&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"SOMETHING SECRET"&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@middleware.reverse&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# Last added, First Run&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="s1"&gt;'redis://localhost:6379/12'&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;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="s1"&gt;'redis://localhost:6379/12'&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;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;|&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="n"&gt;password&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="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#调用代码&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_qr_codes&lt;/span&gt;
  &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="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="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, invoke create_qr_codes"&lt;/span&gt;
  &lt;span class="no"&gt;QRCodeWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QRCodeWorker&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;:retry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="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="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# do hard work&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;
问题出在了服务器上同时跑了多个 sidekiq 的 instances，但是没有设置 namespace。修改如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/sidekiq.rb&lt;/span&gt;
&lt;span class="c1"&gt;# 旧的配置，没有 namespace&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="s1"&gt;'redis://localhost:6379/12'&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;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="s1"&gt;'redis://localhost:6379/12'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 新的配置，有 namespace&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="s1"&gt;'redis://localhost:6379/12'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;namespace: &lt;/span&gt;&lt;span class="s1"&gt;'your namespace'&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;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="s1"&gt;'redis://localhost:6379/12'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;namespace: &lt;/span&gt;&lt;span class="s1"&gt;'your 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;这样就可以了。吃一堑长一智。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Thu, 29 Oct 2015 18:15:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/27890</link>
      <guid>https://ruby-china.org/topics/27890</guid>
    </item>
    <item>
      <title>又一个 MessageVerifier 的使用场景</title>
      <description>&lt;h2 id="问题描述"&gt;问题描述&lt;/h2&gt;
&lt;p&gt;假设微信应用有转发产品介绍和引导注册的页面，如何知道哪些已有用户的转发带来最多的新注册用户呢？&lt;/p&gt;
&lt;h2 id="Short Answer"&gt;Short Answer&lt;/h2&gt;
&lt;p&gt;解决的办法是在每个分享链接里面携带推荐人的用户信息。一旦新用户通过链接完成注册，则通过链接携带的推荐人信息和新用户建立推荐关系。这里分享链接的用户信息可以使用 &lt;a href="http://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html" rel="nofollow" target="_blank" title=""&gt;MessageVerifier&lt;/a&gt; 转码签名用户类型 (user_type) 和 用户 ID(user_id)，然后作为参数加入链接。&lt;/p&gt;
&lt;h2 id="Modelling"&gt;Modelling&lt;/h2&gt;
&lt;p&gt;目标是保存新用户和推荐人的关联关系。可以使用 &lt;a href="http://railscasts.com/episodes/163-self-referential-association" rel="nofollow" target="_blank" title=""&gt;self referential model&lt;/a&gt;。用户模型（User) 作为新用户 (referred) 有且仅有一个推荐人 (referee)。而老用户可以是多位新用户的推荐人。&lt;/p&gt;
&lt;h2 id="More Modelling"&gt;More Modelling&lt;/h2&gt;
&lt;p&gt;如果推荐人是多种用户类型，如学生 (Student) 互相之间，老师 (Teacher) 对学生可以推荐，那么这样的推荐关系可以通过多态 &lt;a href="http://guides.rubyonrails.org/association_basics.html" rel="nofollow" target="_blank" title=""&gt;polymorphic&lt;/a&gt; 的 self referential 模型来实现。如下是模型的 UML 图。
&lt;img src="https://l.ruby-china.com/photo/2015/777dc9245bf7e4c89993db476db4b782.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="分享链接携带一个含有推荐人信息的 share_token"&gt;分享链接携带一个含有推荐人信息的 share_token&lt;/h2&gt;
&lt;p&gt;那么该如何传递推荐人的信息呢？方法一是为每一位用户创建一个全局唯一的 share_token 字段，然后在分享链接里面携带这个 token，每个点击携带 share_token 的分享链接的潜在用户，在创建新账号的时候就可以通过 token 找到推荐人。
第二种方法是使用 MessageVerifier 来签发用户身份信息作为 share_token。在定位推荐人的时候，只需要验证令牌和读取里面的推荐人信息。显然第二种更巧妙，减少了额外的数据库字段。&lt;/p&gt;
&lt;h2 id="实现 models"&gt;实现 models&lt;/h2&gt;
&lt;p&gt;首先是学生，老师和推荐的模型。学生和老师都有多个推荐记录（referrals），而一个新学生只能有一个推荐人（referee）。这里的中间表格 Referral 是为了支持多态而引入的。如果仅仅是学生之间才存在推荐关系，那么不需要这个表。&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;Student&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:referral&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s1"&gt;'referred_id'&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:referrals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s2"&gt;"referee_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :referee&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Teacher&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:referrals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s2"&gt;"referee_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :referee&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Referral&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:referee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:referred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Student'&lt;/span&gt;

  &lt;span class="n"&gt;validates_presence_of&lt;/span&gt; &lt;span class="ss"&gt;:referee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:referred&lt;/span&gt;
  &lt;span class="n"&gt;validates_uniqueness_of&lt;/span&gt; &lt;span class="ss"&gt;:referred_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="实现 share_token generator"&gt;实现 share_token generator&lt;/h2&gt;
&lt;p&gt;下面的代码定义了一个&lt;a href="http://ruby-doc.org/stdlib-1.9.3/libdoc/singleton/rdoc/Singleton.html" rel="nofollow" target="_blank" title=""&gt;单例模式&lt;/a&gt;的 TokenGenerator。通过调用 generate_share_token 方法，它可以为某一个用户生成一个 share_token。这个 share_token 含有 "#{user_type}_#{user_id}" 信息，并且使用秘钥签字。&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;Utilities::TokenGenerator&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:verifier&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secret_key_base&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"any_key"&lt;/span&gt;
    &lt;span class="vi"&gt;@verifier&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;MessageVerifier&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="vi"&gt;@secret_key&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;generate_share_token&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="n"&gt;generate&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&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="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时它可以验证和提取给定 share_token 的用户信息。如验证不通过（token 被修改导致无效），则返回 false。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# token_generator.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encode_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encode_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;decode&lt;/span&gt;
  &lt;span class="k"&gt;rescue&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;MessageVerifier&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidSignature&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="组合了推荐人的微信分享链接"&gt;组合了推荐人的微信分享链接&lt;/h2&gt;
&lt;p&gt;最终的分享链接如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://yoursite.com?share_token=QkFoSklnNXpkSFZrWlc1MFh6RUdPZ1pGUmc9PS" rel="nofollow" target="_blank"&gt;https://yoursite.com?share_token=QkFoSklnNXpkSFZrWlc1MFh6RUdPZ1pGUmc9PS&lt;/a&gt;
0tMDBjMjEzNzMwZmQ5MDA4N2ZkZDZkN2NiYzVkMWIwNDEzNDAyZmNlMw==&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 在 ApplicationController 中定义 share_url 需要携带 share_token&lt;/span&gt;
&lt;span class="c1"&gt;# 生成携带推荐人信息的分享链接&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;share_url&lt;/span&gt;
  &lt;span class="n"&gt;share_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Utilities&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TokenGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_share_token&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="n"&gt;share_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;share_token: &lt;/span&gt;&lt;span class="n"&gt;share_token&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;h2 id="创建新用户并建立推荐关系"&gt;创建新用户并建立推荐关系&lt;/h2&gt;
&lt;p&gt;在到达创建这一步之前，用户还可能去其他页面，所以 share_token 会先被保存在 session 里面（此处不展示保存代码）。在创建新用户成功后，调用 set_referral 来建立推荐关系。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 在创建了新用户之后建立推荐关系 #set_referral&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_referral&lt;/span&gt;
  &lt;span class="c1"&gt;# 获取 share_token 并验证&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;share_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:share_token&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;referee_st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;share_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 从验证通过的 share_token 中获取用户信息，并还原推荐人&lt;/span&gt;
    &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;referee_st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;referee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_constantize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 建立推荐关系&lt;/span&gt;
    &lt;span class="no"&gt;Referral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;referee: &lt;/span&gt;&lt;span class="n"&gt;referee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;referred: &lt;/span&gt;&lt;span class="vi"&gt;@student&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 删除 session 中的 share_token&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:share_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;遇到需要生成 token 的情景，如激活链接，忘记密码连接等等，都可以使用 MessageVerifier 来解决，代码很简洁也容易理解。&lt;/li&gt;
&lt;li&gt;单例模式在这个场景下使用比较适合，推荐这份介绍单例的&lt;a href="http://dalibornasevic.com/posts/9-ruby-singleton-pattern" rel="nofollow" target="_blank" title=""&gt;文章&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>yue</author>
      <pubDate>Sun, 13 Sep 2015 19:28:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/27310</link>
      <guid>https://ruby-china.org/topics/27310</guid>
    </item>
    <item>
      <title>重新思考找回忘记密码解决方法</title>
      <description>&lt;p&gt;TL, DR:
本文简述如何用 JWT 来实现不需要数据库 columns 的找回忘记密码解决方法&lt;/p&gt;

&lt;p&gt;前记：
虽然 Devise 提供了成熟的登陆认证，找回忘记密码的支持，但在纯 REST API 开发的情况下不适用。使用 cookie 和 session 不利于服务的拓展。&lt;/p&gt;

&lt;p&gt;所以在最近的项目中，我们采用了  token-based authorization，运用了 JWT 这样一个小但是优雅的标准。
简单来说 JWT (JSON Web Token) 定义了高可靠的数字签名解决标准。它可以携带自定义用户信息，经过 base64 编码，hamc SHA256 加密生成 token, 然后通过 http authorization 请求头传递作为登陆凭证。&lt;/p&gt;

&lt;p&gt;具体实现如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'jwt'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Gemfile&lt;/span&gt;

&lt;span class="c1"&gt;# 返回登陆认证令牌&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::V1::AuthTokenController&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Concerns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AuthTokenConcern&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@account&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@account.is_activated&lt;/span&gt;
       &lt;span class="c1"&gt;# 验证成功，生成并返回登陆令牌&lt;/span&gt;
       &lt;span class="vi"&gt;@jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="n"&gt;respond_with&lt;/span&gt; &lt;span class="vi"&gt;@jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :created&lt;/span&gt;
     &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@account&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_activated&lt;/span&gt;
       &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;处理账户没激活&lt;/span&gt;
     &lt;span class="k"&gt;else&lt;/span&gt;
       &lt;span class="c1"&gt;# 处理验证失败&lt;/span&gt;
     &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 生成认证令牌&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Concerns::AuthTokenConcern&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;
  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;携带用户的邮箱和令牌过期时间作为&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_salt&lt;/span&gt; &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;签发令牌的密钥&lt;/span&gt;
      &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;expire_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;set_auth_token_expired_time&lt;/span&gt;
      &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"exp"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expire_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;telephone: &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;telephone&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# 设置令牌7天过期&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_auth_token_expired_time&lt;/span&gt;
      &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&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;# 验证认证令牌&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_auth_token&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_auth_token&lt;/span&gt;
    &lt;span class="n"&gt;handle_signin_excaption&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;handle_signin_excaption&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;get_current_account!&lt;/span&gt;
      &lt;span class="c1"&gt;# 处理令牌为空&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ExpiredSignature&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="c1"&gt;# 处理令牌过期&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DecodeError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="c1"&gt;# 处理令牌非法&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;get_current_account!&lt;/span&gt;
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;从请求头获取令牌&lt;/span&gt;
    &lt;span class="n"&gt;auth_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HTTP_AUTHORIZATION"&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;:split&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;读取令牌携带用户信息&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;此处不作令牌的验证&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;不会抛出异常&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;verify_expiration: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;获取验证令牌的密钥&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_salt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; 
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;用秘钥验证令牌&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;会抛出&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ExpiredSignature&lt;/span&gt; &lt;span class="n"&gt;或&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DecodeError&lt;/span&gt; &lt;span class="n"&gt;异常&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;验证成功&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;设置当前用户&lt;/span&gt;
    &lt;span class="vi"&gt;@current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样做的好处有三方面：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不需要再存储 auth_token，因为 token 只会存在客户端，服务器端只需要验证传来的 token 是否合法有效。&lt;/li&gt;
&lt;li&gt;支持服务拓展。服务器不需要存储 session 信息，和客户状态松耦合。&lt;/li&gt;
&lt;li&gt;实现简单。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;正文：
那么 JWT  是否可以解决找回忘记密码的问题呢？
参考 Devise 的实现，它是这样做的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;运用 password controller 来处理找回功能。create action 是发出找回忘记密码指示 邮件；update action 是重置密码。&lt;/li&gt;
&lt;li&gt;在 account 表下 (或任何存储用户信息的表格)，存储 reset_password_token 和 reset_password_at 两个字段。reset_password_token  是一个全局唯一的随机的字符串。reset_password_token 会被包含到重置密码到链接里面，然后和新密码一起作为 update action 的参数传到后台，后台再验证这个 token 是否合法和这个请求是否过期。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样看来，我们只需要一个唯一的 reset_token，同时需要设置重置链接的过期时间。以下是我们的解决方法，不需要任何数据库 columns。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;＃&lt;/span&gt; &lt;span class="n"&gt;重置密码的&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::V1::PasswordsController&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;skip_before_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_auth_token&lt;/span&gt;

  &lt;span class="no"&gt;PARAMS_ACCESSOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:new_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reset_password_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="no"&gt;PARAMS_ACCESSOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
      &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;param&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="c1"&gt;# create action 发送重置忘记密码邮件&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_account&lt;/span&gt;
   &lt;span class="c1"&gt;# 运用列队处理邮件发送&lt;/span&gt;
    &lt;span class="no"&gt;ResetPasswordMailWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="err"&gt;＃&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;验证重置密码令牌&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;重置密码&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="n"&gt;handle_email_account_reset&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_account&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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;handle_email_account_reset&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="err"&gt;＃&lt;/span&gt; &lt;span class="n"&gt;获取重置密码中的用户信息&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;不验证令牌&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;此处不会抛出异常&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reset_password_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;verify_expiration: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# 验证令牌，抛出异常如果验证失败&lt;/span&gt;
    &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reset_password_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 验证成功，重置密码&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="n"&gt;new_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# 返回成功信息&lt;/span&gt;
      &lt;span class="n"&gt;render_message&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'password.reset_password_success'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;      
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ExpiredSignature&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="c1"&gt;# 处理重置令牌过期&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DecodeError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="c1"&gt;# 处理重置令牌非法&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;# 发送重置密码邮件&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResetPasswordMailWorker&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;:retry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="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;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_reset_password_token&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;
    &lt;span class="c1"&gt;# 发送重置密码邮件，里面会携带参有合法重置密码令牌的连接&lt;/span&gt;
    &lt;span class="no"&gt;AccountMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_password_instructions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&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="nf"&gt;deliver&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;get_reset_password_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"exp"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expired_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;JWT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="err"&gt;＃&lt;/span&gt;&lt;span class="n"&gt;设置令牌两天内过期&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expired_at&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&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;secret_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_salt&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;#重置密码连接格式&lt;/span&gt;
  &lt;span class="s2"&gt;"https://www.example.com/password?reset_password_token=any_valid_reset_password_token"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过签发和验证 JWT 格式的 reset password token, 服务器端可以知道请求是否合法，这样就不需要数据库的介入。&lt;/p&gt;

&lt;p&gt;（完）&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Thu, 09 Apr 2015 23:24:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/25080</link>
      <guid>https://ruby-china.org/topics/25080</guid>
    </item>
    <item>
      <title>Ruby 自学书单</title>
      <description>&lt;p&gt;比起 C, C++, Java 等语言，Ruby 相关书籍真是少得可怜。
本文罗列那些年楼主自学 Ruby 看过的书，按照入门到中级到深入的顺序，希望对于想学习 Ruby 的人有帮助。&lt;/p&gt;

&lt;p&gt;补充：
这个帖子总结得更好：
   &lt;a href="https://ruby-china.org/topics/768" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/768&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="入门"&gt;入门&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Well-Grounded Rubyist, Second Edition, 2014, David A. Black&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/34749fba14f772d35052d9385db5ef0a.jpg" title="" alt="the well grounded rubyist"&gt;&lt;/p&gt;

&lt;p&gt;入门书籍不在于多，就是有那么一本全面的介绍语言的基本语法，核心库，再多带那么些高级特性就够了。
这本是第二版，发行于 2014 年 7 月，针对的是 Ruby 2.1 版。
它涵盖了语言的基本特性，包括了 Ruby object, class, module, control flow 等这些基础；
同时深入的介绍了 Ruby String, Symbol, Collection, Regular Expressions，还有 I/O；
最后一部分是 Ruby 动态特性的入门，介绍了 Ruby 下的 callable and runnable objects, 如 lambda, proc, Proc. 
作者是一个耐心的老师，用生动的比喻来解释 Ruby 下的概念，再用很多的例子来引导教学，你可以跟着书一起练习熟悉 Ruby。
书面这位优雅的女士，暗喻的是 Ruby 优雅的语法，而这本书也恰到好处的把她勾勒了出来。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eloquent Ruby, 2011,Russ Olsen&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;如果你是 Java 或者 C# 的开发员转到 Ruby, 难免会把编写前两种语言的习惯带到 Ruby 来。这也就使得你的代码看起来不那么 Ruby。
这本书就是针对专治以上症结的。不单是从写法上，还从思考角度上，让你更好的利用 Ruby 的特性，从 Ruby 的角度编写程序。
它不单止于介绍 Ruby 的语言特性，如 symbol, module, 还指出了使用它们的最佳实践。
同时它还是一本字典，其中解释了 DSL, Framework, Gem, Ruby Implementation 这些概念，为你通往下一个学习阶段做好准备。
如果第一本书是教你扎马步，那么这本书就是让你修炼轻功了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The RSpec Book, 2011&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Ruby 社区是很重视 测试的，无论是 TDD 还是 BDD。
RSpec 是 Ruby TDD 的一个测试框架，以其语言化的语法在社区里流行。
书从介绍 RSpec 的基本语法，特性开始，用实例来描述如何从 TDD 的角度做开发，如何做 Mock 和 Stub，如何让测试也做到 DRY ( Don't Repeat Yourself )。
这本书算是护体神功，帮助你消减 Bugs 的攻击。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Cucumber: 行为驱动测试，2013&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/f8799ade479e5f953d6abcabfbc44b18.jpg" title="" alt=""&gt;
介绍了那么多，总于有一本中文翻译的了。。。。
我没有读过翻译版本，不知道如何。但是原著是很不错的。
与其把它看成是编程书籍，不如是思想书籍：如何通过测试来编写有效的代码？&lt;/p&gt;
&lt;h2 id="中级"&gt;中级&lt;/h2&gt;
&lt;p&gt;Ruby 不难入门，接下来是大量反复的练习和实践，基于这些实践来反思。
Rails 框架很强大，能简化开发时间；但另外一面，太过于依赖框架，很容易忽视软件开发，OOP 思想的原则。
懂得使用框架，不代表能写出优质，可拓展，拥抱变化的系统。
所以在这个阶段，楼主还会推荐一些软件设计和最实践的书籍。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agile Web Development with Rails 4, 2013&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;经典书系列，随着 Rails 的版本发布不断的跟进修改。
亚马逊上的中文版本被批斗出翔了，又一本经典的烂翻译。
做编程的，英文是逃不掉的，好在 Rails 的中文社区也是很活跃，书籍慢慢的看，不单收获技术，英语能力也能提升。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Sinatra: up and running, 2011&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Sinatra 是 Ruby 底下另一个很流行的 Web Framework。她比 Rails 更轻量，灵活可配置，上手快。
这本书很薄，才 100 多页，可是信息量很大。涵盖了 HTTP Request/Response, Session 这些  Web 开发中的基本概念，
还专门用一个章节来细致介绍了 Rack 和 Rack Middleware。最后是介绍如何用 Sinatra 来开发一个 Blog 系统。
短小精悍。如果再结合查看 Sinatra 的源码，会得到很大的提升。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails AntiPatterns: Best Practice Ruby on Rails Refactoring&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;如果要找 Rails 开发的最佳实践，那这本书一定要读。
书的结构很清晰，从 Model 层，到 Controller 到 Views, 然后是 DB, Testing 和 Deployment, 一一介绍开发中会碰到的 AntiPatterns，同时
给出详细的解决方案。
对于有过一定 Rails 开发经验的人，再来阅读这本书，能从别人的经验谈中优化自己的开发，学习更好的解决方案，收益会很大。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Practical Object-Oriented Design in Ruby: An Agile Primer, 2012, Sandi Metz &lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;这本书也是有中文翻译的，据说质量翔。。。
Rails 框架的流行推动了 Ruby 的发展，太多的 Rails/Ruby Apps 被快速的开发出来，可却以牺牲
维护性和拓展性为代价。
系统紧耦合 ( Tightly Coupling), 破坏封装 ( Encapsulation Broken ),  违反 Law of Demeter，这些都会损害
系统的健康。归根结底是没有做到合理的 OOP 设计，而重度依赖了框架或者工具。
这本书让你重新认识 OOP 的编程思想，以及在 Ruby 下如何实现。
“As a self-taught programmer, this was an extremely helpful dive into some OOP concepts that I could definitely stand to become better acquainted with! And, I’m not alone: there’s a sign posted at work that reads, ‘WWSMD? – What Would Sandi Metz Do?’”
—Jonathan Mukai, Pivotal in NYC&lt;/p&gt;
&lt;h2 id="深入"&gt; 深入&lt;/h2&gt;
&lt;p&gt;楼主也是在深入学习 Ruby 的阶段。这个阶段更多是思考设计和架构。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refactoring: Ruby Edition, 2009, Jay Fields, Shane Harvie, Martin Fowler, Kent Beck&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;楼主没有读过这本书，但是冲着 Martin Folwer 和 Kent Back 的大名来的。
在此摘抄官方的介绍：
This book will help you
这本书会帮助你:
•    Understand the core principles of refactoring and the reasons for doing it
     理解重构的核心原则以及为什么要这么做
•    Recognize“bad smells”in your Ruby code
     发现你 Ruby 代码中的 "bad smells"
•    Rework bad designs into well-designed code, one step at a time
     一步一个脚印的修复恶劣设计
•    Build tests to make sure your refactorings work properly
     测试驱动重构
•    Understand the challenges of refactoring and how they can be overcome
    认识重构会面临的挑战以及如何克服
•    Compose methods to package code properly
     运用 package 的思想来重构 methods
•    Move features between objects to place responsibilities where they fit best
     优化对象设计，封装职责
•    Organize data to make it easier to work with
     组织数据，让它更好的被调用
•    Simplify conditional expressions and make more effective use of polymorphism
     利用多态来简化选择逻辑
•    Create interfaces that are easier to understand and use
     设计简单易用的接口
•    Generalize more effectively 
     更有效的简化
•    Perform larger refactorings that transform entire software systems and may take months or years
     大型系统的重构&lt;br&gt;
•    Successfully refactor Ruby on Rails code
      成功的重构 ROR 代码&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design Patterns in Ruby, Russ Olsen, 2007&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;GoF's patterns 的 Ruby 诠释。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metaprogramming Ruby 2: Program Like the Ruby Pros, 2014, Paolo Perrotta&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;学习元编程的不二选择，出到了第二版。
书中最后一章是介绍了 Rails 框架如何运用了元编程的思想，大大的满足好奇心。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby Under a Microscope, 2013, Pat Shaughnessy&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Ruby 到底是如何实现的，又如何运行你的代码，为什么说 Class 和 Module 是一样的，几乎所有东西 都是 Object?
Ruby 如何做 GC？这本书就回答了这些问题。
作者深入到 C code，但是不需要你有很强的 C 知识，借用大量的图片和实践来描绘 Ruby 的内部工作。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Sun, 18 Jan 2015 12:24:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/23795</link>
      <guid>https://ruby-china.org/topics/23795</guid>
    </item>
    <item>
      <title>大家怎么做授权？</title>
      <description>&lt;p&gt;最近的项目涉及到较为复杂的权限。虽然 cancan 用得很顺畅，但是总禁不住思考其他的解决方案。
于是查看了一些资料和看了别人写的总结，自己也弄了一份列表出来。
&lt;a href="http://jianpingz.info/uncategorized/simple_authorisation_review/" rel="nofollow" target="_blank"&gt;http://jianpingz.info/uncategorized/simple_authorisation_review/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;解决方案分为两种：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;入门，小型到中型的系统，可使用 CanCan，节省自己写通用接口的时间，不需要 role related tables，简单灵活。&lt;/li&gt;
&lt;li&gt;采用 role-based authorisation 的理论，资源的权限都是基于角色的，需要额外的 tables 来定义角色。代表有 acl9 和
declarative_authorization&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;实践并且思考更好的解决方案，是程序员的通病，所以想请求大家：
1.大家在项目里面都怎么做授权的，以及为什么这样么做？算是做个小调查吧。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;还有什么好的授权方面的资料推荐？&lt;/li&gt;
&lt;li&gt;大型的系统，比如淘宝，微信，银行这些是怎么做授权的&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>yue</author>
      <pubDate>Sun, 21 Dec 2014 22:31:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/23320</link>
      <guid>https://ruby-china.org/topics/23320</guid>
    </item>
    <item>
      <title>[广州] 艾道信息咨询 诚聘 前端开发工程师 / ROR 开发工程师 / 实习生</title>
      <description>&lt;h3 id="关于我们"&gt;关于我们&lt;/h3&gt;
&lt;p&gt;广州艾道信息咨询公司专注于咨询与培训领域，既为企业客户提供培训管理咨询、管理系统开发、数据分析等服务，同时也向个人客户提供移动互联网产品。&lt;/p&gt;
&lt;h3 id="我们是怎样的团队"&gt;我们是怎样的团队&lt;/h3&gt;
&lt;p&gt;我们都是热爱技术，相信他/她能创造优秀的产品来改变生活的一群人。我们有敏锐的心系市场的产品经理，我们有资深的系统架构开发工程师，我们还有懂得先进的前端框架，拥有完美美学追求的前端工程师。&lt;/p&gt;

&lt;p&gt;这些品质，主动 ( Initiative )，激情 ( Passionate )，专业 ( Professional )，坚持 ( Consistency )，友好 ( friendly ) 把我们凝聚在一起。&lt;/p&gt;
&lt;h3 id="工作环境"&gt;工作环境&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/1e53d27e8c4060464bf15783979b5274.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/66429f94ebf3d8d9b6def6e4212f9ade.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;不求高大上，但是贵在环境舒适，交通方便。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;公司地址：广州花园酒店写字楼 9 层，环市东路 368 号，地铁 5 号线淘金站 A 出口即到。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="我们的技术栈"&gt;我们的技术栈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ruby, Rails&lt;/li&gt;
&lt;li&gt;TDD&lt;/li&gt;
&lt;li&gt;MySql, Postgresql, NoSQL&lt;/li&gt;
&lt;li&gt;Node.js, Twisted, Ember.js, d3js, AngularJS&lt;/li&gt;
&lt;li&gt;Hypermedia API&lt;/li&gt;
&lt;li&gt;Anything meets requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="我们在寻觅"&gt;我们在寻觅&lt;/h3&gt;&lt;h4 id="Web 前端开发工程师 ( 5-8K )"&gt;Web 前端开发工程师 ( 5-8K )&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;工作职能&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;根据产品需求和设计完成兼容性良好的前端 UI 实现和设计。&lt;/li&gt;
&lt;li&gt;与后台协作，完成数据交互，动态信息展示。&lt;/li&gt;
&lt;li&gt;维护优化前端性能。&lt;/li&gt;
&lt;li&gt;研究前端技术动态。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;精通 HTML(5)/CSS(3)/Javascript&lt;/li&gt;
&lt;li&gt;理解 AJAX 原理，熟练掌握 AJAX 开发&lt;/li&gt;
&lt;li&gt;熟悉常用的前端框架，如 Bootstrap&lt;/li&gt;
&lt;li&gt;重视团队协作，愿意探寻和改进目前还不成熟的前端开发流程，了解各种常用工具&lt;/li&gt;
&lt;li&gt;熟悉移动端前端的加分&lt;/li&gt;
&lt;li&gt;熟悉数据可视化的加分&lt;/li&gt;
&lt;li&gt;了解 Ruby&amp;amp;Rails, MVC 框架的加分&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Web ( Rails &amp;amp; Ruby ) 开发工程师 ( 5-8K )"&gt;Web ( Rails &amp;amp; Ruby ) 开发工程师 ( 5-8K )&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;工作职能&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完成 Web 端相关产品的功能开发和维护&lt;/li&gt;
&lt;li&gt;参与产品功能架构设计，参与技术文档的编写和维护&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 年以上 Rails&amp;amp;Ruby 开发&lt;/li&gt;
&lt;li&gt;熟悉 HTML(5)/CSS(3)/Javascript&lt;/li&gt;
&lt;li&gt;熟悉 Web 相关标准&lt;/li&gt;
&lt;li&gt;熟悉 Web 应用开发安全规范&lt;/li&gt;
&lt;li&gt;认可并实践敏捷开发，测试驱动开发&lt;/li&gt;
&lt;li&gt;有责任心，自驱力强&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Rails &amp;amp; Ruby 实习生（ 120/天 ）"&gt;Rails &amp;amp; Ruby 实习生（120/天）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;热爱编程，有一定的编程基础。&lt;/li&gt;
&lt;li&gt;了解 Rails&amp;amp;Ruby。会用 rails 开发简单的系统。&lt;/li&gt;
&lt;li&gt;熟悉 web 标准。&lt;/li&gt;
&lt;li&gt;能保证每周四天的实习时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="以上职位加分项"&gt;以上职位加分项&lt;/h3&gt;
&lt;ul&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;li&gt;精通系统架构和设计模式&lt;/li&gt;
&lt;li&gt;精通除 Ruby 以外的另外一种语言：C/C++/C#/Java/Python/Perl/Lisp/Erlang/Golang/Scala/...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="关于福利"&gt;关于福利&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;按照能力定义薪水，上不封顶&lt;/li&gt;
&lt;li&gt;正式员工录用，安排五险一金&lt;/li&gt;
&lt;li&gt;上班不打卡，弹性工作时间，9:00-18:00&lt;/li&gt;
&lt;li&gt;扁平化管理，没有办公司政治，看能力说话&lt;/li&gt;
&lt;li&gt;定期的技术分享活动&lt;/li&gt;
&lt;li&gt;提供生日蛋糕活动，公司聚餐，无限的办公室零食&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="联系方式"&gt;联系方式&lt;/h3&gt;
&lt;p&gt;发简历到：zhouj@adachina.cn。&lt;/p&gt;

&lt;p&gt;邮件请提供尽可能多的信息（Blog/作品或链接/Github/StackOverflow/dribbble）方便我们了解你。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Mon, 15 Dec 2014 09:36:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/23196</link>
      <guid>https://ruby-china.org/topics/23196</guid>
    </item>
    <item>
      <title>[广州] 艾道信息咨询 诚聘 前端开发工程师 / ROR 开发工程师 / 实习生</title>
      <description>&lt;p&gt;“人生匆匆，来时一人，去时也一人，但若中间几十年能快乐度过，加速宇宙的熵增，验证宇宙终极答案是否 42，想必那也是极好的。”&lt;/p&gt;

&lt;p&gt;“翻译一下！”&lt;/p&gt;

&lt;p&gt;“人生要有追求、有理想，人生意义即在于奋斗，让这个世界更美好。既然选择了 IT 这门职业，那么留给世界的只能是惊艳的产品！移动互联网的大浪潮已经迅猛涌来，抓住时机，挑战自己，抓住一个改变自身命运的绝佳机会。” &lt;/p&gt;

&lt;p&gt;“说人话！”&lt;/p&gt;

&lt;p&gt;“赶紧加入我们！让我们一起改变世界！”&lt;/p&gt;
&lt;h3 id="关于我们"&gt;关于我们&lt;/h3&gt;
&lt;p&gt;广州艾道信息咨询公司专注于咨询与培训领域，既为企业客户提供培训管理咨询、管理系统开发、数据分析等服务，同时也向个人客户提供移动互联网产品。&lt;/p&gt;
&lt;h3 id="我们是怎样的团队"&gt;我们是怎样的团队&lt;/h3&gt;
&lt;p&gt;我们都是热爱技术，相信她能创造优秀的产品来改变生活的一群人。我们有敏锐的心系市场的产品经理，我们有资深的系统架构开发工程师，我们还有懂得先进的前端框架，拥有完美美学追求的前端工程师。&lt;/p&gt;

&lt;p&gt;这些品质，主动 ( Initiative )，激情 ( Passionate )，专业 ( Professional )，坚持 ( Consistency )，友好 ( friendly ) 把我们凝聚在一起。&lt;/p&gt;
&lt;h3 id="工作环境"&gt;工作环境&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/1e53d27e8c4060464bf15783979b5274.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/66429f94ebf3d8d9b6def6e4212f9ade.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;不求高大上，但是贵在环境舒适，交通方便。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;公司地址：广州花园酒店写字楼 9 层，环市东路 368 号，地铁 5 号线淘金站 A 出口即到。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="我们的技术栈"&gt;我们的技术栈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ruby, Rails&lt;/li&gt;
&lt;li&gt;Rspec, Cucumber&lt;/li&gt;
&lt;li&gt;MySql, Postgresql, Redis&lt;/li&gt;
&lt;li&gt;Node.js, Twisted, Ember.js, d3js, AngularJS&lt;/li&gt;
&lt;li&gt;Hypermedia API&lt;/li&gt;
&lt;li&gt;Anything meets requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="我们在寻觅"&gt;我们在寻觅&lt;/h3&gt;&lt;h4 id="Web 前端开发工程师 ( 5-8K )"&gt;Web 前端开发工程师 ( 5-8K )&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;工作职能&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;根据产品需求和设计完成兼容性良好的前端 UI 实现和设计。&lt;/li&gt;
&lt;li&gt;与后台协作，完成数据交互，动态信息展示。&lt;/li&gt;
&lt;li&gt;维护优化前端性能。&lt;/li&gt;
&lt;li&gt;研究前端技术动态。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;精通 HTML(5)/CSS(3)/Javascript&lt;/li&gt;
&lt;li&gt;理解 AJAX 原理，熟练掌握 AJAX 开发&lt;/li&gt;
&lt;li&gt;熟悉常用的前端框架，如 Bootstrap&lt;/li&gt;
&lt;li&gt;重视团队协作，愿意探寻和改进目前还不成熟的前端开发流程，了解各种常用工具&lt;/li&gt;
&lt;li&gt;熟悉移动端前端的加分&lt;/li&gt;
&lt;li&gt;熟悉数据可视化的加分&lt;/li&gt;
&lt;li&gt;了解 Ruby&amp;amp;Rails, MVC 框架的加分&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Web ( Rails &amp;amp; Ruby ) 开发工程师 ( 5-8K )"&gt;Web ( Rails &amp;amp; Ruby ) 开发工程师 ( 5-8K )&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;工作职能&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完成 Web 端相关产品的功能开发和维护&lt;/li&gt;
&lt;li&gt;参与产品功能架构设计，参与技术文档的编写和维护&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 年以上 Rails&amp;amp;Ruby 开发&lt;/li&gt;
&lt;li&gt;熟悉 HTML(5)/CSS(3)/Javascript&lt;/li&gt;
&lt;li&gt;熟悉 Web 相关标准&lt;/li&gt;
&lt;li&gt;熟悉 Web 应用开发安全规范&lt;/li&gt;
&lt;li&gt;认可并实践敏捷开发，测试驱动开发&lt;/li&gt;
&lt;li&gt;有责任心，自驱力强&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Rails &amp;amp; Ruby 实习生（ 120/天 ）"&gt;Rails &amp;amp; Ruby 实习生（120/天）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;热爱编程，有一定的编程基础。&lt;/li&gt;
&lt;li&gt;了解 Rails&amp;amp;Ruby。会用 rails 开发简单的系统。&lt;/li&gt;
&lt;li&gt;熟悉 web 标准。&lt;/li&gt;
&lt;li&gt;能保证每周四天的实习时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="以上职位加分项"&gt;以上职位加分项&lt;/h3&gt;
&lt;ul&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;li&gt;精通系统架构和设计模式&lt;/li&gt;
&lt;li&gt;精通除 Ruby 以外的另外一种语言：C/C++/C#/Java/Python/Perl/Lisp/Erlang/Golang/Scala/...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="关于福利"&gt;关于福利&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;按照能力定义薪水，上不封顶&lt;/li&gt;
&lt;li&gt;正式员工录用，安排五险一金&lt;/li&gt;
&lt;li&gt;上班不打卡，弹性工作时间，9:00-18:00（机智地避开上下班高峰期）&lt;/li&gt;
&lt;li&gt;扁平化管理，没有办公司政治，看能力说话&lt;/li&gt;
&lt;li&gt;定期的技术分享活动&lt;/li&gt;
&lt;li&gt;提供生日蛋糕活动，公司聚餐，无限的办公室零食&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="联系方式"&gt;联系方式&lt;/h3&gt;
&lt;p&gt;发简历到：zhouj@adachina.cn。&lt;/p&gt;

&lt;p&gt;邮件请提供尽可能多的信息（Blog/作品或链接/Github/StackOverflow/dribbble）方便我们了解你。&lt;/p&gt;</description>
      <author>yue</author>
      <pubDate>Mon, 20 Oct 2014 13:47:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/22135</link>
      <guid>https://ruby-china.org/topics/22135</guid>
    </item>
  </channel>
</rss>
