【Rails】active_hashの使い方を丁寧に解説します!

Railsで開発する中で、データベースに保存するほど重要ではなく、またそれほど変更/増減する予定もないデータってよくありますよね。

そのようなデータをあたかもデータベースへ保存しているかのように扱わせてくれるのが「active_hash」になります。

今回はそんな「active_hash」の役割や導入方法、そして主な使い方について解説させていただきますね。

非常に便利で且つ使い方もとてもシンプルで簡単ですので、機会があれば積極的に使っていきましょう。


レバテックフリーランス … 業界最大級の案件数業界トップクラスの高単価報酬を誇る最大手のサービスです。実績豊富なコーディネーターが丁寧な対応をしてくれるため、案件の無理な提案はありません。フリーランスで生きていくためにはまず登録しておきましょう。
MidWorks … フリーランス賠償責任保障生命保険の折半など、フリーランスでありながらも正社員並みの保障を受けられるのが特徴です。また、経験豊富なキャリアコンサルタントによる手厚いサポートも受けられるため、安定したフリーランス生活を送りたい方には特におすすめのサービスです。
ポテパンフリーランス … IT業界・技術に詳しいコンサルタントが担当してくれるため、こちらの要望をきちんと理解した上で案件を紹介してくれます。また、案件情報のみならず、フリーランスのイロハについても教えてくれるため、フリーランスとして初めて活動される方には特におすすめのサービスです。

レバテックキャリア … ITエンジニアが利用したい転職エージェントNo.1にも選ばれており、年収600万円以上のハイクラス求人を5,000件以上も保有しています。エンジニアが転職を考えた時にまず初めに登録しておくべきサービスです。
Tech Stars Agent … Tech Stars Agentでは、担当エージェントが全員エンジニア出身のため、スキルやキャリアを見据えたきめ細かな転職支援が受けられます。運営元の株式会社Branding Engineerは、独立支援サービス「MidWorks」も展開しているため、独立を視野に入れたサポートも受けられます
転職ドラフト … 年収UP率93.8%/平均年収UP額126万円と圧倒的な年収UP率を誇るイベント型のエンジニア向け 転職サービスです。毎月1回開催され、厳選された優良IT/Web系企業約150社からダイレクトスカウトを受け取ることができます。年収アップを目指す方は登録必須です。

active_hashの役割/特徴

上述した通り、active_hashの主な役割は、データベースに保存するほどでもないデータをあたかもデータベースに保存しているかのように扱わせてくれることにあります。

ではどんな時にこのactive_hashを導入すればよいのでしょうか?

筆者は主に以下の3つの条件を満たした場合にはactive_hashを導入してよいのではないかと考えています。

  • 1. データベースに保存しておくほど重要なデータではない
  • 2. 性質上、それほど頻繁に変更/増減されるデータではない
  • 3. ActiveRecordと同じ感覚でデータを扱いたい

このような条件を満たした場合にはactive_hashを導入してしまいましょう。

active_hashを使う際には、扱いたいデータをハッシュとしてモデルに定義することで、そのデータがあたかもデータベース内に保存されているものであるかのように扱うことができるようになります。

イメージとしては、一つのハッシュがテーブル内の一つのレコードとして扱われるものとして認識していただいて大丈夫です。
(※この時点ではまだあまりイメージが湧かないと思いますので、詳しくは後述させていただきますね。)

active_hashの導入方法

active_hashの導入方法は非常に簡単です。

active_hashもGemですので、Gemfileに以下を記載して「bundle install」を実行しましょう。

gem 'active_hash'

active_hashの使い方

active_hashを導入できたところで、続いては実例を見ながらその使い方を解説していきますね。

まずは扱いたいデータをモデルに定義していきましょう。今回は都道府県のデータ(idとname属性を有するものとします)を定義していこうと思います。
(※都道府県であれば、その名前や数は変更/増減するものではないため、active_hashを使用するのに適していますよね)

なお、ここで注意点があるのですが、通常、モデルを作成する際にはrails g model Prefecture name:stringのようなコマンドを実行すると思うのですが、active_hashを用いる場合には手動でファイルを作成するようにしてください。

今回であれば、app/models/prefecture.rbを作成し、その中身は以下のようにしてください。

class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end

これで何となく上述した「一つのハッシュがテーブル内の一つのレコードとして扱われる」のイメージが掴めたかと思います。

こちらの例で言えば、{id: 1, name: '北海道'}が一つのレコード(id, nameがカラム名)として扱われるということですね。

こうすることで、Prefectureモデルに対してActiveRecordの各種メソッドが使用できるようになります。

試しに実行してみると以下のような実行結果が返ってくるかと思います。

[1] pry(main)> Prefecture.find(1)
=> Prefecture:0x007f464b2fad66 @attributes={:id=>1, :name=>"北海道"}>

[2] pry(main)> Prefecture.last
=> Prefecture:0x007f514b2fad37 @attributes={:id=>47, :name=>"沖縄県"}>

