<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>BranLiang (BranLiang)</title>
    <link>https://ruby-china.org/BranLiang</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Octokiq!🐙, 基于 ractor 的后台处理服务 🚀(附简单跑分)</title>
      <description>&lt;p&gt;Ruby 3.0 终于在圣诞节发布了🎉&lt;/p&gt;

&lt;p&gt;对其中 ractor 带来的多核支持比较感兴趣，于是基于测试的态度开发了一个简单的类 sidekiq 后台服务。gem 已经发布 0.1.0 版本，有兴趣的也可以下载进行测试体验。暂时还只有最最基础的功能，不过整个代码结构也为未来的扩展提供了空间。&lt;/p&gt;

&lt;p&gt;项目地址： &lt;a href="https://github.com/BranLiang/octokiq" rel="nofollow" target="_blank"&gt;https://github.com/BranLiang/octokiq&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://screenshot.click/30-24-y2rhi-eurav.gif" title="" alt="Octokiq demo"&gt;&lt;/p&gt;

&lt;p&gt;说一下总体感受：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;速度貌似有不小的提升&lt;/li&gt;
&lt;li&gt;第三方 gem 的 ractor 支持不是很理想，包括基础的 redis-rb 的使用都存在问题&lt;/li&gt;
&lt;li&gt;ENV 在非 main-ractor 环境下面无法访问，ractor 里面能够访问的 object 限制较大&lt;/li&gt;
&lt;li&gt;如何让自己的代码保持 ractor safe 是今后需要考虑的内容&lt;/li&gt;
&lt;li&gt;如何判断代码是否 ractor safe？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;跑分&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/BranLiang/octokiq/tree/main/examples/benchmark" rel="nofollow" target="_blank"&gt;https://github.com/BranLiang/octokiq/tree/main/examples/benchmark&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;运行 50 万个同样任务花费的时间&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;Sidekiq&lt;/th&gt;
&lt;th&gt;Octokiq(Thread)&lt;/th&gt;
&lt;th&gt;Octokiq(Ractor)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;86s&lt;/td&gt;
&lt;td&gt;75s&lt;/td&gt;
&lt;td&gt;42s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;参考文档：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@andresakata/background-job-processing-using-ractor-ruby-3-41c7956d14a0" rel="nofollow" target="_blank"&gt;https://medium.com/@andresakata/background-job-processing-using-ractor-ruby-3-41c7956d14a0&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.io/commands/blpop" rel="nofollow" target="_blank"&gt;https://redis.io/commands/blpop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ruby/ruby/blob/master/doc/ractor.md" rel="nofollow" target="_blank"&gt;https://github.com/ruby/ruby/blob/master/doc/ractor.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mperham/sidekiq" rel="nofollow" target="_blank"&gt;https://github.com/mperham/sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>BranLiang</author>
      <pubDate>Wed, 30 Dec 2020 09:39:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/40763</link>
      <guid>https://ruby-china.org/topics/40763</guid>
    </item>
    <item>
      <title>如何基于 ActionCable 搭建 webrtc 的服务</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2020/e55cf671-1868-4e10-8f7d-05d765d09bd9.gif!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;如果你对 webrtc 感兴趣，但是对于如何基于 rails 实现这个功能有点无从下手或者还有些迷惑的话，这篇教程也许会给予一定的帮助&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码地址：&lt;a href="https://github.com/BranLiang/demo-rails-webrtc" rel="nofollow" target="_blank"&gt;https://github.com/BranLiang/demo-rails-webrtc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;demo 体验链接：&lt;a href="https://demo-rails-webrtc.herokuapp.com/" rel="nofollow" target="_blank"&gt;https://demo-rails-webrtc.herokuapp.com/&lt;/a&gt;  （也支持 iphone 的 safari 和 android 浏览器）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="实现原理"&gt;实现原理&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://medium.com/@BranLiang/a-complete-guide-to-webrtc-with-ruby-on-rails-9ea68e67154e" rel="nofollow" target="_blank"&gt;https://medium.com/@BranLiang/a-complete-guide-to-webrtc-with-ruby-on-rails-9ea68e67154e&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;具体的实现原理我在上面的链接有详细的描述，这里简单说明一下背后的原理。既然使用了 webrtc 的接口，背后肯定是遵循的 webrtc 的规则，首先需要一个 signaling server，这相当于一个被所有人认可的一个中转机构，接收一个人的信息并传递给另外一个人，在这个 demo 项目里面，充当这个角色的就是 ActionCable，因为 ActionCable 能够比较方便的做到主动往客户端推送消息，当然你也可以选择其他的消息订阅服务所谓你的 signaling server，本身 webrtc 这个技术对于 signaling sever 没有做任何规范与限制。其次你需要一个客户端来与 signaling server 进行交互，在网页这个领域，应该除了 js 没有别的选择了，这里选择了 stimulus 框架来进行这个交互操作。有了上述元素之后就需要按照 webrtc 的流程一步步走下去，直到两台设备之间能够直接进行 P2P 的沟通。下面，就让我们一步步走下去，看看到底是如何建立一个简单的 webrtc 通信的。&lt;/p&gt;
