<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>qichunren</title>
    <link>https://ruby-china.org/qichunren</link>
    <description>Working at home, looking for a remote web-dev job.</description>
    <language>en-us</language>
    <item>
      <title>SolidQueue 支持防抖式的 Job 么？</title>
      <description>&lt;p&gt;我的需求场景是这样的，在后台用户可以添加/编辑某个 Post 记录，在 after_commit 后，需要执行一个比较耗时的基于当前表的所有记录的异步缓存任务 PopulateGlobalPostsCacheJob。如果用户在一条条操作记录，这样会依次创建多任务在执行，而实际上只有最后一个任务是有效的。&lt;/p&gt;

&lt;p&gt;我想到的办法就是在模型的 after_commit 中，插入一个在 2 分钟后执行的异步任务。如果这时发现有等待中的任务，就取消之前的这个任务。&lt;/p&gt;

&lt;p&gt;要是 SolidQueue 能支持这个用法就好了。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Tue, 14 Oct 2025 20:25:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/44340</link>
      <guid>https://ruby-china.org/topics/44340</guid>
    </item>
    <item>
      <title>defined？方法疑问</title>
      <description>&lt;p&gt;在使用 audio-log 时遇到 request 为空的问题（不知道是什么时候 Ruby 版本或者 Rails 升级后遇到的），通过 &lt;a href="https://github.com/qichunren/audit-log/commit/49830b66c3707ee4ab11d9ca031459c903ddb14f" rel="nofollow" target="_blank"&gt;https://github.com/qichunren/audit-log/commit/49830b66c3707ee4ab11d9ca031459c903ddb14f&lt;/a&gt; 解决了，但是不清楚原因。&lt;/p&gt;

&lt;p&gt;我通过以下脚本没有复现：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def request 
  puts "a request method"
  1
end

puts "&amp;gt; #{defined?(request)}"

a = "111" 
a = "555" unless defined?(request)
puts "a: #{a}"

unless defined?(request)
  puts "request not defined"
else
  puts "request defined"
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; method
a: 111
request defined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问 AI，它一本正经的解决我没有看明白：
&lt;img src="https://l.ruby-china.com/photo/qichunren/f5ffbc26-c718-4fbd-9f6e-9bcd90a031aa.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Tue, 18 Feb 2025 16:43:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/44059</link>
      <guid>https://ruby-china.org/topics/44059</guid>
    </item>
    <item>
      <title>求助：ActionCable 的 websocket 怎么样处理网络断开重联？</title>
      <description>&lt;p&gt;我最近在做一个 设备实时控制的手机 APP，我在使用 ActionCable 的自带的重联机制时，发现它内部的 websocket 在处理连接超时这一块应该是有问题的。&lt;/p&gt;

&lt;p&gt;问题重现：&lt;/p&gt;

&lt;p&gt;按照官方文档的做法，创建 Consumer，然后创建好 Channel，在连接正常时，可以工作。现在将 Rails 服务进程停止，此时页面里 ActionCable 检测到 websocket 连接断开，会自己尝试不断的连接 ActionCable server。然后我再将 Rails 服务器启动好，此时 &lt;strong&gt;页面里的 Channel 订阅不能按照预期的很快的恢复到正常的可用状态&lt;/strong&gt;，要过几分钟 (大概) 才能自行重联到 ActionCable 服务器。&lt;/p&gt;

