<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>xinyifly (Zeyu Chen)</title>
    <link>https://ruby-china.org/xinyifly</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Ruby 3 的 Ractor 是轻量的吗？</title>
      <description>&lt;p&gt;是否能像 Erlang &lt;code&gt;spawn()&lt;/code&gt; 一样无需操心地对每一个现实中的并发模型启动成千上万个 &lt;code&gt;process&lt;/code&gt; ？&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.ruby-lang.org/en/3.0.0/Ractor.html#class-Ractor-label-Ractors+vs+threads" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt; 提到对于每个 &lt;code&gt;Ractor&lt;/code&gt; 会创建一个 &lt;code&gt;Thread&lt;/code&gt; ，想知道 VM 有没有对 Ractor 做相关优化。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;发现我的需求跟 Fiber 的描述更像一点，但是我的场景有物联网 TCP 通信，这一块想用 send/receive message 实现。&lt;/p&gt;</description>
      <author>xinyifly</author>
      <pubDate>Mon, 01 Nov 2021 20:23:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/41829</link>
      <guid>https://ruby-china.org/topics/41829</guid>
    </item>
    <item>
      <title>一次修改外键的带数据更新的 migration</title>
      <description>&lt;h3 id="背景"&gt;背景&lt;/h3&gt;
&lt;p&gt;项目有时会遇到数据库关系设计不当的情况，如果局限于“开闭原则”的话就发现本该合理的业务在遗留项目中很难实现，扔给产品一句“由于历史原因，这个做不了”。这里分析一个开发过程中的简单 migration 案例，目前已经在使用 &lt;code&gt;execute sql&lt;/code&gt; 了。&lt;/p&gt;
&lt;h3 id="项目"&gt;项目&lt;/h3&gt;
&lt;p&gt;这是一个投票项目，一共两张表：users 和 votes&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="ss"&gt;:users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:votes&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;references&lt;/span&gt; &lt;span class="ss"&gt;:candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :users&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="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :users&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次用户投票 votes 表增加一条记录，candidate 和 voter 分别是一个用户，直到。。。&lt;/p&gt;
&lt;h3 id="新需求"&gt;新需求&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“我们要能创建很多投票活动，为第三方提供投票服务”&lt;/li&gt;
&lt;li&gt;“后台可以添加哪些用户是哪个活动的 candidate”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="对策1"&gt;对策 1&lt;/h3&gt;
&lt;p&gt;创建 polls 表，把 poll_id 添加到 votes 表中：&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="ss"&gt;:polls&lt;/span&gt;
&lt;span class="n"&gt;add_reference&lt;/span&gt; &lt;span class="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:poll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 polls 表到 users 表的多对多关系&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create_join_table :polls, :users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/8d351559-71c7-4108-85f1-3bad3156af3b.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="对策2"&gt;对策 2&lt;/h3&gt;
&lt;p&gt;在对策 1 的思路上，我们可以把 polls 和 users 的多对多关系实体化为一个新的 candidates，（此前 candidate_id 对应的是 users 表），让 votes 表记录这个新的 candidate 实体而不是 user 实体。这样对策 1 就变成了：&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="ss"&gt;:polls&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:candidates&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;references&lt;/span&gt; &lt;span class="ss"&gt;:users&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;references&lt;/span&gt; &lt;span class="ss"&gt;:polls&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;remove_reference&lt;/span&gt; &lt;span class="ss"&gt;:votes&lt;/span&gt; &lt;span class="ss"&gt;:candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :users&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;add_reference&lt;/span&gt; &lt;span class="ss"&gt;:votes&lt;/span&gt; &lt;span class="ss"&gt;:candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/4a99ac93-34a5-400a-87dc-2ebea08cdc18.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;注意到这样 votes 表的外键由 3 个变成了 2 个，并且这样方便对 candidate 作 counter_cache&lt;/p&gt;
&lt;h3 id="数据迁移"&gt;数据迁移&lt;/h3&gt;
&lt;p&gt;直接 remove_reference、add_reference 会丢失生产环境中已有 votes 的 candidate 信息，需要：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;将 users 表中的记录导入到 candidates 表中，poll_id 设为 nil&lt;/li&gt;
&lt;li&gt;将 votes 表中的 candidate_id 从实际引用的 users 表的 id 更新到 candidates 表的 id&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;使用 &lt;code&gt;execute sql&lt;/code&gt; 进行数据迁移（使用了 PostgreSQL 的 Window 函数）&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="ss"&gt;:polls&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:candidates&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;references&lt;/span&gt; &lt;span class="ss"&gt;:users&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;references&lt;/span&gt; &lt;span class="ss"&gt;:polls&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;revert&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;add_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;column: :candidate_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;reversible&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;dir&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;up&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      INSERT INTO candidates (user_id, created_at, updated_at)
        SELECT DISTINCT candidate_id, min(created_at) OVER (PARTITION BY candidate_id), min(created_at) OVER (PARTITION BY candidate_id)
          FROM votes;
      UPDATE votes SET candidate_id = (SELECT id FROM candidates WHERE user_id = candidate_id);
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;down&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      UPDATE votes SET candidate_id = (SELECT user_id FROM candidates WHERE id = candidate_id);
&lt;/span&gt;&lt;span class="no"&gt;    SQL&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;add_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:candidates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="原问题已更新，最后感谢各位对问题的回复"&gt;原问题已更新，最后感谢各位对问题的回复&lt;/h3&gt;</description>
      <author>xinyifly</author>
      <pubDate>Sat, 01 Jun 2019 16:51:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/38596</link>
      <guid>https://ruby-china.org/topics/38596</guid>
    </item>
    <item>
      <title>Migration 新增列并设置值，如何组织更新步骤？</title>
      <description>&lt;p&gt;比如 migration 之前只有 posts 表，字段只有 body。&lt;/p&gt;

&lt;p&gt;现在添加 users 表，并注册了一个 user。&lt;/p&gt;

&lt;p&gt;然后想给 posts 表添加 user_id，全部更新为这个唯一的 user。&lt;/p&gt;

&lt;p&gt;最终要在 db 层面去掉 default value 和 nullable。&lt;/p&gt;

&lt;p&gt;更麻烦一点的场景：测试环境和生产环境这个 user 的 id 不一样。&lt;/p&gt;

&lt;p&gt;怎么组织更新步骤？需要几个 migration？是否（何时）需要在 migration 之外手动更新数据表？&lt;/p&gt;
&lt;h3 id="5 月 31 日更新"&gt;5 月 31 日更新&lt;/h3&gt;
&lt;p&gt;发现 rails migration 的 references 类型默认就是 nullable，非空检察由 rails 框架完成（belongs_to），不需要添加“在 db 层面去掉 default value 和 nullable”这一步。&lt;/p&gt;</description>
      <author>xinyifly</author>
      <pubDate>Mon, 27 May 2019 16:37:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/38575</link>
      <guid>https://ruby-china.org/topics/38575</guid>
    </item>
  </channel>
</rss>