&lt;h2 id="第一步：创建 RTCPeerConnection"&gt;第一步：创建 RTCPeerConnection&lt;/h2&gt;
&lt;p&gt;使用 webrtc 通信的第一步是处理话一个连接，每台设备都至少需要一个这么连接，这个连接会存储 &lt;code&gt;LocalDescription&lt;/code&gt; 和 &lt;code&gt;RemoteDescription&lt;/code&gt; 分别表示本地的配置以及连接的另一方的配置信息，同时还会存储对方连接的信息，一般称之为 &lt;code&gt;iceCandidates&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="c1"&gt;// STUN/TURN server的配置信息，demo中来自twilio提供的服务，属于必须配置的参数&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;初始化连接之后，你需要获取自身的配置，并通过 signaling server 传递到另外一个客户端，获取的方式是多种多样的，下面是其中一种，通过 &lt;code&gt;createOffer&lt;/code&gt; 这个接口获取本地的配置，示例代码如下：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOffer&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;offer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OFFER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sdp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;err&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;action cable 方面处理的逻辑非常简单，基本上除了 token，其余的请求都是传过来什么，就播送出去什么，最理想的状态是可以指定接收的订阅者，但是通过 action cable 并不能实现这一点，着实有些遗憾。&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;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"OFFER"&lt;/span&gt;
    &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第三步：对方收到offer请求，并回复"&gt;第三步：对方收到 offer 请求，并回复&lt;/h2&gt;
