<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hiveer (李平)</title>
    <link>https://ruby-china.org/hiveer</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>为什么 Rails System Test 没有在社区流行起来？</title>
      <description>&lt;p&gt;因为自己对于什么是 end to end 测试，以及和 feature test 是什么关系我一直比较迷惑，所以最近花了一些时间了解了下跟测试相关的知识，特别是如何在 Rails 项目中写测试，总结了一篇文章：&lt;a href="https://zhuanlan.zhihu.com/p/628037293" rel="nofollow" target="_blank"&gt;https://zhuanlan.zhihu.com/p/628037293&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;从搜集到的信息中发现一个我之前的盲点就是：其实在 Rails 5.1 以及 Rspec 3.7 之后，就支持使用 System Test 了，而且根据 rspec-rails（&lt;a href="https://github.com/rspec/rspec-rails/blob/main/README.md#feature-specs" rel="nofollow" target="_blank"&gt;https://github.com/rspec/rspec-rails/blob/main/README.md#feature-specs&lt;/a&gt;）的描述：&lt;/p&gt;

&lt;p&gt;｜ Before Rails introduced system testing facilities, feature specs were the only spec type for end-to-end testing. While the RSpec team now officially recommends system specs instead, feature specs are still fully supported, look basically identical, and work on older versions of Rails.&lt;/p&gt;

&lt;p&gt;更推荐使用 System Test，而不再是 Rspec feature test&lt;/p&gt;

&lt;p&gt;但是，但是，为啥在社区开源项目中，很少看到使用 System Test 的场景呢？&lt;/p&gt;

&lt;p&gt;大家知道吗？&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Wed, 17 May 2023 14:14:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/43083</link>
      <guid>https://ruby-china.org/topics/43083</guid>
    </item>
    <item>
      <title>我的远程办公 ～</title>
      <description>&lt;p&gt;分享给那些，想要远程办公，准备远程办公，正在远程办公的朋友们！&lt;/p&gt;

&lt;p&gt;本来说是准备些素材，然后做个视频的，但是尝试之后发现我没啥办公环境可以秀，视频剪辑水平也是够糟糕，所以最后我还是决定通过我最擅长的码字的方式来分享。&lt;/p&gt;

&lt;p&gt;还记得那是 2013 年左右吧，第一次听说远程办公，是因为有一次 Terry Tai 和 吕国宁 等几位大佬想要在成都组织个 Ruby 圈的聚会，我有幸协调了部门领导提供了场地，然后认真听了各位大咖的知识分享，经历分享。第一次了解到，哇哦！原来工作还可以这样来。是的，心中那颗向往自由的种子就此埋下。&lt;/p&gt;

&lt;p&gt;后面几年有幸在两个外企就职，因为外企的管理风格和企业文化比较自由和宽松，经常可以申请在家办公。当然你知道，肯定还是需要有一个理由的，比如感冒了，水管坏了，亲戚来了……这种自由度让我有机会体会到了，工作和生活初步融合带来的好处。不过此时，我生活和工作的天平依然是像这样的：
&lt;img src="https://l.ruby-china.com/photo/hiveer/72b66381-4c9d-4614-85d2-d471c905d99e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图片来源： &lt;a href="https://www.entrepreneur.com/article/251703" rel="nofollow" target="_blank"&gt;https://www.entrepreneur.com/article/251703&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以，我其实一直在关注可以 remote 的机会，不管是国内的还是国外的。国外的 Ruby Rails 圈子中，remote 的机会真的特别的多，而且质量都很不错。不过！不过！不过！别个都是有地域限制的，大多数都是仅限北美地区，少部分说是支持真正 remote 的，你仔细一看，哦，原来是多了欧洲和日本，所以只能瞪大眼睛流哈喇子了。&lt;/p&gt;

&lt;p&gt;再说说国内的 remote 机会。你想在什么拉勾啊，51 啊，Boss 直聘上找，那 99.99% 的概率没戏。所以，我一般都在我心目中的第一社区平台 &lt;a href="https://ruby-china.org/jobs" title=""&gt;Ruby China 招聘&lt;/a&gt; 上关注 Remote 的机会。&lt;/p&gt;

&lt;p&gt;根据我接触过的机会来讲，有一个问题比较突出，那就是提供 remote 职位的公司和想要 remote 的求职者之间，对于 remote 的理念是不一样的。公司认为 remote 可以节约成本，而且常常借 remote 的名义削减福利（社保，公积金），刻意压低薪水。而求职者的出发点往往是在保证基本生活品质的前提下对自由和灵活的追求，对工作和生活平衡的追求。&lt;/p&gt;

&lt;p&gt;所以在上一个工作单位就职期间，我接触过几个国内的机会都没有达到我的预期。直到某一天，依然在我心目中排名第一的社区平台 &lt;a href="https://ruby-china.org/jobs" title=""&gt;Ruby China 招聘&lt;/a&gt; 浏览到一篇极狐 (GitLab) 的招聘帖，经过详细的沟通和仔细的了解，我确信我找到了跟我心灵契合的，算是真正用心支持 remote 的公司。&lt;/p&gt;

&lt;p&gt;再然后，你们知道的，我开始了我新的故事……&lt;/p&gt;
&lt;h3 id="也许你们会感兴趣，并且会问我： “你进了公司是不是发现理想和现实的差距挺大？”"&gt;也许你们会感兴趣，并且会问我： “你进了公司是不是发现理想和现实的差距挺大？”&lt;/h3&gt;
&lt;p&gt;我会告诉你，确实挺大！我没想到这个体量，这个知名度的公司，居然是全员远程办公，并且还把支持远程办公写进了公司的 OKR。而且，而且，而且，公司是真的在为远程办公提供各种配套支持，包括每个月的健身费，办公桌椅置办费，根据 base 地缴纳公积金和社保。不说了，再说你们以为我在给公司打广告，总之一句话，确实超出了我的预期！&lt;/p&gt;
&lt;h3 id="也许你们会感兴趣，并且会问我： “你觉得远程办公适合你吗？”"&gt;也许你们会感兴趣，并且会问我： “你觉得远程办公适合你吗？”&lt;/h3&gt;
&lt;p&gt;这个我需要重点回答。老实说这是我第一次全职的远程办公，即使我之前在外企有过远程办公的经历和实践，但那不是一个长期稳定的真正的远程办公模式。&lt;/p&gt;

&lt;p&gt;从对远程办公模式的适应上来讲，我经历过一个调整期。以前习惯了转个头就问的即时沟通方式，在远程办公的模式下出现了水土不服。你转个头看到的不是同事，可能是一面墙，一个扇窗，抑或是你的衣柜。这个时候你肯定会感觉到有点失落，因为你的期望没有被满足，你的问题没有人马上给你回答。那怎么办呢？&lt;/p&gt;

&lt;p&gt;一个懂得思考的人，在面对问题的时候一般不会花太多时间去情绪化，而是尽快去找解决方案。我的解决方案就是：主动拥抱异步沟通，适当采用同步确认。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“主动拥抱”，这为什么很重要？因为当我们对某件事不认可，有抵触的时候，是的，你别想把这件事做好。&lt;/li&gt;
&lt;li&gt;“异步沟通”，也就是你不要期待可以马上得到回复和确认，你只需要在相关的 issue/ticket/story 留下你的问题和需求并且通知到你的协作伙伴，然后去做别的事情，当你的协作伙伴回答了你的问题或者提供了你需求的内容或者材料时，他（她）会通知你，你再去处理。&lt;/li&gt;
&lt;li&gt;“适当采用同步确认”，不得不说，有些时候，有些事情，不打开语音吧啦个把小时，还真整不明白。是的，你没猜错，就是沟通需求。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hiveer/5936ae81-1b0b-4a38-ae18-e347628a2c3c.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图片来源：&lt;a href="https://jihulab.com/Rex/keep_running" rel="nofollow" target="_blank"&gt;https://jihulab.com/Rex/keep_running&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;远程办公的沟通问题解决了，那工作节奏呢？&lt;/p&gt;

&lt;p&gt;我们在公司朝九晚六工作过的同学都明白，中午到了饭点三五一群，四六一浪的就会一起去觅食，一路上唠嗑唠嗑，八卦八卦。到了下班时间，你提醒我，我提醒你，一起下班，一起挤地铁，或者一起去喝杯咖啡。在家办公，将会完全不一样了，没有人会提醒你吃饭，没有人会提醒你下班。特别是像我这种，不把问题解决，吃不下，睡不着的人来说，这是一个相当严重的问题。我经历过几次这种情况，就是有时遇到了棘手的问题，为了把这个难缠的问题解决，忘了吃饭，忘了休息，结果把自己节奏完全打乱，得不偿失。是的，正如你看到的，问题又来了？那怎么办呢？&lt;/p&gt;

&lt;p&gt;我想说，作为一个懂得思考的人，在面对问题的时候一般不会花太多时间去情绪化，而是尽快去找解决方案。我的解决方案就是：坚持做一个有原则，有界限，并且能自我控制的人。&lt;/p&gt;

&lt;p&gt;似乎有点深奥，说人话，那就是：该工作的时候认真工作，该吃饭的时候按时吃饭，该睡觉的时候按时睡觉，该运动的时候不要偷懒。我们可以做一个分析：&lt;/p&gt;

&lt;p&gt;当你用你吃饭的时间去做工作的时候，你饿着肚子，缺乏能量，脑子运转变慢，效率变低。当你浑浑噩噩折腾了一个小时之后，撑不住了，屁颠屁颠的，昏昏沉沉的去随便点个外卖，或者煮碗面条，把自己本来应该开开心心进食的午餐时间糊弄过去。记住，你糊弄生活的时候，生活就会糊弄你。你会发现因为错过最佳进食时间，身体会不在状态，心情会变 down，一天的状态都欠佳，效率自然不高。所以，最后你发现，你不仅什么都没有得到，而且可能是失去美好的一天。如果这种情况持续一个月，你可能整个人会垮掉！！！绝不是危言耸听！！！&lt;/p&gt;

