下級エンジニアの綴

新しく発見したことを綴っていこうと思っています。夢はでっかく上級エンジニアになることです。

railsのhash#tryでちょっと躓いたのでメモ

railsでhashにtryをかけているとエラーが出たので、その時に調べた内容をメモ。(一度先輩に教えてもらった気がするのですが完全に忘れてましたorz

本題

この書き方だとhashにkeyが存在する時は問題無いのですが、

{ name: 'yantera' }.try(:fetch, :name)
# => "yantera"

hashにkeyが存在しない時はerrorになります

{}.try(:fetch, :name)
# KeyError: key not found: :name
# from /usr/local/bundle/gems/activesupport-4.1.8/lib/active_support/core_ext/object/try.rb:45:in `fetch'

解決するには以下の2パターンがありました。

  • 自分でdefault値を設定する
    • 同時に好きな値を定義できる
  • []メソッドを設定する
    • メリットはシンプルに書ける
{}.try(:fetch, :name, nil)
{}.try(:fetch, :name, 1)
{}.try(:fetch, :name, 'yantera')
# => nil
# => 1
# => "yantera"

{}.try(:[], :name)
# => nil

参考リンク

qiita.com

小ネタ

  • tryの中身はこのようになってます
    • aにはtryを呼ぶときの引数が配列で入って、bにはブロック文が入ります。
def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

要は下のように呼ぶと

{}.try(:fetch, :test, 1) do
  p 'test'
end
# a
# => [:fetch, :test, 1]
# b
# => #<Proc:0x007f5b9bf15f88@(pry):2>

が入ります。

  • if文を見るとaがemptyでかつブロック文が投げられている時、自分で作成したブロックぶんが呼ばれるという内容ですね。
{}.try() do
  p 'test'
end
# => 'test'

というように使えます。(用途はわからないですが)

続いてelseの方ですが

def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

self(Hash)にa.firstのメソッドがあれば実行されるので

{}.try(:fetch, :test, 1) do
  p 'test'
end
# a
# => [:fetch, :test, 1]

この場合fetchが呼ばれます。fetchメソッドははhashのkeyをとって実行するので、

{}.fetch(:test, 1)
# => 1
{}.fetch(:test)
# KeyError: key not found: :test

の形になり、hashの中にkeyが存在していると、そのkeyの値が返りますが、デフォルト値を定義していないとKeyErrorになります。

[]はhashからkeyを取るメソッドなので

{}.[] :test
# => nil

という風にかけます。

また、

hoge = {}
hoge.[] :test
# => nil

hoge.default = 1
hoge.[] :test
# => 1

hashにdefault値を設定できるみたいで、指定しなければnilが返りますが、指定すると好きな値に出来るみたいでした。