使い方も非常にシンプルで、active_hashを導入したということを意識することなくデータを扱うことが可能ですよね。

active_hashでアソシエーションを組んでみよう

通常のモデル同様、アソシエーションを組むのも非常に簡単です。

今回はCity(市区町村)モデルを定義し、Prefectureモデルと紐づいているものとします。

Prefectureモデルはhas_manyを使用するために以下のように記載しておきましょう。

class Prefecture < ActiveHash::Base
  include ActiveHash::Associations

  has_many :cities

  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'} ...
  ]
end

Cityモデルは通常通り、rails g model City name:string prefecture_id:integerを実行してください。

その後、作成されたapp/model/city.rbを以下のように記載しましょう。

class City < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions

  belongs_to_active_hash :prefecture
end

ここでは以下の2点を追加しているのですが、特にbelongs_toは少し書き方が異なっていますのでご注意くださいね。

  • extend ActiveHash::Associations::ActiveRecordExtensions
  • belongs_to_active_hash :prefecture

その後ターミナルにて確認すると無事アソシエーションが確立されているのが確認できるかと思います。

[1] pry(main)> city_1 = City.create(prefecture_id: 1, name: '札幌市')
 id: 1,
 prefecture_id: 1,
 name: '札幌市',
 created_at: Tue, 28 Jan 2020 11:41:41 UTC +00:00,
 updated_at: Tue, 28 Jan 2019 11:41:41 UTC +00:00

[2] pry(main)> city_2 = City.create(prefecture_id: 27, name: '大阪市')                                                                                        
 id: 2,
 prefecture_id: 27,
 name: '大阪市',
 created_at: Tue, 28 Jan 2020 11:42:41 UTC +00:00,
 updated_at: Tue,28 Jan 2020 11:42:41 UTC +00:00

[3] pry(main)> city_1.prefecture.name                                                                                                                               
=> "北海道"
[4] pry(main)> city_2.prefecture.name                                                                                                                               
=> "大阪府"

他にも色々なメソッドが使えます

上記で扱ったもの以外にも使用できるメソッドは多くありますので、ここではその一部をご紹介させていただきますね。

メソッド戻り値
City.all全てのCityオブジェクト
City.countCityオブジェクトの数
City.first最初のCityオブジェクト
City.last最後のCityオブジェクト
City.find(1)idが「1」である最初のCityオブジェクト
City.find_by_id(1)idが「1」である最初のCityオブジェクト
City.find_by_name(“大阪市”)nameが「大阪市」である最初のCityオブジェクト
City.find_all_by_name(“大阪市”)nameが「大阪市」である全てのCityオブジェクト

さらに詳しく知りたい方は以下のGithubのREADMEを参照してみてくださいね。
https://github.com/zilkey/active_hash

active_hashを使用する際の注意点

このように、active_hashは非常に便利ではあるのですが、1点だけ注意事項があります。

それは、active_hashを使用したモデルがあると、1対多は問題ないのですが、多対多のアソシエーションは組むことができない、ということです。

そのため、active_hashを使用したモデルで多対多を実現しようと思うと、以下の例のようにメソッドを追加してあげる必要がありますので、この点はくれぐれもご注意くださいね。

前提条件

  • 前提条件① Human(人材)モデルとSkill(スキル)モデルを定義
  • 前提条件② 人材には複数のスキルが登録されており、スキルも複数の人材を有している(=HumanとSkillは多対多の関係)
  • 前提条件③ 中間テーブルとしてHumanSkillモデルを定義

実装例

class Human < ApplicationRecord
  has_many :human_skills
  
  def skills
    human_skills.map(&:skill)
  end
end
class HumanSkill < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions

  belongs_to :human
  belongs_to_active_hash :skill
end
class Skill < ActiveHash::Base
  include ActiveHash::Associations

  has_many :human_skills
  
  def humans
    human_skills.map(&:human)
  end
end

上記では、直接humansskillsを呼び出す代わりに、一度中間テーブル(ここではhuman_skills)のレコードを取得し、mapメソッドを使って各レコードごとのhuman或いはskillを配列に格納する形に実装しています。

これ以外にも実現方法はいくつかあるとは思いますが、まずは「active_hashで多対多を組みたい」場合の選択肢の一つとして知っておいていただければと思います。

お仕事の途中ですが、少し一休みして、転職独立について考えてみませんか🙌?

現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】

レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】

MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】

日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲

最後に

さて、ここまでactive_hashの役割や導入方法、そしてその使い方についてご紹介してきましたがいかがでしたか?

便利なGemではありながらもその使い方は非常にシンプルで分かりやすかったと思います。

無駄なテーブルを増やしていかないためにも、導入に適したデータがある場合には積極的に使っていきましょうね!

ただし、多対多のアソシエーションを組む場合には少し工夫が必要になりますので、くれぐれもご注意くださいね。

このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。

最後まで読んで頂きありがとうございました。