&lt;p&gt;通过上面的 action cable 数据播送，远在其他地方的客户端会接收到相应的请求消息，这时候它会保存对应的数据，并和请求方一样生成自身的 description，并通过 signaling server 传递出来，此时作为 offer 的接收方，已经获取了两边的配置数据。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&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;connected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rtcOffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCSessionDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&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;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRemoteDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rtcOffer&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;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAnswer&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;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ANSWER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;sdp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第三步：请求方获取配置信息"&gt;第三步：请求方获取配置信息&lt;/h2&gt;
&lt;p&gt;上面一步中，对方回复了配置消息，那么很自然的请求方就会处理这个消息并记录下来，到这一步结束，两个客户端都保存了自身以及对方的配置信息。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;receiveAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rtcAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCSessionDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&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;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRemoteDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rtcAnswer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第四步：加载对方的连接方式"&gt;第四步：加载对方的连接方式&lt;/h2&gt;
&lt;p&gt;理想的情况下，两边应该是可以正常开始通信了，但是如果你测试的话，发现并不能，因为虽然知道了对方的配置，但是如何访问对方仍然是未知的，这个需要通过第一步配置 servers 来来获取自身的连接信息（一般是 ip 等信息），然后通过 signaling server 传递给对方，这样对方就能够顺利的直接访问自己了！&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onicecandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;candidate&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CANDIDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sdp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&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;addCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rtcCandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCIceCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&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;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIceCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rtcCandidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="完成连接"&gt;完成连接&lt;/h2&gt;
&lt;p&gt;到这一步之后，两个客户端之间的连接就已经完成了。需要如何利用这个连接就全凭大家的发挥了！欢迎留言讨论&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;</description>
      <author>BranLiang</author>
      <pubDate>Thu, 30 Apr 2020 16:46:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/39820</link>
      <guid>https://ruby-china.org/topics/39820</guid>
    </item>
    <item>
      <title>芝麻认证 on Rails</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/478b8434-cbeb-4b43-89d4-dd36b3585be5.jpeg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;近期因为工作需要而接触了芝麻认证，因为官方并没有提供相关的 sdk 包，因此花了一天时间写了这么一个 gem 包给自己用，因为考虑到也许有别的同学也有相似得需求，因此就在这边把他发布出来，觉得有改进的地方欢迎大家指正，如果觉得有用的话，请不吝给个红心啊:D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/BranLiang/zhima_auth" rel="nofollow" target="_blank" title=""&gt;ZhimaAuth 链接&lt;/a&gt;&lt;/p&gt;</description>
      <author>BranLiang</author>
      <pubDate>Sat, 15 Apr 2017 16:13:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/32787</link>
      <guid>https://ruby-china.org/topics/32787</guid>
    </item>
    <item>
      <title>Angular JS 2 正式发布了</title>
      <description>&lt;p&gt;&lt;img src="https://lh6.googleusercontent.com/Eduq1SGmav17xp4hg91xMSt3DA1bS-zvZbo4TLwLf43Bu1XmIOSJyeb-H2HTeQEXHdTJvSVCMmuWXwZJpKwT_XmKpKEh-4x1eZgsmjRvu2YTKzPqSxn_XRkecD9rMqmOo0gMNybF" title="" alt="angularjs2"&gt;&lt;/p&gt;
&lt;h3 id="Angular JS 2 终于发布正式版了，有准备进入的同学吗？"&gt;Angular JS 2 终于发布正式版了，有准备进入的同学吗？&lt;/h3&gt;</description>
      <author>BranLiang</author>
      <pubDate>Thu, 15 Sep 2016 13:32:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/31077</link>
      <guid>https://ruby-china.org/topics/31077</guid>
    </item>
    <item>
      <title>GitHub 终于出项目管理功能了！</title>
      <description>&lt;p&gt;&lt;img src="https://pbs.twimg.com/profile_images/750390616916242432/i02NOlc1.jpg" title="" alt="github universe"&gt;&lt;/p&gt;

&lt;p&gt;GIthub Universe 2016 发布了一系列新功能及改进。大家可以看一下。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;更加强大的评论系统&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;内置的项目管理&lt;br&gt;
及许多。。。&lt;br&gt;
貌似 2017 年还会推出社区论坛功能，这个非常期待啊。&lt;br&gt;
&lt;a href="https://github.com/universe-2016" rel="nofollow" target="_blank" title=""&gt;链接&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>BranLiang</author>
      <pubDate>Thu, 15 Sep 2016 09:13:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/31076</link>
      <guid>https://ruby-china.org/topics/31076</guid>
    </item>
    <item>
      <title>这两天的一个练习项目 - 迷你 Facebook (Update 09-24)</title>
      <description>&lt;h4 id="Danebook"&gt;Danebook&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/2189298e0263a5055d85a4edba2d0d16.jpg!large" title="" alt="danebook"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;一个简单的社交网站平台，这几天学习练习 Ruby on Rails 所用。已经在 Heroku 发布了，各位可以试一试。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Heroku 链接"&gt;&lt;a href="https://cool-danebook.herokuapp.com/" rel="nofollow" target="_blank" title=""&gt;Heroku 链接&lt;/a&gt;&lt;/h2&gt;&lt;h3 id="Github Repository 链接"&gt;&lt;a href="https://github.com/BranLiang/project_danebook" rel="nofollow" target="_blank" title=""&gt;Github Repository 链接&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;额外彩蛋：如果使用你自己的真实邮箱注册的话，还能收到一封 Awesome 欢迎信，只此一封，你永远不会再收到任何与这个软件有关的任何邮件，我发誓！&lt;/p&gt;
