Rails における hash のキーの扱いについての tips

rails logo

みなさん、こんにちは kota です。
日頃、業務では Rails を使っているのですが、先日 hash のキーの扱いについて、再認識したことがあったので、簡単に共有したいと思います。

困ったこと

  • model からデータを所得し、変数に入れ、その変数に対し attributes メソッドを使って hash 化した。
  • hash 化した変数から name の値を取得しようとuser[:name]でアクセスするも、nil が返って来て取得できなかった。
[1] pry(main)> user = User.first
  User Load (13.8ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 1, name: "hoge", created_at: "2022-09-30 08:24:03", updated_at: "2022-09-30 08:24:03">

[2] pry(main)> user = user.attributes
=> {"id"=>1,
 "name"=>"hoge",
 "created_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 "updated_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00}

[2] pry(main)> user[:name]
=> nil

調査

先述した通り model から取得したデータに対して、attributes メソッドを使った際の返り値は下記の通りでした。
ここで、キーが文字列になっていることに気付きました。

[2] pry(main)> user = user.attributes
=> {"id"=>1,
 "name"=>"hoge",
 "created_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 "updated_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00}

当たり前ですが、値を取得するには定義されている形式と同じ形式でアクセスしないと取得できないことを改めて認識しました。

[4] pry(main)> user["name"]
=> "hoge"

params の値を取得する際などにシンボルを使うことが多かった為、少々ハマってしまったというお話でした。

追加調査・メソッドまとめ

いい機会なので hash についてもう少し調べ、使えそうなメソッドをまとめました。

キーを文字列からシンボルに変換

[1] pry(main)> user.symbolize_keys
=> {:id=>1,
 :name=>"hoge",
 :created_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 :updated_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00}

キーをシンボルから文字列に変換

  • stringify_keys
    • Rails が用意しているメソッド。内部的には、ruby の transform_keys(&:to_s) メソッドを使用している。
    • 参考: https://api.rubyonrails.org/classes/Hash.html#method-i-stringify_keys
      [1] pry(main)> user.stringify_keys
      => {"id"=>1,
      "name"=>"admin",
      "created_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
      "updated_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00}

hash のネストも変換したい

上記2つのメソッドの注意点として、hash 内にネストがあると、そのネストは変換されません。

[1] pry(main)> user
=> {"id"=>1,
 "name"=>"hoge",
 "created_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 "updated_at"=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 "group"=>{"id"=>1, "group_name"=>"fuga"}}

[2] pry(main)> user.symbolize_keys
=> {:id=>1,
 :name=>"hoge",
 :created_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 :updated_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 :group=>{"id"=>1, "group_name"=>"fuga"}} <- シンボルに変換されてない

ネストも変換したい場合は、deep_symbolize_keys (deep_stringify_keys) を使うことで、変換が可能です。

[3] pry(main)> user.deep_symbolize_keys
=> {:id=>1,
 :name=>"hoge",
 :created_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 :updated_at=>Fri, 30 Sep 2022 17:24:03 JST +09:00,
 :group=>{:id=>1, :group_name=>"fuga"}}

※ ruby のメソッド transform_keys も同様に deep_transform_keys で、ネストの変換も可能

文字列でもシンボルでも値を取得したい

  • with_indifferent_access

先ほどまで使っていた変数 user の class を確認します。

[1] pry(main)> user.class
=> Hash

ここで、with_indifferent_access というメソッドを使い class を ActiveSupport::HashWithIndifferentAccess に変更してみます。

[1] pry(main)> user = user.with_indifferent_access

...

[2] pry(main)> user.class
=> ActiveSupport::HashWithIndifferentAccess

文字列とシンボルの両方で値を取得してみます。

[3] pry(main)> user[:name]
=> "hoge"
[4] pry(main)> user["name"]
=> "hoge"

文字列、シンボルの両方で値を取得することが出来ました。
params もこの ActiveSupport::HashWithIndifferentAccess class を継承していて、文字列、シンボルの両方で値を取得することが出来ます。

最後に

いかがだったでしょうか?
今回は、Rails での hash のキーの扱いや関連するメソッドについて、簡単にまとめてみました。
hash に関わる便利なメソッドは多々ありますので、ご自身でも調べてみて下さい。