<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>zhangbin-github (张斌)</title>
    <link>https://ruby-china.org/zhangbin-github</link>
    <description>从小不学好，长大搞电脑</description>
    <language>en-us</language>
    <item>
      <title>Active Record 中 many to many 遇到的问题记录</title>
      <description>&lt;p&gt;rails-guides 中有关于 many to many 的具体文档 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://ruby-china.github.io/rails-guides/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many" rel="nofollow" target="_blank"&gt;https://ruby-china.github.io/rails-guides/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我最近有一个需求，就是每次更新以后，需要保留一个 history。&lt;/p&gt;

&lt;p&gt;举例：&lt;/p&gt;

&lt;p&gt;有一个 model：User，一个 model：UserHistory，还有 model：Role，User 和 Role 是多对多的关联。&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users_role&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:roles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :users_role&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;Role&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users_role&lt;/span&gt;
  &lt;span class="n"&gt;has_many&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;through: :users_role&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 有一列role_ids,将当时用户关联的role id以字符串的形式保存&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserHistory&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次更新 user 时，需要将 user 对象的记录同步到 user_history 中，我采用的做法时，在 user 中增加一个 after_save 的 callback。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in user.rb&lt;/span&gt;

  &lt;span class="ss"&gt;after_save: &lt;/span&gt;&lt;span class="n"&gt;backup_to_history&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backup_to_history&lt;/span&gt;
    &lt;span class="no"&gt;UserHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;role_ids: &lt;/span&gt;&lt;span class="n"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里在 console 中测试的时候发现一个问题。&lt;/p&gt;

&lt;p&gt;如果采用 &amp;lt;&amp;lt; append 数组，是无法保存 role_ids 的&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为&amp;lt;&amp;lt;是一个独立的操作，从方法定义来看，也是 append 的意思，并不会删除原先已经存在的 role_ids。
如果需要更新 user 的 roles，必须采用 update 的方法&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;role_ids: &lt;/span&gt;&lt;span class="no"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pluck&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，不仅能够修改原先用户的 roles，还能在 call back 中保存住 role_ids。&lt;/p&gt;</description>
      <author>zhangbin-github</author>
      <pubDate>Thu, 18 Jan 2018 09:34:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/34904</link>
      <guid>https://ruby-china.org/topics/34904</guid>
    </item>
    <item>
      <title>验证支付宝回调时踩过的坑</title>
      <description>&lt;p&gt;这几天重写以前遗留的我们对接支付宝的支付服务，在做到 rsa 签名验证的时候遇到一个小坑。&lt;/p&gt;

&lt;p&gt;我首先获取 controller 的 request params，然后用过一个方法，将 params hash 转换成 string，然后使用 public key 进行 rsa 验签，整个过程，并没有什么高科技，在 console 里面也一切 ok。
部署线上以后，就发现签名怎么也过不了&lt;/p&gt;

&lt;p&gt;原因是 rails 的 request params 里面会添加一些额外的参数进去，例如 controller，action，format 等。&lt;/p&gt;

&lt;p&gt;参考：
 &lt;a href="https://stackoverflow.com/questions/7467901/getting-the-rails-params-without-the-defaults" rel="nofollow" target="_blank"&gt;https://stackoverflow.com/questions/7467901/getting-the-rails-params-without-the-defaults&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rails 源码
&lt;a href="https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameters.rb" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameters.rb&lt;/a&gt; 第 48 行 parameters 方法&lt;/p&gt;

&lt;p&gt;所以正确的姿势应该是：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sign_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;except&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path_parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;更好的写法&lt;/span&gt;&lt;span class="err"&gt;：&lt;/span&gt;

&lt;span class="n"&gt;sign_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request_parameters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取需要参与签名的参数&lt;/p&gt;</description>
      <author>zhangbin-github</author>
      <pubDate>Fri, 14 Jul 2017 11:18:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/33500</link>
      <guid>https://ruby-china.org/topics/33500</guid>
    </item>
    <item>
      <title>Rails 5.1 add delegate_missing_to</title>
      <description>&lt;p&gt;&lt;a href="http://blog.bigbinary.com/2017/05/30/rails-5-1-adds-delegate-missing-to.html" rel="nofollow" target="_blank"&gt;http://blog.bigbinary.com/2017/05/30/rails-5-1-adds-delegate-missing-to.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rails 5.1 添加了 delegate_missing_to 方法&lt;/p&gt;

&lt;p&gt;实验如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g model user usename:string password:string email:string
rails g model book title:string user:references

rails  db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成如下 model&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:books&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;Book&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;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 console 中实验&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User.create(username:'test', password:'123456', email:'t@test.com')
Book.create(user:User.last, title:'this is a book')
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):017:0&amp;gt; Book.last.user
  Book Load (0.3ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
  User Load (0.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=&amp;gt; #&amp;lt;User id: 1, username: "test", password: "123456", email: "test@test.com", created_at: "2017-06-02 01:55:54", updated_at: "2017-06-02 01:55:54"&amp;gt;

irb(main):018:0&amp;gt; Book.last.username
  Book Load (0.4ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
NoMethodError: undefined method `username' for #&amp;lt;Book &amp;gt;
    from (irb):18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到直接访问 username 会报 method missing&lt;/p&gt;

&lt;p&gt;在 book 中添加 delegate_missing_to&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;Book&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;:user&lt;/span&gt;
  &lt;span class="n"&gt;delegate_missing_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;try again&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):020:0&amp;gt; Book.last.username
  Book Load (0.2ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=&amp;gt; "test"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看实现
pr:  &lt;a href="https://github.com/rails/rails/pull/23930/files#r64312481" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/pull/23930/files#r64312481&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The target can be anything callable withing the object. E.g. instance&lt;/span&gt;
&lt;span class="c1"&gt;# variables, methods, constants ant the likes.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delegate_missing_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"self.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;DELEGATION_RESERVED_METHOD_NAMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;module_eval&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__LINE__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;
    def respond_to_missing?(name, include_private = false)
      &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.respond_to?(name, include_private)
    end
    def method_missing(method, *args, &amp;amp;block)
      &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.send(method, *args, &amp;amp;block)
    end
&lt;/span&gt;&lt;span class="no"&gt;  RUBY&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好处：1：可以少写一点代码，在比较多的 delegate 指向同一个 asocciation 的时候；2：可以向其他非动态语言炫技&lt;/p&gt;</description>
      <author>zhangbin-github</author>
      <pubDate>Fri, 02 Jun 2017 10:18:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/33119</link>
      <guid>https://ruby-china.org/topics/33119</guid>
    </item>
  </channel>
</rss>