&lt;h4 id="Key features"&gt;Key features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;基于 Devise 的认证注册系统&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;Sendgrid 的邮件后台发送&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;Responsive bootstrap front-end 没有花太多精力在前端，只有简单基础的 Bootstrap 构建以及部分 Font awesome 的图标，但是经手工测试还是对移动端非常友好的 &lt;/li&gt;
&lt;li&gt;基于 Amazon s3 + paperclip 的图片存储，管理，你可以上传或者使用在线图片更新你的头像或者 Cover photo&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;配置了及其简陋的全站用户搜索功能，基本就是一行 SQL 语言就解决了的搜索&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;用户之间构建了 self-association 关联，你可以添加别的用户为好友，或者去除其好友关系&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;喜欢，和评论都是 polymorphic 的，因此软件中的 Post 和图片都可以享有评论和喜欢它&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;专门的 news feed 界面，用户可以查看好友的最近状态和所有好友的 posts&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;基本去除了 N+1 的 bug&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;有一定的测试，虽然覆盖面还是很小...&lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;软件的问题还是很多的，其中最致命的是用户只要登录后其实是可是删除任何其他用户评论或 post 的（虽然页面不会显示按钮），因为 devise 只有认证功能，并不支持授权，需要布置 Cancancan 才行。&lt;/p&gt;

&lt;p&gt;TODO(如果有时间的话): &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;前端 Javascript 化&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;配置 Cancanacan&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Update (2016-09-24)"&gt;Update (2016-09-24)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;post 和 comment 部分添加了 ajax，从此评论和 post 不用再刷新页面&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;添加新图片时不需要在转页面，取而代之的是 lightbox&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="欢迎各位大牛指点，评价"&gt;欢迎各位大牛指点，评价&lt;/h2&gt;&lt;h4 id="上一期作品Viking Surveyor"&gt;上一期作品&lt;a href="https://ruby-china.org/topics/30970" title=""&gt;Viking Surveyor&lt;/a&gt;
&lt;/h4&gt;</description>
      <author>BranLiang</author>
      <pubDate>Mon, 12 Sep 2016 13:03:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/31043</link>
      <guid>https://ruby-china.org/topics/31043</guid>
    </item>
    <item>
      <title>推荐一本书 Learn Enough Action Cable To Be Dangerous</title>
      <description>&lt;h4 id="学习action cable的用书，推荐给各位有需求的Rubist"&gt;学习 action cable 的用书，推荐给各位有需求的 Rubist&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;这本书应该是刚刚才面世，还属于草稿版本，但作者是大名鼎鼎的 Ruby on Rails tutorail 的作者 Michael Hartl，因此此书的质量应该是毋庸置疑的，适用于学习了一段时间之后的 Ruby on Rails 新手，同时一定的 JavaScript 知识也对理解本书有很大的在作用（虽然作者说 JavaScrip 不是必须的，但你信吗？我是不相信），还没来的及看，各位有看过的可以来写个评价什么的。