&lt;p&gt;工作节奏的问题解决了，那生活的平衡呢，从何谈起？&lt;/p&gt;

&lt;p&gt;老实讲，我选择远程办公，其中最重要的原因就是考虑生活平衡，所以我必须要分享一下。我之前在成都工作，而我女儿在德阳，我对她甚是想念，你们没有女儿的理解不了，我不怪你们。
在家办公之后，我早上可以送女儿上学，下午可以接女儿放学，这种幸福感真的不要那么美好！有时候，有什么突发情况我也可以及时的处理，比如有一次老师打电话说女儿肚子痛，我马上就可以放下手上的工作去先把这个紧急的事情处理了，然后我回来再继续工作。&lt;/p&gt;

&lt;p&gt;在家办公之后，我很少在外面吃东西，很少点外卖，我一般都是自己做饭吃。就我个人而言，我随便做个菜也比外卖吃得放心，吃得舒心。&lt;/p&gt;

&lt;p&gt;我还报名参加了一个跑团，每个月跑 30 公里，所以有些时候，6 点钟下班之后，我马上就可以热身，换上装备，出去绕着德阳旌湖跑个五六公里。&lt;/p&gt;

&lt;p&gt;总之，我对现在的工作生活平衡很满意，它看起来是这样的状态：
&lt;img src="https://l.ruby-china.com/photo/hiveer/786fc1a6-65e0-4ae4-a070-7b01b5ff2e58.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图片来源：&lt;a href="https://www.efilecabinet.com/how-work-life-balance-can-be-achieved-dispelling-5-myths/" rel="nofollow" target="_blank"&gt;https://www.efilecabinet.com/how-work-life-balance-can-be-achieved-dispelling-5-myths/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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;有时候确实会觉得孤独，想要找人说话。这个时候，我会找同事语音，或者视频，然后跟他们聊天，不管是工作的内容，或者不是工作的内容，与人交流可以有效的缓解这种孤独感。毕竟孤独的时候是很少的，而且还有家人的陪伴，所以这个问题对我来说不是个大问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="也许你们会感兴趣，并且还会问我！算了，我还是不猜测你们会怎么问我了，如果真的想要问我，我相信你肯定知道我会定期查看留言的。"&gt;也许你们会感兴趣，并且还会问我！算了，我还是不猜测你们会怎么问我了，如果真的想要问我，我相信你肯定知道我会定期查看留言的。&lt;/h3&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 31 Mar 2022 17:02:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/42268</link>
      <guid>https://ruby-china.org/topics/42268</guid>
    </item>
    <item>
      <title>ActionText 让人头大！(===== 已解决 =====)</title>
      <description>&lt;p&gt;第一次尝试 ActionText，遇到了好些让我头痛的问题。&lt;/p&gt;