&lt;p&gt;ActionCable 的 Javascript 代码：&lt;a href="https://github.com/rails/rails/blob/main/actioncable/app/assets/javascripts/actioncable.esm.js" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/main/actioncable/app/assets/javascripts/actioncable.esm.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;类 ConnectionMonitor 中的重联方法：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;reconnectIfStale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectionIsStale&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&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="s2"&gt;`ConnectionMonitor detected stale connection. reconnectAttempts = &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;reconnectAttempts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, time stale = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;secondsSince&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;refreshedAt&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; s, stale threshold = &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="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staleThreshold&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; s`&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;reconnectAttempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnectedRecently&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&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="s2"&gt;`ConnectionMonitor skipping reopening recent disconnect. time disconnected = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;secondsSince&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;disconnectedAt&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; s`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&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;ConnectionMonitor reopening&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reopen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后看一下 Connection 类的相关方法：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&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="s2"&gt;`Attempted to open WebSocket, but existing socket is &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;getState&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 处于连接中的状态时，代码就不向下执行了。&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&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="s2"&gt;`Opening WebSocket, current state is &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;getState&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, subprotocols: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uninstallEventHandlers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebSocket&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;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;protocols&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;installEventHandlers&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;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;close&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;allowReconnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allowReconnect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;allowReconnect&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowReconnect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;reopen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&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="s2"&gt;`Reopening WebSocket, current state is &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;getState&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&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;Failed to reopen WebSocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&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="s2"&gt;`Reopening WebSocket in &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="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reopenDelay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&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;open&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="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reopenDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&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;问题出在这个 isActive 方法上，&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isActive() {
    return this.isState("open", "connecting");
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个“connecting” “正在连接”的状态没有一个可以配置的超时的机制，导致 ActionCable 连接恢复好了后，前端页面还不能及时地恢复到可用的状态，显示“Attempted to open WebSocket, but existing socket is connecting”&lt;/p&gt;

&lt;p&gt;你们在使用 ActionCable 时有没有遇到这方面类似的问题，是不是要自己写补丁代码来解决这个问题？我在 Issue 里找，没有发现相关的问题。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qichunren/32230dab-f1a9-4120-903f-30d2778e2e55.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Tue, 11 Apr 2023 02:54:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/42999</link>
      <guid>https://ruby-china.org/topics/42999</guid>
    </item>
    <item>
      <title>Rails 路由怎么匹配生成一个带有 "/"结尾的 的 URL？</title>
      <description>&lt;p&gt;在 Rails routes.rb 中  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get "/:name", to: "hello#welcome", as: :hello&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get "/:name/", to: "hello#welcome", as: :hello2&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;两种形式都是生成同一个形式的 URL，
&lt;code&gt;hello_path(name: "Jim")&lt;/code&gt; 生成为 /Jim,   &lt;code&gt;hello2_path(name: "Jim")&lt;/code&gt; 也是生成为 /Jim，而不是 /Jim/,&lt;/p&gt;

&lt;p&gt;有什么办法解决这个问题呢？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Sat, 10 Sep 2022 16:22:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/42645</link>
      <guid>https://ruby-china.org/topics/42645</guid>
    </item>
    <item>
      <title>Rails 项目里使用 React 的方式</title>
      <description>&lt;p&gt;我自己其实也在折腾前端，感觉还是没有真正找到一个合适的方案。前端这玩意，感觉折腾进来了，就无止境了，所以在选型一个技术框架的时候得全面考查清楚。我记得很久以前出来了 AngularJS，我研究了一阵子，感觉很惊艳，在一个项目中采用了，后面你看，出来了 Angular2，完全不兼容了，再后面出来 React / Vue 这些，基本没有人搞 AngularJS 了。所以对前端技术的选型应该是一个很慎重的问题。 &lt;/p&gt;

&lt;p&gt;我目前自己做一个电商网站，我采用的是 Rails 7 / esbuild / Turbo / Stimulus, 对于大部分的数据展示页面和表单都是采用 Rails 原生的那一套很直观地快速实现，但是有些复杂一点的页面，如商品图片管理，包括图片即时批量上传、排序、简单编辑等，用 React 的最基本库 + Json + ActiveStorageDirectUpload 就解决问题了，可以说复杂度减少数倍。&lt;/p&gt;

&lt;p&gt;我目前使用 React 的方法是通过 Stimulus Controller 集成 React 的。这样的话，在某些特定页面里局部使用 React 简化一些交互。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"
import * as React from 'react'
import * as ReactDOM from "react-dom";
import ProductMedia from "../components/product_media"

export default class extends Controller {
  connect() {
    let product_id = this.element.dataset.product_id;
    let url = this.element.dataset.directUploadUrl;    
    ReactDOM.render(&amp;lt;ProductMedia product_id={product_id} upload_url={url} /&amp;gt;, this.element);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实我好想找到一个合适好用的 Rails 前端集成方案，同时又能不失去 Rails 的特性的途径。我了解的各种可能的方法有（不限于）：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React 纯前端 (Create React App) + Rails JSON API, 前后端项目分离&lt;/li&gt;
&lt;li&gt;NextJS + NodeJS + Rails JSON API 前后端项目分离&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shakacode/react_on_rails" rel="nofollow" target="_blank" title=""&gt;react_on_rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;我上面提到的通过 Stimulus 集成，选择性的在某一些页面里采用 React app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lit.dev/" rel="nofollow" target="_blank" title=""&gt;lit&lt;/a&gt; ？&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我想请教一下大家，你们的真实项目里是怎么样使用 React 这类的框架呢？有没有什么实践分享一下，谢谢。&lt;/p&gt;

&lt;p&gt;说到 React，刚好前几天看到有一个 v2ex 上的帖子 &lt;a href="https://v2ex.com/t/850921?p=1" rel="nofollow" target="_blank" title=""&gt;2022 年 react 生态，大家都用啥&lt;/a&gt; 看看有多少名词吧？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Mon, 09 May 2022 14:22:25 +0800</pubDate>
      <link>https://ruby-china.org/topics/42380</link>
      <guid>https://ruby-china.org/topics/42380</guid>
    </item>
    <item>
      <title>宿主机中的 nginx 怎么访问 docker 容器中的 public assets 文件？</title>
      <description>&lt;p&gt;我目前正在切换到 docker 部署方案。nginx 是原来就已经安装在服务器的主机环境中，通过将 docker 容器中的 puma 的端口暴露在主机中，可以实现 Rails 程序跑起来。但是静态文件这部分，public/目录怎么让 nginx 能访问到呢？&lt;/p&gt;

&lt;p&gt;我看到 homeland 是由于就一个 Rails 应用，它是直接在 Rails 应用的容器中安装了 nginx，这样就可以直接访问到同一个容器中的文件。但是我这个服务器，由于有多个网站在运行，我后续打算将每一个 Rails 应用都采用 docker 容器来运行，它们共用一个 nginx 服务（后面有可能采主机的 nginx 或者是 docker 容器的 nginx 服务）。&lt;/p&gt;

&lt;p&gt;所以 nginx 怎么样可以访问每个容器中 Rails 应用的 public 目录呢，以便配置&lt;code&gt;/assets&lt;/code&gt;等静态资源 url？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Thu, 14 Apr 2022 19:00:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/42306</link>
      <guid>https://ruby-china.org/topics/42306</guid>
    </item>
    <item>
      <title>稍加改进 render partial ，复用 html 模板更方便了</title>
      <description>&lt;p&gt;我最近在整理项目中一些 TailwindCSS 的常用的 HTML 片段，作为 erb partial‘组件’方便复用。在带有多个参数的情况下，默认的 render 方法用起来不是很方便，暂时不想引入 view_component 这类复杂一点的东西，就稍加改进了一下，现在页面中 render 方法用起来更方便了，可以将一些常用的 TailwindCSS HTML 代码复用起来。&lt;/p&gt;

&lt;p&gt;Github README: &lt;a href="https://github.com/qichunren/view_context#viewcontext" rel="nofollow" target="_blank"&gt;https://github.com/qichunren/view_context#viewcontext&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一般情况下，如下面这个 Tab 的 html 模板&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;# _tab.html.erb
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 flex items-center justify-between border-b-2 border-gray-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex px-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- slot for tab links --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;yield&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- slot for right button --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;side&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用的时候这样用：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;tab&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;side:&lt;/span&gt; &lt;span class="err"&gt;"&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'/some'&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'btn'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;".html_safe do  %&amp;gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 2&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 3&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样用起来一点也不方便。现在稍微改进一点，同时没有带来太多复杂性，也没有带来破坏性。&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;# _tab.html.erb
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;_view_context = &lt;/span&gt;&lt;span class="s"&gt;ViewContext.new(self)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 flex items-center justify-between border-b-2 border-gray-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab px-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;yield&lt;/span&gt; &lt;span class="na"&gt;_view_context&lt;/span&gt;  &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;_view_context.main&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;_view_context.secondary&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, render tab template partial below:&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;tab&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;context.set_main&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 2&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/some"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 3&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;context.set_secondary&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'/settings'&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'btn'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Settings&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/qichunren/view_context/blob/main/view_context.rb" rel="nofollow" target="_blank" title=""&gt;view_context.rb&lt;/a&gt; 的代码如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewContext&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:secondary&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_view&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;
      &lt;span class="vi"&gt;@view.capture&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_secondary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@secondary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;
      &lt;span class="vi"&gt;@view.capture&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^set_([a-z]\w*)/&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;m&lt;/span&gt;
      &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;
        &lt;span class="nb"&gt;instance_variable_set&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;iv&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@view.capture&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;blk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;instance_variable_set&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;iv&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只需要把这个 view_context.rb 文件放在 lib 下，并设置&lt;code&gt;config.eager_load_paths &amp;lt;&amp;lt; Rails.root.join("lib")&lt;/code&gt;就可以了。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Fri, 25 Mar 2022 00:14:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/42256</link>
      <guid>https://ruby-china.org/topics/42256</guid>
    </item>
    <item>
      <title>在 Ruby 中实现一个信号订阅通知功能 (一)</title>
      <description>&lt;p&gt;为了实现模块之间的解耦，我需要一个类似 Qt 中的&lt;a href="https://doc.qt.io/qt-5/signalsandslots.html" rel="nofollow" target="_blank" title=""&gt;信号槽机制&lt;/a&gt;&amp;nbsp;和&amp;nbsp;GDScript&amp;nbsp;中的&lt;a href="https://docs.godotengine.org/en/3.1/getting_started/step_by_step/signals.html" rel="nofollow" target="_blank" title=""&gt;信号机制​​​​​​​&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;使用场景：&lt;/p&gt;

&lt;p&gt;有一个计数器和一个显示屏，当计数器更新时，需要显示屏同步显示更新的计数，&lt;strong&gt;后续可能需要将计数器的计数用于到其它地方&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;先看看在没有实现信号订阅机制时的代码是怎么样写的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;led_screen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@led_screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;led_screen&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;
        &lt;span class="vi"&gt;@value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="vi"&gt;@led_screen.display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LedScreen&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# 显示到屏上，暂无实现&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;led_screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LedScreen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;led_screen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上代码可以基本工作，但是我对这样的代码不满意。在对计数器 Counter 类进行单元测试时，需要引入&amp;nbsp;LedScreen&amp;nbsp;类，增加了复杂性。另外当有新的需求时，需要修改 Counter#increment 方法。&lt;/p&gt;

&lt;p&gt;我不喜欢这样，我准备使用信号机制实现解耦。当需要将计数器 Counter&amp;nbsp;的值显示到其它地方，也不需要改动&amp;nbsp;Counter&amp;nbsp;的代码，计数器&amp;nbsp;Counter&amp;nbsp;就只是做它本应该做的事情。&lt;/p&gt;

&lt;p&gt;首先不考虑具体的实现，先把我设想的 API 接口初步定下来，后续考虑使用&amp;nbsp;Ruby&amp;nbsp;的魔法来实现出来。用下面的代码来说明：&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;Counter&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:value&lt;/span&gt;
    &lt;span class="n"&gt;register_signal&lt;/span&gt; &lt;span class="ss"&gt;:value_changed&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;
        &lt;span class="vi"&gt;@value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;emit_signal&lt;/span&gt; &lt;span class="ss"&gt;:value_changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@value&lt;/span&gt;
        &lt;span class="c1"&gt;# emit_signal 第0个参数是信号名，第1个参数是附带的消息&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;class&lt;/span&gt; &lt;span class="nc"&gt;LedScreen&lt;/span&gt;
    &lt;span class="c1"&gt;# 默认调用 on_value_changed&lt;/span&gt;
    &lt;span class="c1"&gt;# 也可以附加一个参数用来指定信号订阅的方法&lt;/span&gt;
    &lt;span class="c1"&gt;# eg: subscribe_signal :value_changed, :when_value_changed&lt;/span&gt;
    &lt;span class="n"&gt;subscribe_signal&lt;/span&gt; &lt;span class="ss"&gt;:value_changed&lt;/span&gt; 

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_value_changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Got counter value changed to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# 更新到显示屏上&lt;/span&gt;
        &lt;span class="nb"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# 显示到屏上，暂无实现&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Testcase&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;
&lt;span class="n"&gt;led_screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LedScreen&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;# 期望 LedScreen#on_value_changed 方法被调用&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;待续。也希望了解一下大家的想法。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Fri, 13 Dec 2019 19:29:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/39332</link>
      <guid>https://ruby-china.org/topics/39332</guid>
    </item>
    <item>
      <title>使用 Protocol Buffers 在 TCP 中需要自己设计协议头以解决 “粘包” 问题吗？</title>
      <description>&lt;p&gt;由于 TCP 是流式的，我发送数据时，使用 Protocol Buffers 序列化消息后，需要额外将序列化的消息放进自己设计的数据包中吗？还是可以直接发送给接收方？&lt;/p&gt;

&lt;p&gt;想知道 &lt;a href="https://developers.google.com/protocol-buffers/" rel="nofollow" target="_blank" title=""&gt;Protocol Buffers&lt;/a&gt; 本身是否已经支持这一点，即接收方收到数据放入数据缓冲区后，直接反序列化即可解析出消息实体？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Tue, 14 Aug 2018 12:07:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/37327</link>
      <guid>https://ruby-china.org/topics/37327</guid>
    </item>
    <item>
      <title>我觉得 “头条” 栏目没有什么用</title>
      <description>&lt;p&gt;每次点进去看看，还是一样的内容，三个月前的发布的。是不想维护这个栏目了吗？还不如去掉算了。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Mon, 24 Jul 2017 13:47:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/33603</link>
      <guid>https://ruby-china.org/topics/33603</guid>
    </item>
    <item>
      <title>免费赠送图书</title>
      <description>&lt;p&gt;最近看了一本书《断舍离》，有一种想见恨晚的感觉。我想到了我每次搬家的最难收拾的那一推书，与其放着吃灰，不如送给需要的人。有些后悔没有将这些书籍早点处理，有好多技术书都过时了。这些书大都是毕业那几年买的看的，后来主要看电子书居多，以后也不会再买纸书了。&lt;/p&gt;

&lt;p&gt;以下图片中的这些书，每个人可以选 4 本，以回复的时间为准，邮费自理，收件地址可以单独发送到我的邮箱中 whyruby#gmail.com。
&lt;img src="https://l.ruby-china.com/photo/2017/2ddd6d20-cb8c-4086-8606-a938f4558248.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/e8980b3c-c844-4cc7-9cfd-f1bf1ad8b50a.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/3aaa6cde-c28d-46db-8e21-51ae2ed4ef75.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;回复书名的时候，麻烦看一下其他同学的回复，检查你要的书是否已经没有了。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Sun, 04 Jun 2017 15:04:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/33137</link>
      <guid>https://ruby-china.org/topics/33137</guid>
    </item>
    <item>
      <title>-----</title>
      <description>&lt;hr&gt;</description>
      <author>qichunren</author>
      <pubDate>Thu, 25 May 2017 17:01:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/33072</link>
      <guid>https://ruby-china.org/topics/33072</guid>
    </item>
    <item>
      <title>Ruby China 的 assets 是如何部署到 UpYun 的 CDN？</title>
      <description>&lt;p&gt;我在 &lt;a href="https://github.com/ruby-china/homeland/search?utf8=%E2%9C%93&amp;amp;q=upyun&amp;amp;type=" rel="nofollow" target="_blank" title=""&gt;homeland&lt;/a&gt; 中没有搜索到相关的内容。&lt;/p&gt;

&lt;p&gt;部署 assets 到 upyun 的 cdn 这一步是自动完成的吗？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Wed, 10 May 2017 11:32:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/32966</link>
      <guid>https://ruby-china.org/topics/32966</guid>
    </item>
    <item>
      <title>[上海] 塞伯坦 招聘 Ruby 工程师 2 名</title>
      <description>&lt;h2 id="我们是谁？"&gt;我们是谁？&lt;/h2&gt;
&lt;p&gt;上海塞伯坦智能科技有限公司：成立于 2015 年，最近获得天使融资，提供高速图片鉴别算法，鉴别图片视频服务。网站是 &lt;a href="https://exadeep.com" rel="nofollow" target="_blank"&gt;https://exadeep.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们使用机器学习来分析图片和视频，做各种有趣的事情，例如目前实现的图片鉴黄和汽车型号识别。更多牛 X 的使用玩法还在后面！期待 Ruby 高手加盟！&lt;/p&gt;
&lt;h2 id="任职要求:"&gt;任职要求：&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Ruby on Rails 2 年以上开发经验，有独立开发的能力。&lt;/li&gt;
&lt;li&gt;熟练应用 Unix/Linux 操作系统、Git 等版本管理软件。&lt;/li&gt;
&lt;li&gt;懂得各类算法的优缺点。&lt;/li&gt;
&lt;li&gt;熟悉前端开发常用技术栈。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="职位描述:"&gt;职位描述：&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;网站前端开发&lt;/li&gt;
&lt;li&gt;网站后端开发，使用 API 对接&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="职位"&gt;职位&lt;/h2&gt;
&lt;p&gt;职位：中高级 Rails 工程师；薪资：14k+
职位：初级 Rails 工程师；薪资：8k-12k&lt;/p&gt;
&lt;h2 id="公司地址"&gt;公司地址&lt;/h2&gt;
&lt;p&gt;雁荡路 107 号&lt;/p&gt;
&lt;h2 id="联系方式"&gt;联系方式&lt;/h2&gt;
&lt;p&gt;请把您的简历或任何有说服力的资料发往：three@exadeep.com，谢谢！&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Mon, 20 Jul 2015 18:20:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/26578</link>
      <guid>https://ruby-china.org/topics/26578</guid>
    </item>
    <item>
      <title>刚才 ruby-china 网站怎么了？</title>
      <description>&lt;p&gt;我以为出什么事了&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Fri, 23 May 2014 16:06:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/19494</link>
      <guid>https://ruby-china.org/topics/19494</guid>
    </item>
    <item>
      <title>基于 WebSocket 写了一个简单实时的 pusher 工具：SimplePusher</title>
      <description>&lt;p&gt;受 pusher,socket.io,slanger 等工具的影响，加上我目前的一个项目对实时方面的需要，又不希望太复杂和引入额外的组件，我就直接基于 websocket 做了这样一个工具，服务器端可以直接运行在基于像 Thin 等构建于 Eventmachine 之上的 Rails 项目或者 Sinatra 项目中。&lt;/p&gt;
&lt;h2 id="用法"&gt;用法&lt;/h2&gt;
&lt;p&gt;添加&lt;code&gt;gem 'simple_pusher'&lt;/code&gt;到&lt;code&gt;Gemfile&lt;/code&gt;中&lt;/p&gt;

&lt;p&gt;然后添加初始化代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/initializers/simple_pusher.rb
EventMachine.next_tick do
  SimplePusher.setup do |config|
    config.port = 8088
    config.debug = false
  end
  SimplePusher.start
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;application.js 中添加&lt;code&gt;//= require simple_pusher&lt;/code&gt;, 页面中添加 js 代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;
    var simple_pusher = new SimplePusher("ws://&amp;lt;%= request.host %&amp;gt;:8088/");
    simple_pusher.broadcast("Hello everybody.");
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器端的 API 有&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SimplePusher.broadcast&lt;/code&gt; 方法向所有的连接客户端 push 消息
&lt;code&gt;SimplePusher.on&lt;/code&gt; 处理客户端 emit 的消息&lt;/p&gt;

&lt;p&gt;更多详细的信息直接看代码吧。&lt;a href="https://github.com/qichunren/simple_pusher" rel="nofollow" target="_blank" title=""&gt;https://github.com/qichunren/simple_pusher&lt;/a&gt;&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Sun, 18 May 2014 18:33:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/19357</link>
      <guid>https://ruby-china.org/topics/19357</guid>
    </item>
    <item>
      <title>小伙伴们对加密 Ruby 源代码有需求吗？</title>
      <description>&lt;p&gt;你们做的产品如果是开发出来后，直接打包发布给客户，你们是怎么样保护产品中的 Ruby 源代码的？&lt;/p&gt;

&lt;p&gt;我们做的东西有这方面的需求，几个月前我们修改了 Ruby 的核心加载方法（Ruby 1.9.3），可以将项目中的 Ruby 源文件加密，同时 和官方发布的 Ruby 版本相比，在使用上没有什么两样，支持项目中的加密的 Ruby 文件和没有加密的文件。&lt;/p&gt;

&lt;p&gt;想了解一下小伙伴们对于这个有什么想法呢？&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Fri, 11 Oct 2013 12:55:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/14667</link>
      <guid>https://ruby-china.org/topics/14667</guid>
    </item>
    <item>
      <title>社区的 API 谁来完善一下呗</title>
      <description>&lt;p&gt;我最近有点时间，在家里开发 Ruby China 的 iPhone 客户端，进度大约是 80% 左右。&lt;/p&gt;

&lt;p&gt;目前的一个问题是服务器端的 JSON API 接口功能还不够完善（利用 grape 库）&lt;a href="https://github.com/ruby-china/ruby-china/blob/master/lib/api.rb" rel="nofollow" target="_blank"&gt;https://github.com/ruby-china/ruby-china/blob/master/lib/api.rb&lt;/a&gt; ，如回复主题\关注主题\收藏主题，以及个人的一些信息之类的，你们大家谁感兴趣，帮忙完善一把吧。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;我本来是打算自己来完善的，但是从服务器端、客户端切换来切换去的，也感觉挺麻烦的，我想专心地把客户端的体验做好。&lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Thu, 12 Jul 2012 15:49:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/4271</link>
      <guid>https://ruby-china.org/topics/4271</guid>
    </item>
    <item>
      <title>大家对 Github 的 Feed 功能有没有觉得不好用？</title>
      <description>&lt;p&gt;我个人目前感觉 Github 的 Feed 功能 不能满足我的需求了。&lt;/p&gt;

&lt;p&gt;整体给我的感觉就是满屏的 Feed 信息让我感觉很杂乱，想从中了解到我想要的有趣的信息不容易。当然也有可能是我关注的源（用户和项目）没有弄好。但是在 github 里各种各样的有趣的、实用的项目不断出现，我关注吧，又感觉关注过多。真是矛盾啊！另外关注的人都是在一起，我有时还想要看看我熟悉的一些开发者们的 timeline，找起来还真是麻烦。&lt;/p&gt;

&lt;p&gt;大家对此有什么看法呢？或者说大家平时是怎么样浏览 Github 的呢？  &lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Mon, 18 Jun 2012 16:54:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/3873</link>
      <guid>https://ruby-china.org/topics/3873</guid>
    </item>
    <item>
      <title>Ruby China 的 Chrome 插件来了</title>
      <description>&lt;p&gt;我最近给 Ruby China 写了一个 Chrome 插件。这样就不用每次亲自到网站上来刷新了，浏览更方便，有个人的未读提醒。&lt;/p&gt;

&lt;p&gt;代码开源，在 &lt;a href="https://github.com/qichunren/ruby-china-chrome" rel="nofollow" target="_blank"&gt;https://github.com/qichunren/ruby-china-chrome&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;插件安装地址：&lt;a href="https://github.com/downloads/qichunren/ruby-china-chrome/ruby-china-chrome.crx" rel="nofollow" target="_blank"&gt;https://github.com/downloads/qichunren/ruby-china-chrome/ruby-china-chrome.crx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前有用户的头像 (gravatar) 还没有显示出来，这个问题等&lt;a href="/huacnlee" class="user-mention" title="@huacnlee"&gt;&lt;i&gt;@&lt;/i&gt;huacnlee&lt;/a&gt; 再次部署后就解决了。其它一些存在的问题会慢慢解决的。  &lt;/p&gt;</description>
      <author>qichunren</author>
      <pubDate>Wed, 13 Jun 2012 11:08:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/3778</link>
      <guid>https://ruby-china.org/topics/3778</guid>
    </item>
  </channel>
</rss>