&lt;img src="https://softcover.s3.amazonaws.com/636/learn_enough_action_cable/images/cover-web.png" title="" alt="cover"&gt;&lt;br&gt;
&lt;a href="https://www.learnenough.com/action-cable-tutorial" rel="nofollow" target="_blank" title=""&gt;这里是链接地址&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>BranLiang</author>
      <pubDate>Sun, 11 Sep 2016 10:17:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/31036</link>
      <guid>https://ruby-china.org/topics/31036</guid>
    </item>
    <item>
      <title>一个简单在线问卷制作及回答及分析 app - Viking Surveyor</title>
      <description>&lt;h2 id="在线问卷app"&gt;在线问卷 app&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;最近学 ROR 的一道表单练习题，新鲜出炉，分享给各位看看（mobile 体验更佳），图个乐&lt;img title=":stuck_out_tongue_winking_eye:" alt="😜" src="https://twemoji.ruby-china.com/2/svg/1f61c.svg" class="twemoji"&gt;  如果对这个小应用感兴趣，这是&lt;a href="https://github.com/BranLiang/project_surveyor" rel="nofollow" target="_blank" title=""&gt;Git 链接地址&lt;/a&gt;，下面是 Heoku 地址，欢迎试用，评论&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="Heroku链接地址"&gt;&lt;a href="http://viking-surveyor.herokuapp.com/" rel="nofollow" target="_blank" title=""&gt;Heroku 链接地址&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;简单的一个问卷网站，你可以制作多选问题，单选问题以及数字范围问题这三种类型的问卷。
问卷制作之后可以在线回答，同时回答之后可以分析问卷的回答结果。&lt;br&gt;
Key feature  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nested form with fields_for&lt;/code&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;多问题可选&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;每个选项都有统计&lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bug  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果必选题没有回答，重新&lt;code&gt;render&lt;/code&gt;时出现选项被复制（原因暂未查明，如果你知道可能的原因请一定告诉我）&lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/b48538b849ab97be8e558526403a39e7.png!large" title="" alt="surveyor"&gt;&lt;/p&gt;</description>
      <author>BranLiang</author>
      <pubDate>Thu, 01 Sep 2016 23:58:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/30970</link>
      <guid>https://ruby-china.org/topics/30970</guid>
    </item>
    <item>
      <title>Could not find the source association (s)</title>
      <description>&lt;h2 id="[已解决]Could not find the source association(s)"&gt;[已解决]Could not find the source association(s)&lt;/h2&gt;&lt;h3 id="Sorry for the long post, I didn't mean that."&gt;Sorry for the long post, I didn't mean that.&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这两天正在做一个在线调查问卷的 ROR 习题。这是其中遇到的一个让我困惑不解的问题，先简单说明一下问题所对应的数据结构，文字说明不如直接上代码。  &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"join_options"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;  &lt;span class="s2"&gt;"multi_response_id"&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;  &lt;span class="s2"&gt;"response_option_id"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;create_table "multi_responses", force: :cascade do |t|
    t.integer  "respondent_id"
    t.integer  "question_id"
    t.text     "answer"
  end&lt;/p&gt;

&lt;p&gt;create_table "questions", force: :cascade do |t|
    t.text     "text"
    t.integer  "survey_id"
    t.integer  "options"
    t.boolean  "multi_select"
    t.boolean  "required"
  end&lt;/p&gt;

&lt;p&gt;create_table "respondents", force: :cascade do |t|
    t.string   "name"
    t.integer  "survey_id"
  end&lt;/p&gt;

&lt;p&gt;create_table "response_options", force: :cascade do |t|
    t.integer  "question_id", null: false
    t.text     "text",        null: false
  end&lt;/p&gt;

&lt;p&gt;create_table "surveys", force: :cascade do |t|
    t.string   "title",       null: false
    t.text     "description", null: false
  end&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;gt; 我的目的是希望一次提交创建Respondent，MultiResponse(以及之后的Range表单)。为了达到这个目的，我想到了两个方案，一个方案是只创建一个单独的`answer`列而舍弃`JoinOption`这个关系连（或者数据保存之后再创建关系），但这要求我保存一个`Array`到数据结构中，这是我不愿看到的.（可能的数据提交如下）