&lt;h3 id="重新部署会导致attachments失效"&gt;重新部署会导致 attachments 失效&lt;/h3&gt;
&lt;p&gt;在本地环境，上传附件之后，如果不更新 (edit)，即使是重启服务器，附件也是能正常找到并且渲染的。但是对于 staging 环境，通过 docker 打包，通过 docker-compose 部署，则不同了。上传附件之后，可以看到附件能正常的展示，但是如果重新部署，附件则会失效，实际上这个附件已经找不到了。暂时不晓得这个部署的操作，哪里会触发附件的失效。&lt;/p&gt;
&lt;h3 id="direct uploads 的 URL 是 http 不是 https (===== 已解决 =====）"&gt;direct uploads 的 URL 是 http 不是 https (===== 已解决 =====）&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hiveer/c1ce9a69-0785-4734-b7f2-f8ba100b1e70.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="edit 会导致已经上传的video（attachments）失效 (===== 已解决 =====）"&gt;edit 会导致已经上传的 video（attachments）失效 (===== 已解决 =====）&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hiveer/b42d1f67-0489-4edd-9f10-9fb80cd02d9f.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/hiveer/6a70df2c-f735-4464-9c74-4b66ff2b2435.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/hiveer/932d5f91-732f-4477-908a-993a9687e4c4.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/hiveer/6ca057da-1047-4c9f-b09f-f50b4d67aeed.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="补充项目的配置"&gt;补充项目的配置&lt;/h3&gt;
&lt;p&gt;目前我只在本地和 staging 做了测试，本地和 staging 的 ActiveStorage 都是基于 Aliyun OSS 而且使用的是同一个 bucket。&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;aliyun_staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Aliyun&lt;/span&gt;
  &lt;span class="na"&gt;access_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.application.credentials.aliyun_oss[:access_key_id] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;access_key_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.application.credentials.aliyun_oss[:access_key_secret] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xxx-staging"&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= Rails.application.credentials.aliyun_oss[:endpoint] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active_storage"&lt;/span&gt;
  &lt;span class="na"&gt;public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rails 的配置：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class="c1"&gt;# Store uploaded files on the local file system (see config/storage.yml for options).&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;active_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:aliyun_staging&lt;/span&gt;

&lt;span class="c1"&gt;# config/environments/staging.rb&lt;/span&gt;
&lt;span class="c1"&gt;# Store uploaded files on the local file system (see config/storage.yml for options).&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;active_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:aliyun_staging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于上面提到的问题，我独立使用 ActiveStorage 的时候是没有的。如下面的代码所示，在同一个 model，我同时添加了富文本字段和一个 video 附件字段。
video 是能正确的上传和渲染的。但是通过 'rich_content' 上传的附件就存在上面所述的问题。&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;Blog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_rich_text&lt;/span&gt; &lt;span class="ss"&gt;:rich_content&lt;/span&gt;
  &lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:video&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>hiveer</author>
      <pubDate>Wed, 16 Feb 2022 13:04:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/42144</link>
      <guid>https://ruby-china.org/topics/42144</guid>
    </item>
    <item>
      <title>GitLab 的国内版本上线了？各位对此有何看法</title>
      <description>&lt;p&gt;2022 年 02 月 09 日，也就是这个话题发布的今天，极狐 GitLab SaaS 版本正式上线了。&lt;/p&gt;

&lt;p&gt;官网：&lt;a href="https://gitlab.cn" rel="nofollow" target="_blank"&gt;https://gitlab.cn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;意味这个大家以后会多一个优秀的选择，既能用上同 gitlab.com 一样优秀的特性，还不用担心网络的限制。&lt;/p&gt;

&lt;p&gt;就我而言，我觉得是个不错的选择，不晓得大家对此又何看法。&lt;/p&gt;

&lt;p&gt;请留言激励讨论！&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Wed, 09 Feb 2022 10:14:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/42119</link>
      <guid>https://ruby-china.org/topics/42119</guid>
    </item>
    <item>
      <title>Postgres Full Text Search with Docker Compose </title>
      <description>&lt;p&gt;最近需要做一个搜索的功能，数据量不大，本来想着尽量简单，就用 Ruby code 去做了一个模糊搜索的功能。但是折腾完了，发现不行，这怎么支持分页功能呢？哈哈，偷鸡不成蚀把米！所以还是乖乖的回到了 Postgres Full Text Search 来。&lt;/p&gt;

&lt;p&gt;参考了 &lt;a href="/hooopo" class="user-mention" title="@hooopo"&gt;&lt;i&gt;@&lt;/i&gt;hooopo&lt;/a&gt; 大神的帖子：
&lt;a href="https://ruby-china.org/topics/38153" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/38153&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以及 zhparser 的文档：
&lt;a href="https://github.com/amutu/zhparser/" rel="nofollow" target="_blank"&gt;https://github.com/amutu/zhparser/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后使用 gem 'pg_search' 在本地（Mac）环境下先做了一个实验（成功了）：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;scws
scws &lt;span class="nt"&gt;-v&lt;/span&gt;
git clone https://github.com/amutu/zhparser.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zhparser/
make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install
&lt;/span&gt;psql &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;database&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARSER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;MAPPING&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'人生苦短，我用 Python'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                      &lt;span class="n"&gt;to_tsvector&lt;/span&gt;
&lt;span class="c1"&gt;--------------------------------------------------------&lt;/span&gt;
 &lt;span class="s1"&gt;'python'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="s1"&gt;'人生'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="s1"&gt;'我'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="s1"&gt;'用'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="s1"&gt;'短'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="s1"&gt;'苦'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="s1"&gt;'，'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;PgSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
&lt;span class="n"&gt;pg_search_scope&lt;/span&gt; &lt;span class="ss"&gt;:full_text_search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;against: &lt;/span&gt;&lt;span class="sx"&gt;%i[company_name company_description address contact_person]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;using: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;tsearch: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;dictionary: &lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&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;没有遇到什么大的问题，接下来就是把这套逻辑如何迁移到通过 docker，docker-compose 部署的服务器上了。
在服务器上，postgres 是通过 docker-compose service 的形式启动的，使用的是官方的默认 'postgres' image. 所以为了安装这个 zhparser 的扩展，我必须要改造下这个默认的 image，以它为基础，再包装一层，把 zhparser 的扩展装上去。所以有了下面的 Dockerfile：&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; postgres:14&lt;/span&gt;
&lt;span class="k"&gt;SHELL&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "-c"]&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;      bzip2 &lt;span class="se"&gt;\
&lt;/span&gt;      gcc &lt;span class="se"&gt;\
&lt;/span&gt;      make &lt;span class="se"&gt;\
&lt;/span&gt;      libc-dev &lt;span class="se"&gt;\
&lt;/span&gt;      postgresql-server-dev-14 &lt;span class="se"&gt;\
&lt;/span&gt;      wget &lt;span class="se"&gt;\
&lt;/span&gt;      unzip &lt;span class="se"&gt;\
&lt;/span&gt;      ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;      openssl &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - &lt;span class="s2"&gt;"http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2"&lt;/span&gt; | &lt;span class="nb"&gt;tar &lt;/span&gt;xjf - &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; wget &lt;span class="nt"&gt;-O&lt;/span&gt; zhparser.zip &lt;span class="s2"&gt;"https://github.com/amutu/zhparser/archive/master.zip"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; unzip zhparser.zip &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;scws-1.2.3 &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./configure &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /zhparser-master &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;SCWS_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CREATE EXTENSION pg_trgm; &lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s2"&gt;CREATE EXTENSION zhparser; &lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s2"&gt;CREATE TEXT SEARCH CONFIGURATION chinese_zh (PARSER = zhparser); &lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;&lt;span class="s2"&gt;ALTER TEXT SEARCH CONFIGURATION chinese_zh ADD MAPPING FOR n,v,a,i,e,l,t WITH simple;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /docker-entrypoint-initdb.d/init-zhparser.sql &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get purge &lt;span class="nt"&gt;-y&lt;/span&gt; gcc make libc-dev postgresql-server-dev-14 &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get autoremove &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    /zhparser-master &lt;span class="se"&gt;\
&lt;/span&gt;    /zhparser.zip &lt;span class="se"&gt;\
&lt;/span&gt;    /scws-1.2.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里，我们不仅仅把 zhparer 安装了，还把配置 sql 写进了数据库启动文件中，在数据库启动的时候就会去运行，然后把数据库配置好。&lt;/p&gt;

&lt;p&gt;接下来就是 image build 和 push，这里依然是利用了 极狐 GitLab 自带的 container registry，轻松完成了 image 的打包和部署。&lt;/p&gt;

&lt;p&gt;然后在服务器上，更新 docker-compose.yml 文件：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db:
  image: registry.gitlab.cn/&amp;lt;your project repo&amp;gt;/zhparser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose -f docker-compose.yml pull 
docker-compose -f docker-compose.yml up -d db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;满怀期待的试了试，输入关键字，seach……
结果，结果，不对！什么玩意儿？&lt;/p&gt;

&lt;p&gt;通过数据库日志发现了 root cause，原来是 zhparser 的配置没有生效，也就是我们在 Dockerfile 写进 init sql 的代码没有运行，原因是我们这是一个已经运行的项目，数据库挂载了 volume：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;volumes:
  - pgdata:/var/lib/postgresql/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;postgres 会认为已经存在数据库，不再执行初始化 sql：&lt;/p&gt;

&lt;p&gt;PostgreSQL Database directory appears to contain a database; Skipping initialization&lt;/p&gt;

&lt;p&gt;这种情况，我们需要做的就是 psql 连接数据库，并且手动配置 zhparser，这个是一次性的动作，之后不再需要：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE EXTENSION pg_trgm; 
CREATE EXTENSION zhparser; 
CREATE TEXT SEARCH CONFIGURATION chinese_zh (PARSER = zhparser); 
ALTER TEXT SEARCH CONFIGURATION chinese_zh ADD MAPPING FOR n,v,a,i,e,l,t WITH simple;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以开心 search 了。&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Sun, 02 Jan 2022 20:06:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/42047</link>
      <guid>https://ruby-china.org/topics/42047</guid>
    </item>
    <item>
      <title>Rails 容器化部署 master.key 的处理</title>
      <description>&lt;ul&gt;
&lt;li&gt;Rails 的版本是： '6.1.4.1'&lt;/li&gt;
&lt;li&gt;代码仓库是：极狐 GitLab&lt;/li&gt;
&lt;li&gt;部署方式是：极狐 GitLab CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CI 阶段把最新的代码 build 成为 image，并且 push 到 极狐 GitLab container registry。在部署的时候通过 docker-compose 拉取最新的 image，并且运行即可。
在 build image 阶段，有一个步骤就是需要 precompile，但是 precompile 依赖&lt;code&gt;master.key&lt;/code&gt;，给我带来了一些困扰：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production rails assets:precompile
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; Missing encryption key to decrypt file with. Ask your team &lt;span class="k"&gt;for &lt;/span&gt;your master key and write it to /Users/hiveerli/works/jihu-partner-portal/config/master.key or put it &lt;span class="k"&gt;in &lt;/span&gt;the ENV[&lt;span class="s1"&gt;'RAILS_MASTER_KEY'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;代码仓库并没有 master.key（config/master.key 并没有提交到代码仓库）&lt;/li&gt;
&lt;li&gt;我并不能把 master.key 直接放在 Dockerfile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当遇到这个问题的时候，首先我觉得不太合理，precompile 处理的都是前端的资源，为啥需要依赖 master.key。所以我想是不是这个不是必须的，可以通过什么方式规避。但是经过一番调查，发现这个依赖是必须。&lt;/p&gt;

&lt;p&gt;最终采用的方式是生成一个假的 master.key.sample 和 credentials.yml.enc.sample 并且提交到代码仓库。在 build 的时候，首先隐藏真的 credentials.yml.enc，然后使用假的 master.key.sample 和 credentials.yml.enc.sample。具体代码如下：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN if [ "$RAILS_ENV" == "production" ]; then \&lt;/span&gt;
      &lt;span class="s"&gt;mv config/credentials.yml.enc config/credentials.yml.enc.backup; \&lt;/span&gt;
      &lt;span class="s"&gt;mv config/credentials.yml.enc.sample config/credentials.yml.enc; \&lt;/span&gt;
      &lt;span class="s"&gt;mv config/master.key.sample config/master.key; \&lt;/span&gt;
      &lt;span class="s"&gt;bundle exec rails assets:precompile; \&lt;/span&gt;
      &lt;span class="s"&gt;mv config/credentials.yml.enc.backup config/credentials.yml.enc; \&lt;/span&gt;
      &lt;span class="s"&gt;rm config/master.key; \&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过验证这个方法可以很好的工作，但是看起来不美观，不仅有代码上的冗余，还有逻辑上的误导性，但是至少确保了安全性。&lt;/p&gt;

&lt;p&gt;参考：
&lt;a href="https://github.com/rails/rails/issues/32947" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/issues/32947&lt;/a&gt;&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Sun, 26 Dec 2021 17:28:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/42029</link>
      <guid>https://ruby-china.org/topics/42029</guid>
    </item>
    <item>
      <title>微信 JS API 集成</title>
      <description>&lt;p&gt;集成微信 JSSDK（使用微信分享的 API）&lt;/p&gt;

&lt;p&gt;最近有个需求，就是在微信分享官网的时候，需要能定制标题，描述和 logo。&lt;/p&gt;

&lt;p&gt;经过调查发现，微信现在不再支持通过在页面上设置图片的形式来设置 logo（亲测，经过验证），而是只能通过绑定公众号，然后通过微信的公众平台的 API 来定制这些内容。&lt;/p&gt;

&lt;p&gt;下面就展示如何一步一步的实现这个集成：&lt;/p&gt;
&lt;h3 id="域名绑定"&gt;域名绑定&lt;/h3&gt;
&lt;p&gt;首先我们需要有个域名，然后将这个域名绑定到微信公众号。这里要分两种情况：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果您使用微信提供的&lt;strong&gt;测试公众号&lt;/strong&gt;进行测试&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;入口：&lt;a href="http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login" rel="nofollow" target="_blank"&gt;http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;直接用你自己的微信扫码登录即可。登录之后你会看到这样一个配置：
&lt;img src="https://l.ruby-china.com/photo/hiveer/9b422a3f-422a-4d61-a37c-57249e33c953.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在这里你需要把你的域名填入，需要特别强调的是，这里需要填入的是域名而非 URL。所以，请把&lt;code&gt;http://&lt;/code&gt;, &lt;code&gt;https://&lt;/code&gt;以及一般在 URL 最后的反斜杠&lt;code&gt;/&lt;/code&gt;都去掉。&lt;/p&gt;

&lt;p&gt;特别强调，在测试账号下，域名可以没有备案，所以你可以看到我给的例子中使用的是&lt;a href="https://ngrok.com/" rel="nofollow" target="_blank" title=""&gt;ngrok&lt;/a&gt;生成的测试链接。同时测试账号下，这个域名对应的服务也不需要经过微信网页授权验证。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果是正常的公众号
你需要保证您的域名是经过备案的域名，以及您的域名对应的站点经过了微信网页授权（将微信提供的文件：MP_verify_&amp;lt;*&amp;gt;.txt 上传到域名对应的服务器，并且保证能通过域名访问到这个文件）。其余的同上！&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="引入JS文件"&gt;引入 JS 文件&lt;/h3&gt;
&lt;p&gt;在需要调用 JS 接口的页面引入如下 JS 文件，（支持 https）：&lt;a href="http://res.wx.qq.com/open/js/jweixin-1.6.0.js" rel="nofollow" target="_blank"&gt;http://res.wx.qq.com/open/js/jweixin-1.6.0.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;引入这个文件之后，来自微信的 JS 对象&lt;code&gt;wx&lt;/code&gt;将会全局可用，下面会看到如何使用。&lt;/p&gt;
&lt;h3 id="权限验证"&gt;权限验证&lt;/h3&gt;
&lt;p&gt;这里需要先明白我们为什么需要权限验证，以及背后的原理是怎么回事。&lt;/p&gt;

&lt;p&gt;我们回过头看第一步所做的事情，我们把网站的域名绑定到了一个公众号。那么我们在分享这个网页的时候，如果微信发现这个网页调用了微信的 API，那么微信就需要验证，这个网页是不是跟其绑定的公众号所配置的 JS 安全与名对应的网站。如果是，微信才会响应 API 的调用，否则拒绝。&lt;/p&gt;

&lt;p&gt;下面我们来看看权限验证的接口&lt;code&gt;wx.config&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;wx&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="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 开启调试模式,调用的所有api的返回值会在客户端alert出来，若要查看传入的参数，可以在pc端打开，参数信息会通过log打出，仅在pc端时才会打印。&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 必填，公众号的唯一标识&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 必填，生成签名的时间戳&lt;/span&gt;
  &lt;span class="na"&gt;nonceStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 必填，生成签名的随机串&lt;/span&gt;
  &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="c1"&gt;// 必填，签名&lt;/span&gt;
  &lt;span class="na"&gt;jsApiList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;// 必填，需要使用的JS接口列表&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;debug: true&lt;/code&gt;，这个只能在测试的时候开启，如果上线，请务必关掉&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timestamp&lt;/code&gt; 这个就是一个数字字符串格式的时间戳，没有什么特别的要求，但是请务必保证和生成签名时候使用的是一致的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nonceStr&lt;/code&gt; 这个就是一个随机的字符串，没有什么特别的要求，但是请务必保证和生成签名的时候使用的是一致的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signature&lt;/code&gt; 这个就是签名，这里需要特别强调的是这个签名是不能在前端代码完成的，否则微信会认为其无效，具体微信怎么实现的不清楚。所以这里要分两种情况，如果你是一个正常的 web 应用，那么在后端生成签名，通过适合你的方式传递到前端，然后再调用这个接口认证。如果是静态网站，那么你只能通过一个后端的 API 来返回这个签名，然后前端通过 ajax 来调用这个 API，获取签名。我的情况就是第二种。&lt;/p&gt;
&lt;h3 id="真正的去调用微信的API"&gt;真正的去调用微信的 API&lt;/h3&gt;
&lt;p&gt;上一步我们只是在做权限验证，相当于找微信确认我们是否有资格调用 API，如果通过了验证，我们才会被允许调用 API。这里我们将看到如何调用 API。&lt;/p&gt;

&lt;p&gt;微信提供了一个认证成功之后会执行的钩子函数&lt;code&gt;wx.ready()&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;// config信息验证后会执行ready方法，所有接口调用都必须在config接口获得结果之后，config是一个客户端的异步操作，所以如果需要在页面加载时就调用相关接口，则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口，则可以直接调用，不需要放在ready函数中。&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有的对 API 的调用都必须放在这个钩子函数中，否则因为权限认证接口是异步的，API 调用可能不会成功。&lt;/p&gt;
&lt;h3 id="认证失败的处理"&gt;认证失败的处理&lt;/h3&gt;
&lt;p&gt;微信提供了一个认证失败之后会执行的钩子函数&lt;code&gt;wx.error()&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="c1"&gt;// config信息验证失败会执行error函数，如签名过期导致验证失败，具体错误信息可以打开config的debug模式查看，也可以在返回的res参数中查看，对于SPA可以在这里更新签名。&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;p&gt;参与签名的字段包括 noncestr（随机字符串）, 有效的 jsapi_ticket, timestamp（时间戳）, url（当前网页的 URL，不包含#及其后面部分） &lt;/p&gt;

&lt;p&gt;这里面有一个新字段我们没有聊过&lt;strong&gt;jsapi_ticket&lt;/strong&gt;，这个是什么呢？它类似于一个我们日常生活中，使用到的票，火车票，电影票等。微信将&lt;strong&gt;jsapi_ticket&lt;/strong&gt;视作调用微信 JS API 的票据，只有你拥有了票，你才能调用。&lt;/p&gt;

&lt;p&gt;那么怎么获取这个票呢？&lt;/p&gt;

&lt;p&gt;方法如下：&lt;code&gt;https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&amp;amp;type=jsapi&lt;/code&gt;，这里又多了一个新的参数&lt;strong&gt;access_token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;access_token&lt;/strong&gt;又是什么呢？&lt;/p&gt;

&lt;p&gt;access_token 是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用 access_token。&lt;/p&gt;

&lt;p&gt;这里我们可以这样理解，你去游乐园玩，你需要一个入场的票，这个就是&lt;strong&gt;access_token&lt;/strong&gt;，你可以通过它玩游乐场的一些项目。但是某些特别的项目你需要单独再买票，这个就是&lt;strong&gt;jsapi_ticket&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;你可以通过下面的链接获取&lt;strong&gt;access_token&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;https 请求方式：GET &lt;a href="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;amp;appid=APPID&amp;amp;secret=APPSECRET" rel="nofollow" target="_blank"&gt;https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;amp;appid=APPID&amp;amp;secret=APPSECRET&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;至此我们拿到了生成签名的所有参数，接下来我们需要把这些签名排个序，这个排序的规则是根据参数名字的 ASCII 码从小到大排序，就像这样：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&amp;amp;noncestr=Wm3WZYTPz0wzccnW&amp;amp;timestamp=1414587457&amp;amp;url=http://mp.weixin.qq.com?params=value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后对这个字符串进行 sha1 签名：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0f9de62fce790f9a083d5c99e95740ceb90c27ed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后我附上完整的代码供对照理解：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerWechatSharingApi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;后端接口返回signature&amp;gt;?current_site_url=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;wx&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="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;nonceStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noncestr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;jsApiList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onMenuShareTimeline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onMenuShareAppMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onMenuShareWeibo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onMenuShareQZone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetch WX signature failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WX config verify success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;shareData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meta[name='description']&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;imgUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;图片的地址&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onMenuShareAppMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shareData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onMenuShareTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shareData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onMenuShareWeibo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shareData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onMenuShareQZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shareData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WX config verify failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;registerWechatSharingApi&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;ul&gt;
&lt;li&gt;&lt;a href="https://mikelin.cn/513.html" rel="nofollow" target="_blank"&gt;https://mikelin.cn/513.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html" rel="nofollow" target="_blank"&gt;https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html" rel="nofollow" target="_blank"&gt;https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.csdn.net/hadues/article/details/106639473" rel="nofollow" target="_blank"&gt;https://blog.csdn.net/hadues/article/details/106639473&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/wannananana/p/11824083.html" rel="nofollow" target="_blank"&gt;https://www.cnblogs.com/wannananana/p/11824083.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/wannananana/p/11824083.html" rel="nofollow" target="_blank"&gt;https://www.cnblogs.com/wannananana/p/11824083.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://segmentfault.com/a/1190000012860070" rel="nofollow" target="_blank"&gt;https://segmentfault.com/a/1190000012860070&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>hiveer</author>
      <pubDate>Sun, 24 Oct 2021 15:42:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/41797</link>
      <guid>https://ruby-china.org/topics/41797</guid>
    </item>
    <item>
      <title>导出数据到 Excel 表格（同步/异步）形式</title>
      <description>&lt;h3 id="准备"&gt;准备&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# For dealing with exel&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'caxlsx'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'caxlsx_rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考： &lt;a href="https://github.com/caxlsx/caxlsx_rails" rel="nofollow" target="_blank"&gt;https://github.com/caxlsx/caxlsx_rails&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Axlsx-Rails 提供了一个 renderer 和一个 template handler. 它添加了一个新的 format :xlsx，并且可以解析 .xlsx.axlsx 模版。&lt;/p&gt;
&lt;h3 id="同步形式"&gt;同步形式&lt;/h3&gt;
&lt;p&gt;在控制器层，可以处理 :xlsx format 的请求：&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;ButtonController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_name&lt;/span&gt;
    &lt;span class="vi"&gt;@buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xlsx&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;然后我们需要提供一个模版，根据 convention，它的名字需要是这样： &lt;code&gt;action_name.xlsx.axlsx&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;wb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xlsx_package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;workbook&lt;/span&gt;
&lt;span class="n"&gt;wb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_worksheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Buttons"&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;sheet&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="vi"&gt;@buttons.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;button&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&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;然后我们就可以通过请求，直接下载 excel 文件&lt;/p&gt;
&lt;h3 id="异步形式"&gt;异步形式&lt;/h3&gt;&lt;h4 id="前端Ajax call"&gt;前端 Ajax call&lt;/h4&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.export-consumers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/admin/consumers.js?campaign_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;campaignId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/admin/consumers/download-consumers?file_name=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exported_file_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Export Failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/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;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;js&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@consumers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consumers&lt;/span&gt;
    &lt;span class="n"&gt;exported_file_name&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;XlsxGeneration&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;@consumers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@current_campaign_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build_tw_consumers_xlsx&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;exported_file_name: &lt;/span&gt;&lt;span class="n"&gt;exported_file_name&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;h4 id="后端提供文件下载接口"&gt;后端提供文件下载接口&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_consumers&lt;/span&gt;
  &lt;span class="n"&gt;send_file&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;"tmp"&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:file_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xlsx"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;type: :xlsx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;filename: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:file_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.xlsx"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于异步形式的下载，上面示例的方案是先生成临时文件，返回文件名，然后再异步的下载这个文件。这个方案经过验证，有效可行。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于异步的情况我还有一个想法，就是在后端不生成文件，直接通过&lt;code&gt;send_data&lt;/code&gt;异步返回文件，但是我没有成功，因为在前端的 callback( done ) 中，没法拿到&lt;code&gt;send_data&lt;/code&gt;返回的文件数据。不知道有人尝试过没？&lt;/strong&gt;&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Sun, 09 Aug 2020 15:47:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/40260</link>
      <guid>https://ruby-china.org/topics/40260</guid>
    </item>
    <item>
      <title>官方 Gitflow Workflow 不适用于所有项目</title>
      <description>&lt;p&gt;最近公司团队在讨论采用什么样的 Git 分支工作方式，在仔细读了 Atlassian 的官方推荐文章之后，有一些小心得分享下：
&lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow" rel="nofollow" target="_blank"&gt;https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="读前准备"&gt;读前准备&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里用 'git-flow' 指代：官方 'Gitflow Workflow'，参考上面链接&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="git-flow 的应用场景"&gt;git-flow 的应用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;适用于 "larger projects"&lt;/li&gt;
&lt;li&gt;适用于需要定期 release 的项目&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="核心：中的核心 feature branch"&gt;核心：中的核心 feature branch&lt;/h2&gt;
&lt;p&gt;这是任何的 Git Flow 都会包含的，基础中的基础&lt;/p&gt;

&lt;p&gt;请参见： &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow" rel="nofollow" target="_blank"&gt;https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="特色：develop 分支"&gt;特色：develop 分支&lt;/h2&gt;
&lt;p&gt;这个分支是个关键，我在思考为什么要存在这个分支？我是否可以不要这个分支？&lt;/p&gt;

&lt;p&gt;--1-- 为什么要存在？&lt;br&gt;
下面这张图足以说明：
&lt;img src="https://l.ruby-china.com/photo/2020/8e6b3477-1935-4a16-97d7-f2d4446c4249.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;其存在的意义就是让 master 分支只记录发布历史，而所有的开发分支的集成都发生在 develop 分支。
从图上看很直观，master 的发布历史确实很简洁，而且每次 release 都有一个 tag。&lt;/p&gt;

&lt;p&gt;--2-- 我是否可以不要 develop 分支&lt;br&gt;
我的观点是看情况：&lt;br&gt;
从技术层面，我们可以不要，后果就是 master 将成为集成分支，发布历史乱糟糟
而想要一个简洁的发布历史，那么你就需要 develop 分支&lt;/p&gt;

&lt;p&gt;看起来是个很容易的决定&lt;/p&gt;
&lt;h4 id="但是："&gt;但是：&lt;/h4&gt;
&lt;p&gt;注意再看看上面的应用场景，其中一个是 &lt;code&gt;适用于需要定期release的项目&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;那如果不是定期 release 的项目呢？&lt;/p&gt;

&lt;p&gt;假设现在有个项目是经常有 change request，而且一些是要即时发布，一些是要稍后发布。
------------ 这里暂且把这个项目叫做 &lt;strong&gt;奇怪的项目&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;那么这两种请求还能同时在 develop 分支工作吗？明显不行了，因为你需要分开部署上线。&lt;/p&gt;

&lt;p&gt;对于即时发布的，你需要在完成后第一时间通过 Pull Request 的形式 merge 回 master&lt;/p&gt;

&lt;p&gt;对于稍后发布的，你就需要放在另外一个地方存起来等待发布&lt;/p&gt;

&lt;p&gt;对于这样的项目，develop 分支，我认为不是最佳的实践&lt;/p&gt;
&lt;h2 id="release 分支"&gt;release 分支&lt;/h2&gt;
&lt;p&gt;release 是从 develop checkout 的，用来做发布用。它存在的意义在于可以解脱 develop 分支，承担了发布的任务，而 develop 分支可以继续开发。
这对于定期发布的项目来讲，确实，堪称完美。&lt;/p&gt;

&lt;p&gt;但是对于“奇怪的项目”，release 还能得心应手吗？其实不然，在奇怪的项目中，对于需要即时发布的需求，一天可能会有几个 release，每个 release 存在的周期及其短，这个时候对于 release 分支的维护成本会变得昂贵，而效用变得低下。&lt;/p&gt;

&lt;p&gt;那对于非即时发布的需求呢？&lt;br&gt;
这个时候个人觉得，release 分支可以用来存储非即时发布的任务，但是有一点较为关键，要做好 master 到 release 的同步，避免时间太久的 merge conflict 产生&lt;/p&gt;

&lt;p&gt;先写这么多吧，跑步去了……
随意讨论&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Fri, 06 Mar 2020 21:15:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/39570</link>
      <guid>https://ruby-china.org/topics/39570</guid>
    </item>
    <item>
      <title>怎么理解`setState`是异步这回事？</title>
      <description>&lt;p&gt;Google 了很久没有找到我要的答案：&lt;/p&gt;
&lt;h3 id="问题1"&gt;问题 1&lt;/h3&gt;
&lt;p&gt;在 React re-render 被真正执行前，state 是不是必须已经被更新了
或者说&lt;code&gt;setState&lt;/code&gt;的异步操作必须执行完了，才会真的去 call re-render&lt;/p&gt;
&lt;h3 id="问题2"&gt;问题 2&lt;/h3&gt;
&lt;p&gt;虽然 &lt;code&gt;setState&lt;/code&gt; 是异步的，但是由它导致的 component 生命周期方法的执行并不是异步的？
比如，假设我有如下的代码&lt;/p&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nf"&gt;updateOthers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="err"&gt;#&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;componentWillUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码的执行顺序是不是 #1 #3 #4 #2&lt;br&gt;
还是说 是不一样的执行顺序？&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Wed, 06 Nov 2019 17:55:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/39223</link>
      <guid>https://ruby-china.org/topics/39223</guid>
    </item>
    <item>
      <title>How to expose jQuery ($) to `window` in webpacker</title>
      <description>&lt;p&gt;项目技术栈：Rails + webpacker&lt;/p&gt;

&lt;p&gt;最近做项目遇到这样一个场景：&lt;/p&gt;

&lt;p&gt;前端用 ajax 发送一个 post 请求，&lt;code&gt;dataType: 'script'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;后端用 JS builder，render 一个局部模版去替换页面上的部分内容&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$("#step-02").html("&amp;lt;%= escape_javascript(render partial: "smile_sharing_referral/prospect_form", locals: { referrer: @referrer } ) %&amp;gt;");&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;问题来了：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;请求成功，也可以看到返回的 JS code，但是 JS code 并没有执行。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug&lt;/strong&gt;
尝试手动去执行，发现了问题：在 window 环境下的&lt;code&gt;$&lt;/code&gt;并不是我想要的&lt;code&gt;jQuery&lt;/code&gt;
然后才意识到，webpacker 打包之后的 JS 并没有暴露&lt;code&gt;jQuery&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;下面就是自己探索一番之后的结果：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 package.json 中加一个新的 dependency&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expose-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;为 webpack 创建一个定制的配置文件 &lt;code&gt;Rails.root/config/webpack/expose_jquery.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expose-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jQuery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},{&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expose-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在 webpack environment 配置文件中，加载并且 merge 这个定制配置&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rails/webpacker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exposeJquery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./expose_jquery.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&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="nx"&gt;exposeJquery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>hiveer</author>
      <pubDate>Tue, 06 Aug 2019 22:09:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/38925</link>
      <guid>https://ruby-china.org/topics/38925</guid>
    </item>
    <item>
      <title>分享 - JS - 函数中的 this</title>
      <description>&lt;p&gt;参考文献：&lt;br&gt;
&lt;a href="https://www.cnblogs.com/ifantastic/p/4654370.html" rel="nofollow" target="_blank"&gt;https://www.cnblogs.com/ifantastic/p/4654370.html&lt;/a&gt;&lt;br&gt;
&lt;a href="http://api.jquery.com/bind/" rel="nofollow" target="_blank"&gt;http://api.jquery.com/bind/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;任何代码都需要一个上下文来执行，而&lt;code&gt;this&lt;/code&gt;就是这个上下文。&lt;/p&gt;

&lt;p&gt;这里直接借用参考文献中的例子：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 假设这段代码是在浏览器的全局环境中&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pseudoWindow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm fake URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;getUrl1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;getUrl2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么，getUrl, pseudoWindow 现在所处的上下文就是 window，也就是说此时的 this === window。&lt;br&gt;
所以执行 getUrl()，将打印出当前页面的 URL。&lt;/p&gt;
&lt;h3 id="那么执行 pseudoWindow.getUrl1() 情况会是怎么样的呢？"&gt;那么执行 pseudoWindow.getUrl1() 情况会是怎么样的呢？&lt;/h3&gt;
&lt;p&gt;getUrl1 是 pseudoWindow 的一个属性，可以理解为是绑定到 pseudoWindow 上下文的一个变量。而这个变量 getUrl1 使用了和 getUrl 相同的代码片段。&lt;br&gt;
所以，这里的调用实际上是把相同的代码的片段放在了一个不同于 window 的上下文 pseudoWindow 中来执行。&lt;br&gt;
此时&lt;code&gt;this&lt;/code&gt;就是 pseudoWindow，所以打印出的结果将是 I'm fake URL。&lt;/p&gt;
&lt;h3 id="那么执行pseudoWindow.getUrl2(getUrl)的情况呢？"&gt;那么执行 pseudoWindow.getUrl2(getUrl) 的情况呢？&lt;/h3&gt;
&lt;p&gt;对于 getUrl2 情况类似于 getUrl1，关键是在匿名函数接收了一个参数，这个参数是一个 global 的函数对象。&lt;br&gt;
因为这里传入的 getUrl 没有和任何上下文关联，所以在匿名函数中执行的时候，它的上下文还是 window。&lt;br&gt;
&lt;code&gt;this.func = callback;&lt;/code&gt; 这个表达式的效果跟 getUrl1 完全类似，差别在于这里隐式的给 pseudoWindow 设置了一个属性。&lt;br&gt;
所以打印结果是：当前页面的 URL，I'm fake URL。&lt;/p&gt;

&lt;p&gt;对于 getUrl2 中 callback 参数，如果想要拿到当前匿名函数的 this 为上下文，我们可以这么做：  &lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;getUrl2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;        
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到这里我就想起了我们在使用 jQuery 的时候常看到这样的写法：  &lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slideUp&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;这看起来和 getUrl2 种的 callback 很相似，所以我就 confusing 了！！！！
如果是一样的，那么在这里 this 应该是 window 啊，但是明显不是。所以我就去 jQuery 文档找啊找……^_^&lt;br&gt;
终于被我找到了：&lt;br&gt;
在 jQuery 中，这个函数被叫做 handler，然后有这样的陈述：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Event Handlers
  The handler parameter takes a callback function, as shown above. Within the handler, the keyword this refers to the DOM element to which the handler is bound. To    make use of the element in jQuery, it can be passed to the normal $() function. For example:
  $( "#foo" ).bind( "click", function() {
    alert( $( this ).text() );
  });&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以，我的疑惑算是消除了，你的呢？&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 25 Oct 2018 16:21:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/37674</link>
      <guid>https://ruby-china.org/topics/37674</guid>
    </item>
    <item>
      <title>代码分析小工具</title>
      <description>&lt;p&gt;这周应 manager 的需求去找一个类似于 code climate 的代码分析工具。因为我们 code climate 不能用了。&lt;br&gt;
Google 之后发现了很多，但是对比分析结果，比较靠谱的当属 &lt;a href="https://github.com/flyerhzm/rails_best_practices" rel="nofollow" target="_blank"&gt;https://github.com/flyerhzm/rails_best_practices&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;但是 html 格式的 output 不太满足我们的需求，因为并没有详细的代码片段。&lt;/p&gt;

&lt;p&gt;所以，为了能得到类似于 code climate 的有代码片段的效果，我把‘rails_best_practices’的分析结果，结合项目自身的代码重新组织了一下，得到了现在的这个 gem &lt;a href="https://github.com/hiveer/railsbp_in_browser" rel="nofollow" target="_blank"&gt;https://github.com/hiveer/railsbp_in_browser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;希望对于有这方面需求的朋友有所帮助。&lt;/p&gt;

&lt;p&gt;效果图，如下：
&lt;img src="https://l.ruby-china.com/photo/2018/e40dc91a-a359-42c3-9508-d2701cf0a91c.jpg!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 26 Apr 2018 23:01:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/36074</link>
      <guid>https://ruby-china.org/topics/36074</guid>
    </item>
    <item>
      <title>Rails 官方文档，有疑惑 之 “image_url”</title>
      <description>&lt;p&gt;我对下面的这个文档产生了疑惑：  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Computes the full URL to an image asset. This will use image_path internally, so most of their behaviors will be the same. Since image_url is based on asset_url method you can set :host options. If :host options is set, it overwrites global config.action_controller.asset_host setting.&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;image_url&lt;/span&gt; &lt;span class="s2"&gt;"edit.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s2"&gt;"http://stage.example.com"&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; http://stage.example.com/edit.png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;以上的文档是基于我们要使用 asset pipleline 的。我们先看下源码的调用：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"edit.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s2"&gt;"http://stage.example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  
&lt;span class="n"&gt;url_to_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"edit.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;type: :image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s2"&gt;"http://stage.example.com"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  
&lt;span class="n"&gt;path_to_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"edit.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;type: :image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s2"&gt;"http://stage.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;protocol: :request&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;path_to_asset&lt;/code&gt;中，参考源码链接&lt;a href="https://github.com/rails/rails/blob/3387676efdd03fd6e5b9a70b215ce02cdb1d4ee1/actionview/lib/action_view/helpers/asset_url_helper.rb#L183" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/3387676efdd03fd6e5b9a70b215ce02cdb1d4ee1/actionview/lib/action_view/helpers/asset_url_helper.rb#L183&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们重点关注计算 path 的那部分：  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;?/&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:skip_pipeline&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;public_compute_asset_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&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;else&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compute_asset_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为我们并没有 skip pipeline，所以自然会去执行 else 中的代码 &lt;code&gt;compute_asset_path(source, options)&lt;/code&gt;&lt;br&gt;
而&lt;code&gt;compute_asset_path&lt;/code&gt;是被 sprockets-rails 重写了的，源码在这里 (&lt;a href="https://github.com/rails/sprockets-rails/blob/857e781998c10e4f429699da1d47ef251844991f/lib/sprockets/rails/helper.rb#L77" rel="nofollow" target="_blank"&gt;https://github.com/rails/sprockets-rails/blob/857e781998c10e4f429699da1d47ef251844991f/lib/sprockets/rails/helper.rb#L77&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;我们可以看到&lt;code&gt;compute_asset_path&lt;/code&gt;会到所有的 asset 中去找这个 source("edit.png")，如果找到了就加上 assets_prefix&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assets_prefix&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legacy_debug_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没找到就报错。&lt;/p&gt;

&lt;p&gt;所以，我们这里假设“edit.png"是存在项目中的。所以，根据代码的推算我们得到的结果应该是：&lt;br&gt;
"&lt;a href="http://stage.example.com/assets/edit-fingerprint.png" rel="nofollow" target="_blank"&gt;http://stage.example.com/assets/edit-fingerprint.png&lt;/a&gt;"&lt;br&gt;
而不是“&lt;a href="http://stage.example.com/edit.png" rel="nofollow" target="_blank"&gt;http://stage.example.com/edit.png&lt;/a&gt;”&lt;/p&gt;
&lt;h4 id="实验"&gt;实验&lt;/h4&gt;
&lt;p&gt;我在我的一个现有项目 (Rails5) 做了一个实验：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我添加了一个 image('app/assets/images/approve.jpg')&lt;/li&gt;
&lt;li&gt;在任何一个 view 当中 raise error(&amp;lt;% raise %&amp;gt;)，这是为了拿到运行时&lt;/li&gt;
&lt;li&gt;刷新 view 对应的页面，然后出现了错误调试&lt;/li&gt;
&lt;li&gt;测试结果
&lt;code&gt;ruby
&amp;gt;&amp;gt;  image_url 'approve.jpg',  host: "http://stage.example.com"
=&amp;gt; "http://stage.example.com/assets/approve-53ea399216b1c6b2a08f6da00032588a01581600317d94d0445d3514f1dbdba9.jpg"
&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以看到，结果和我对代码的推算一致，但是和文档却不同。这个是不是说明文档有问题呢？&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Mon, 02 Apr 2018 14:35:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/35384</link>
      <guid>https://ruby-china.org/topics/35384</guid>
    </item>
    <item>
      <title>记一次奇怪的部署问题</title>
      <description>&lt;p&gt;这是一个个人练手的小项目，做了一个微信吃鸡群里的吃鸡排名，有兴趣的可以见 (&lt;a href="https://github.com/IFS49F/knivesout_ranking" rel="nofollow" target="_blank"&gt;https://github.com/IFS49F/knivesout_ranking&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;项目基于 Rails + React，具体版本如下：&lt;br&gt;
ruby 2.5.0&lt;br&gt;
rails 5.1.4&lt;br&gt;
react 16.2.0&lt;br&gt;
capistrano 3.10.1&lt;br&gt;
unicorn 5.4.0  &lt;/p&gt;

&lt;p&gt;在某个风和日丽的下午，我对项目的 style 做了一些更新，主要是给 table 中的每个 tr 行加上了 opacity 的 style，从上至下，渐渐透明。&lt;br&gt;
完成编码，本地测试，都没有问题。接下来，git push origin, 再然后&lt;br&gt;
        cap production deploy&lt;br&gt;
        ....&lt;br&gt;
        ...log...&lt;br&gt;
        ...&lt;br&gt;
经历了较为漫长了等待，部署完成。然后日常的去刷新页面，然后，尼玛，什么鬼？页面样式居然没有更新。&lt;br&gt;
接下来，我上了 github 以及服务器上 double confirm 了代码已经更新。所以，问题就在于页面拿到的是老的 stylesheet。  &lt;/p&gt;

&lt;p&gt;接着，来到了部署目录的 current，以及 shared 目录，查看所有 css assets 文件，然后和之前的 release 版本相比较，发现根本就没有新的 css assets 生成。&lt;/p&gt;

&lt;p&gt;接着，在服务器上，手动执行 rake assets:precompile，然后 check current 下的 assets，发现了生成的新 css assets，然后刷新页面，依然没有动静&lt;/p&gt;

&lt;p&gt;接着，cap production unicorn:restart，依然 fucking the same&lt;/p&gt;

&lt;p&gt;接着，执行 cap production deploy:clobber_assets 清除了 current 下的所有 assets，但是无济于事，因为页面加载的是之前 release 对应的 css assets&lt;/p&gt;

&lt;p&gt;接着，执行 cap production deploy:cleanup 结果是告诉我，没什么可以做的，因为我设置了保留 5 个 release，现在就是 5 个&lt;/p&gt;

&lt;p&gt;接着，在 current 目录下执行 bundle exec rake tmp:clear 依然没有什么卵用&lt;/p&gt;

&lt;p&gt;其实，看着我最后执行的两个命令，已经能看出我比较抓狂了……😫&lt;/p&gt;

&lt;p&gt;最后不抱什么希望依次的执行了&lt;br&gt;
    rake assets:precompile&lt;br&gt;
    cap production unicorn:stop&lt;br&gt;
    cap production unicorn:start  &lt;/p&gt;

&lt;p&gt;WTF, 居然，成功了！看到了该看到的样式。&lt;/p&gt;

&lt;p&gt;再之后，我两次更新了一些代码，样式之类的，然后通过 cap production deploy 发布，都能正常工作。&lt;/p&gt;

&lt;p&gt;目前为止，我还没有搞清楚发生了什么，我还在继续探索。如果你遇到过类似情况，希望你能在评论区分享下。&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 15 Mar 2018 17:02:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/35244</link>
      <guid>https://ruby-china.org/topics/35244</guid>
    </item>
    <item>
      <title>Can't find component 在使用 react-rails 的时候</title>
      <description>&lt;p&gt;正常情况：  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;目录结构 'app/javascript/components/Users.js'&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;&amp;lt;%= react_component("Users",  users: @users) %&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;异常情况：  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;目录结构 'app/javascript/components/users/Users.js'&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;&amp;lt;%= react_component("users/Users",  users: @users) %&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;error message: Can't find component 'users/Users'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在 application.js 中代码如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log('Hello World from Webpacker')
// Support component names relative to this directory:
var componentRequireContext = require.context("components", true)
var ReactRailsUJS = require("react_ujs")
ReactRailsUJS.useContext(componentRequireContext)
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>hiveer</author>
      <pubDate>Wed, 28 Feb 2018 11:07:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/35102</link>
      <guid>https://ruby-china.org/topics/35102</guid>
    </item>
    <item>
      <title>读 RubyGems 源码遇到的问题</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/rubygems/rubygems/blob/v2.6.10/lib/rubygems/core_ext/kernel_require.rb#L39" rel="nofollow" target="_blank"&gt;https://github.com/rubygems/rubygems/blob/v2.6.10/lib/rubygems/core_ext/kernel_require.rb#L39&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;
&lt;span class="c1"&gt;#--&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.&lt;/span&gt;
&lt;span class="c1"&gt;# All rights reserved.&lt;/span&gt;
&lt;span class="c1"&gt;# See LICENSE.txt for permissions.&lt;/span&gt;
&lt;span class="c1"&gt;#++&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'monitor'&lt;/span&gt;

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

  &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Monitor&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;# :nodoc:&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gem_original_require&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c1"&gt;# Ruby ships with a custom_require, override its require&lt;/span&gt;
    &lt;span class="n"&gt;remove_method&lt;/span&gt; &lt;span class="ss"&gt;:require&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;##&lt;/span&gt;
    &lt;span class="c1"&gt;# The Kernel#require from before RubyGems was loaded.&lt;/span&gt;

    &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;gem_original_require&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="ss"&gt;:gem_original_require&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;##&lt;/span&gt;
  &lt;span class="c1"&gt;# When RubyGems is required, Kernel#require is replaced with our own which&lt;/span&gt;
  &lt;span class="c1"&gt;# is capable of loading gems on demand.&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# When you call &amp;lt;tt&amp;gt;require 'x'&amp;lt;/tt&amp;gt;, this is what happens:&lt;/span&gt;
  &lt;span class="c1"&gt;# * If the file can be loaded from the existing Ruby loadpath, it&lt;/span&gt;
  &lt;span class="c1"&gt;#   is.&lt;/span&gt;
  &lt;span class="c1"&gt;# * Otherwise, installed gems are searched for a file that matches.&lt;/span&gt;
  &lt;span class="c1"&gt;#   If it's found in gem 'y', that gem is activated (added to the&lt;/span&gt;
  &lt;span class="c1"&gt;#   loadpath).&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# The normal &amp;lt;tt&amp;gt;require&amp;lt;/tt&amp;gt; functionality of returning false if&lt;/span&gt;
  &lt;span class="c1"&gt;# that file has already been loaded is preserved.&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
    &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_path&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt; &lt;span class="ss"&gt;:to_path&lt;/span&gt;

    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_unresolved_default_spec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;spec&lt;/span&gt;
      &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_unresolved_default_spec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:gem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# If there are no unresolved deps, then we can use just try&lt;/span&gt;
    &lt;span class="c1"&gt;# normal require handle loading a gem from the rescue below.&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Specification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unresolved_deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gem_original_require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;# If +path+ is for a gem that has already been loaded, don't&lt;/span&gt;
    &lt;span class="c1"&gt;# bother trying to find it in an unresolved gem, just go straight&lt;/span&gt;
    &lt;span class="c1"&gt;# to normal require.&lt;/span&gt;
    &lt;span class="c1"&gt;#--&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO request access to the C implementation of this to speed up RubyGems&lt;/span&gt;

    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Specification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_active_stub_by_path&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gem_original_require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;

    &lt;span class="c1"&gt;# Attempt to find +path+ in any unresolved gems...&lt;/span&gt;

    &lt;span class="n"&gt;found_specs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Specification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_in_unresolved&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

    &lt;span class="c1"&gt;# If there are no directly unresolved gems, then try and find +path+&lt;/span&gt;
    &lt;span class="c1"&gt;# in any gems that are available via the currently unresolved gems.&lt;/span&gt;
    &lt;span class="c1"&gt;# For example, given:&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;#   a =&amp;gt; b =&amp;gt; c =&amp;gt; d&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# If a and b are currently active with c being unresolved and d.rb is&lt;/span&gt;
    &lt;span class="c1"&gt;# requested, then find_in_unresolved_tree will find d.rb in d because&lt;/span&gt;
    &lt;span class="c1"&gt;# it's a dependency of c.&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;found_specs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;found_specs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Specification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_in_unresolved_tree&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

      &lt;span class="n"&gt;found_specs&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;found_spec&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;found_spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# We found +path+ directly in an unresolved gem. Now we figure out, of&lt;/span&gt;
    &lt;span class="c1"&gt;# the possible found specs, which one we should activate.&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;

      &lt;span class="c1"&gt;# Check that all the found specs are just different&lt;/span&gt;
      &lt;span class="c1"&gt;# versions of the same gem&lt;/span&gt;
      &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;found_specs&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&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;then&lt;/span&gt;
        &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LoadError&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;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; found in multiple gems: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Ok, now find a gem that has no conflicts, starting&lt;/span&gt;
      &lt;span class="c1"&gt;# at the highest version.&lt;/span&gt;
      &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;found_specs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_conflicts?&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;le&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LoadError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="s2"&gt;"unable to find a version of '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' to activate"&lt;/span&gt;
        &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
        &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gem_original_require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;LoadError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;load_error&lt;/span&gt;
    &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;

    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;load_error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Could not find"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load_error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try_activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;require_again&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="no"&gt;RUBYGEMS_ACTIVATION_MONITOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gem_original_require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;require_again&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;load_error&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="ss"&gt;:require&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的测试设置&lt;code&gt;path='active_support'&lt;/code&gt;，active_support 已经在本地安装了。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;active_model_serializers &lt;span class="o"&gt;(&lt;/span&gt;0.10.1, 0.8.3&lt;span class="o"&gt;)&lt;/span&gt;
activemodel &lt;span class="o"&gt;(&lt;/span&gt;4.1.14.2&lt;span class="o"&gt;)&lt;/span&gt;
activerecord &lt;span class="o"&gt;(&lt;/span&gt;4.1.14.2&lt;span class="o"&gt;)&lt;/span&gt;
activesupport &lt;span class="o"&gt;(&lt;/span&gt;4.1.14.2&lt;span class="o"&gt;)&lt;/span&gt;
omniauth-active_passport &lt;span class="o"&gt;(&lt;/span&gt;0.0.3&lt;span class="o"&gt;)&lt;/span&gt;
redis-activesupport &lt;span class="o"&gt;(&lt;/span&gt;4.1.5&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pry&lt;/code&gt; 进入 console，查看$:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/slop-3.6.0/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/method_source-0.8.2/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/pry-0.10.3/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/coderay-1.1.1/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/pry-nav-0.2.4/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/pry-rails-0.3.4/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/gems/ruby-2.1.2@acl-api/gems/pry-remote-0.1.8/lib"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/vendor_ruby/2.1.0"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/vendor_ruby"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0"&lt;/span&gt;,
 &lt;span class="s2"&gt;"/Users/hiveerli/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并没有 active_support 的 lib，所以下面的结果可以预见&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;2] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; gem_original_require &lt;span class="s1"&gt;'active_support'&lt;/span&gt;
LoadError: cannot load such file &lt;span class="nt"&gt;--&lt;/span&gt; active_support
from &lt;span class="o"&gt;(&lt;/span&gt;pry&lt;span class="o"&gt;)&lt;/span&gt;:2:in &lt;span class="sb"&gt;`&lt;/span&gt;require&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后手动测试 RubyGems 的 require 代码：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;1] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; path &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active_support'&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"active_support"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; RUBYGEMS_ACTIVATION_MONITOR.enter
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 1
&lt;span class="o"&gt;[&lt;/span&gt;3] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; path.respond_to? :to_path
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;4] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; Gem.find_unresolved_default_spec&lt;span class="o"&gt;(&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; nil
&lt;span class="o"&gt;[&lt;/span&gt;5] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; Gem::Specification.unresolved_deps.empty?
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;6] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; RUBYGEMS_ACTIVATION_MONITOR.exit
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#&amp;lt;mutex:0x007fecaf079c90&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;8] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; gem_original_require&lt;span class="o"&gt;(&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;
LoadError: cannot load such file &lt;span class="nt"&gt;--&lt;/span&gt; active_support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果走不下去了？ &lt;strong&gt;求解&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;但是如果我直接调用&lt;code&gt;require 'active_support'&lt;/code&gt;还是 work 的，迷惑不解中……&lt;/p&gt;
&lt;h2 id="问题已经解决"&gt;问题已经解决&lt;/h2&gt;
&lt;p&gt;正如评论中 1 楼所讲，其实源代码里面有一个 rescue，在 rescue 中有这样的一个条件表达式&lt;code&gt;load_error.message.end_with?(path) and Gem.try_activate(path)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;我们的错误信息是"LoadError: cannot load such file -- active_support"，这满足了&lt;code&gt;load_error.message.end_with?(path)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;而并列的第二表达式也是返回&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;20] pry&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; Gem.try_activate&lt;span class="o"&gt;(&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以接下来会执行 &lt;code&gt;return gem_original_require(path) if require_again&lt;/code&gt;，大家可以看到这里又在用原始的 require 来加载了。&lt;/p&gt;

&lt;p&gt;但是意外的是这里会成功，原因在于在条件判断的时候
我们执行了&lt;code&gt;Gem.try_activate(path)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这里会调用到&lt;code&gt;Gem::Specification#activate&lt;/code&gt;，而这个方法又有这样的一个调用&lt;code&gt;add_self_to_load_path&lt;/code&gt;，源码如下&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_self_to_load_path&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;default_gem?&lt;/span&gt;

  &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_require_paths&lt;/span&gt;

  &lt;span class="c1"&gt;# gem directories must come after -I and ENV['RUBYLIB']&lt;/span&gt;
  &lt;span class="n"&gt;insert_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_path_insert_index&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;insert_index&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c1"&gt;# gem directories must come after -I and ENV['RUBYLIB']&lt;/span&gt;
    &lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insert_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;# we are probably testing in core, -I and RUBYLIB don't apply&lt;/span&gt;
    &lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里自然明白，为什么最后对&lt;code&gt;gem_original_require&lt;/code&gt;的 call 会成功了！&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Tue, 28 Feb 2017 17:26:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/32406</link>
      <guid>https://ruby-china.org/topics/32406</guid>
    </item>
    <item>
      <title>Ruby 的字符串比较，一个天天用，但是没用对的知识点</title>
      <description>&lt;p&gt;在写 Ruby 代码的时候，经常会遇到需要比较字符串的场景。我相信遇到的最多的就是看两个字符串是否相等了&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;str2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况只要长度和内容相等就会返回&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;而有些时候遇到的情况比较特殊，像是这样的字符串“20170101”， “20170113”，或者是“2017-01-01”， “2017-01-13”
这些字符串一看就是代表的时间，而我们业务上也是需要比较出两个时间的大小。这个时候你可能会将字符串 parse 为&lt;code&gt;Time&lt;/code&gt;或者是&lt;code&gt;DateTime&lt;/code&gt;类型，然后再比较。
但是这比较麻烦，所以会考虑直接将字符串进行比较如何：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&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;"20170101"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"20170113"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&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;"2017-01-01"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"2017-01-13"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从结果来看这两个 case 都是正确的结果。但是背后的原理究竟是什么呢，其他的 case 是不是一样的 work 呢？&lt;/p&gt;

&lt;p&gt;大家觉得下面这个 case 的结果如何？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"20121110"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"201210100"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也许会出乎你的预料，结果是&lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&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;"20121110"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"20120110000000"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个也是&lt;code&gt;true&lt;/code&gt;。这是不是让你侥幸的认为他们就是在比较&lt;code&gt;str.to_i&lt;/code&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;A: 如果长度一样，那么从第一个开始比较，当遇到不一样的字符时，哪个字符串对应的字符的编码值大，就谁大。&lt;/p&gt;

&lt;p&gt;B: 如果长度不一样，并且较长字符串的前面部分跟短的字符串相同，那么算是长的字符串大。&lt;/p&gt;

&lt;p&gt;C: 如果长度不一样，并且较长字符串的前面部分跟短的字符串不同，那么按照规则 A 比较。&lt;/p&gt;

&lt;p&gt;所以回到比较两个时间的 case，你可以直接通过字符串比较，但是切记保证时间的格式一致，长度一致，否则得到的可能不是你想要的结果。&lt;/p&gt;

&lt;p&gt;如有错误，欢迎指正！&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 16 Feb 2017 21:31:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/32318</link>
      <guid>https://ruby-china.org/topics/32318</guid>
    </item>
    <item>
      <title>“码农的自我修养” 之 同源策略</title>
      <description>&lt;p&gt;最近在读 Rails 安全性相关的文章时，再一次接触了“同源策略”这个术语。当然不陌生，不然还怎么混，俗话说“没吃过猪肉，至少见过猪跑嘛”！&lt;/p&gt;

&lt;p&gt;可是真心讲，我并没有真正理解他的内涵。秉持着一颗孜孜不倦，求索不断的心，我开始了探索“同源策略”本质内涵之路。&lt;/p&gt;

&lt;p&gt;起码看了 30+ 的文章，Google 表示我就这么多资源了……&lt;/p&gt;

&lt;p&gt;最终我获得了一些想要的东西，但是对“同源策略”的理解也并没有完全透测，苦恼😖&lt;/p&gt;

&lt;p&gt;下面就跟大家分享下我的所得吧！
这是我认为还有用的文章：&lt;/p&gt;

&lt;p&gt;&lt;a href="http://yincheng.site/cross-domain/comment-page-1#comment-3307" rel="nofollow" target="_blank"&gt;http://yincheng.site/cross-domain/comment-page-1#comment-3307&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html" rel="nofollow" target="_blank"&gt;http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://eleveneat.com/2015/05/22/Same-origin-policy-md/" rel="nofollow" target="_blank"&gt;http://eleveneat.com/2015/05/22/Same-origin-policy-md/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://bbs.csdn.net/topics/390716390" rel="nofollow" target="_blank"&gt;http://bbs.csdn.net/topics/390716390&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blog.csdn.net/shimiso/article/details/21830313" rel="nofollow" target="_blank"&gt;http://blog.csdn.net/shimiso/article/details/21830313&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Same-origin_policy" rel="nofollow" target="_blank"&gt;https://en.wikipedia.org/wiki/Same-origin_policy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials" rel="nofollow" target="_blank"&gt;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wiki 的解释：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.[1][2] This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mdn 的解释：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same-origin policy restricts how a document or script loaded from one origin can interact with a resource from another origin. It is a critical security mechanism for isolating potentially malicious documents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;百度的解释：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;同源策略（Same origin policy）是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的，浏览器只是针对同源策略的一种实现。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;仔细研读之后发现百度的解释是屁话，Wiki 的解释不全面，而 MDN 话不多，但说的都是核心。&lt;/p&gt;

&lt;p&gt;而我说的核心如是：&lt;strong&gt;document or script&lt;/strong&gt; loaded from one origin. &lt;strong&gt;resource&lt;/strong&gt; from another origin&lt;/p&gt;

&lt;p&gt;下面分别解释这两个核心：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;document or script&lt;/strong&gt; loaded from one origin&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document&lt;/strong&gt;怎么操作另外一个 origin 的资源呢？&lt;/p&gt;

&lt;p&gt;借用阮一峰博文的例子：&lt;/p&gt;

&lt;p&gt;父窗口下有一个 iframe 窗口，如果父窗口的 dom 想去拿 iframe 窗口的 dom 会报错，反之亦然：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myIFrame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;contentWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;
&lt;span class="c1"&gt;// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面命令中，父窗口想获取子窗口的 DOM，因为跨源导致报错。 
反之亦然，子窗口获取主窗口的 DOM 也会报错。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="c1"&gt;// 报错&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt; 怎么去操作另外一个 origin 的资源呢？&lt;/p&gt;

&lt;p&gt;除了 Ajax 我还没找到其他的例子：
这是我自己做的一个实验，我启动一个 local server，然后通过 ajax 向我的 heroku 服务器发送请求：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://daily-plan.herokuapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version=1;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;xhrFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;withCredentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
          &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Error Message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;XMLHttpRequest cannot load &lt;a href="https://daily-plan.herokuapp.com/" rel="nofollow" target="_blank"&gt;https://daily-plan.herokuapp.com/&lt;/a&gt;. Redirect from '&lt;a href="https://daily-plan.herokuapp.com/" rel="nofollow" target="_blank"&gt;https://daily-plan.herokuapp.com/&lt;/a&gt;' to '&lt;a href="https://daily-plan.herokuapp.com/plans" rel="nofollow" target="_blank"&gt;https://daily-plan.herokuapp.com/plans&lt;/a&gt;' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '&lt;a href="http://localhost:3000" rel="nofollow" target="_blank"&gt;http://localhost:3000&lt;/a&gt;' is therefore not allowed access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通过上面的两个例子我们可以引申出第二个要点：
&lt;strong&gt;resource&lt;/strong&gt; from another origin&lt;/p&gt;

&lt;p&gt;什么算是一个 origin 的资源？
从之上的例子可以看出来，DOM 算是一类，Ajax 返回的数据算是一类 (这一类包含 DOM，当然也包含像是 JSON 格式的数据)，还有一种这里没有举例的就是 Cookie。&lt;/p&gt;

&lt;p&gt;关于 Cookie，上面 Ajax 的例子也同时验证了同源策略对 Cookie 的部分限制。&lt;/p&gt;

&lt;p&gt;在我没有设置 'withCredentials': true 的时候，Ajax cross domain request 没有带任何的 cookie(local domain or heroku domain)；而设置之后同样的 request 带上了 heroku domain 下的 cookie。&lt;/p&gt;

&lt;p&gt;还有一些比较亮眼的结论是：&lt;/p&gt;

&lt;p&gt;同源策略并没有限制请求的发送，仅仅是阻隔了响应的获取（参见：&lt;a href="http://yincheng.site/cross-domain/comment-page-1#comment-3307" rel="nofollow" target="_blank"&gt;http://yincheng.site/cross-domain/comment-page-1#comment-3307&lt;/a&gt;）&lt;/p&gt;

&lt;p&gt;在浏览器中，script, img, iframe, link 等标签都可以加载跨域资源，而不受同源限制，但浏览器限制了 JavaScript 的权限使其不能读、写加载的内容&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我想说&lt;/strong&gt;：
这目前是我整理得到的一些信息，以及自己的认识。我相信，肯定这不是全部。所以需要大家来完善下，帮助等多人理解这个关键的概念。毕竟，没有它我甚至不敢打开浏览器写这篇博文。&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Thu, 19 Jan 2017 12:03:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/32174</link>
      <guid>https://ruby-china.org/topics/32174</guid>
    </item>
    <item>
      <title>练手的前端小项目 poker</title>
      <description>&lt;p&gt;&lt;a href="http://cdifs-49f.poker/" rel="nofollow" target="_blank"&gt;http://cdifs-49f.poker/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/IFS49F/poker" rel="nofollow" target="_blank"&gt;https://github.com/IFS49F/poker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在敏捷开发的过程中，我们经常需要对任务进行切分，估工作量（简称估点）。那么我们就需要用到一个叫 poker 的东西，国外有一个些较为成熟的产品，像是&lt;a href="https://www.pointingpoker.com/" rel="nofollow" target="_blank"&gt;https://www.pointingpoker.com/&lt;/a&gt;。但是由于是国外站点，所以异常的慢，不适合国内的团队使用，所以才有想法自己写一个。主要目的是保证自己能用的舒心，如果能同时给大家带来方便那就最好不过了。&lt;/p&gt;

&lt;p&gt;作为一个前端新手，入门都算不上。代码拙劣，还望各位大咖不吝赐教，帮助这个小项目能够尽善尽美！&lt;/p&gt;</description>
      <author>hiveer</author>
      <pubDate>Mon, 09 Jan 2017 15:21:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/32081</link>
      <guid>https://ruby-china.org/topics/32081</guid>
    </item>
  </channel>
</rss>
