Ruby, Railsでハッシュで初期化する(カイゼン版)

photo credit: gremionis via photopin cc

以前、ハッシュで初期化時にABCサイズを小さくするで、以下のように実装した。

class User
  attr_accessor :id, :name, …(略)…, :email
 
  def initialize(hash)
    hash.each do | key, value |
      send("#{key}=", value)
    end
  end
end

上記でPull/Merge Requestしたところ、指摘をもらってもっと良い方法にカイゼンできたので,記しておく。

レビューア「これだと setter が生えてない key が含まれたときに問題あるのでは?」
ちぇんわ「はい。
そのため、インスタンス変数に未定義の値が投げられることがあるのか確認しました。あるとのことなので、現在の修正だとエラーになります。
別のカイゼン方法を考えます」

で、以下のように変えた。

class User
  attr_accessor :id, :name, …(略)…, :email
 
  def initialize(hash)
    begin
      hash.each do | key, value |
        send("#{key}=", value)
      rescue NoMethodError
        # If no such parameter exists, don't do anything.
      end
    end
  end
end
ちぇんわ「インスタンス変数に未定義の値が投げられた場合は握りつぶすようにしました」
レビューア「全部やってみて例外握りつぶすんじゃなくて setter のリストを取ってくるほうがいいと思います。
あと public_send で。
http://stackoverflow.com/questions/1676200/listing-the-accessors-in-a-ruby-class」

で、教えてもらったStackOverFlowで一番推奨されている方法で対応することにした。

class User
  attr_accessor :id, :name, …(略)…, :email
 
  def initialize(hash)
    hash.each do | key, value |
      send("#{key}=", value) if respond_to?("#{key}=")
    end
  end
end
ちぇんわ「どうもすいませんでした。
http://stackoverflow.com/questions/1676200/listing-the-accessors-in-a-ruby-class
↑こちらで一番推奨されている方法で対応しました」
レビューア「本当は public_send がいいと思うけど瑣末な点なので merge します。」

最後の指摘が気になったので、調べたところ、

  • public_send:private methodは呼べない
  • send = __send__:private methodも呼べる

という差があることが分かった。今回setterを呼ぶだけなので、public_sendを使った方が好ましかったようだ。というわけで、以下が最終形。

class User
  attr_accessor :id, :name, …(略)…, :email
 
  def initialize(hash)
    hash.each do | key, value |
      public_send("#{key}=", value) if respond_to?("#{key}=")
    end
  end
end