```ruby
"respondent"=&amp;gt;
  {"survey_id"=&amp;gt;"1",
   "name"=&amp;gt;"",
   "multi_responses_attributes"=&amp;gt;
    {"0"=&amp;gt;{"question_id"=&amp;gt;"2", "answer"=&amp;gt;["", "3", "4"]},
     "1"=&amp;gt;{"question_id"=&amp;gt;"4", "answer"=&amp;gt;"13"},
     "2"=&amp;gt;{"question_id"=&amp;gt;"6", "answer"=&amp;gt;"20"},
     "3"=&amp;gt;{"question_id"=&amp;gt;"9", "answer"=&amp;gt;["", "30", "31", "32"]}}},
 "commit"=&amp;gt;"Submit your Survey"}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;问题的关键是我根本就无法顺利保存这个&lt;code&gt;array&lt;/code&gt;。顺便说一下，我用的是&lt;code&gt;postgresql&lt;/code&gt;做的开发环境，&lt;code&gt;serialize&lt;/code&gt;和在&lt;code&gt;migration&lt;/code&gt;里添加&lt;code&gt;array： true， default： []&lt;/code&gt;等办法都试过了，都没有用。于是我决定采取另一种即现在这种
直接建立&lt;code&gt;MultiResponse&lt;/code&gt;和&lt;code&gt;ResponseOption&lt;/code&gt;关系的办法。于是表单的形式就如下所见&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_for&lt;/span&gt; &lt;span class="vi"&gt;@respondent&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:survey_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="vi"&gt;@survey.id&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- Multiple selection questions --&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;multi_question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multi_select?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:multi_responses&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;multi_response&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;multi_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:question_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;multi_question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;multi_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_check_boxes&lt;/span&gt; &lt;span class="ss"&gt;:response_option_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multi_question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"checkbox"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- Single selection questions --&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:multi_responses&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;multi_response&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;multi_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:question_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;multi_question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;multi_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_radio_buttons&lt;/span&gt; &lt;span class="ss"&gt;:answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multi_question&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"radio radio-inline"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Submit your Survey"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上个图也许能对这个表格看的更清楚点（以上表单简略了许多）
&lt;img src="https://l.ruby-china.com/photo/2016/48b651ec2e6e77d7490f8cd6a7968765.png!large" title="" alt="tu"&gt;&lt;/p&gt;

&lt;p&gt;关于这个办法的&lt;code&gt;radio button&lt;/code&gt;数据如何保存，也还没头绪就遇到了标题所说的问题。 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Could not find the source association(s) "response_option" or :response_options in model JoinOption. Try 'has_many :response_options, :through =&amp;gt; :join_options, :source =&amp;gt; &amp;lt;name&amp;gt;'. Is it one of ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我的&lt;code&gt;model&lt;/code&gt;关系&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JoinOption&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:multi_response&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:response_option&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;class MultiResponse &amp;lt; ApplicationRecord
  belongs_to :respondent, optional: true
  belongs_to :question&lt;/p&gt;

&lt;p&gt;has_many :join_options
  has_many :response_options, through: :join_options
end&lt;/p&gt;

&lt;p&gt;class ResponseOption &amp;lt; ApplicationRecord
  belongs_to :question&lt;/p&gt;

&lt;p&gt;has_many :join_options
  has_many :multi_responses, through: :join_options
end&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;如果有不清楚的可以留言或者直接去我的[Github链接地址](https://github.com/BranLiang/project_surveyor/tree/withjointable)去看。
总结一下我遇到主要问题。
 - 为什么这个简单`many to many`关系建立不成功
 - 如何才能向`posgresql`里面保存`array`
 - 是否有更好的办法完成该任务即同时保存`checkbox`和`radio`的数据
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>BranLiang</author>
      <pubDate>Wed, 31 Aug 2016 20:44:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/30956</link>
      <guid>https://ruby-china.org/topics/30956</guid>
    </item>
  </channel>
</rss>
