ずみーBlog

元クラゲ研究者(見習い)の92年生まれがエンジニアを目指しながら日々寄り道するブログです。

Active Hashで固定値をDB保存せずにテーブルのようにして扱う

ブログ投稿サービスで、記事投稿時にジャンル選択ができるような機能を実装しながら、Active Hashについて学びました。

Active Hashのきほん

都道府県や記事カテゴリなど、追加・変更が頻繁に生じないようなデータは、DBにテーブルを作るのではなく、Active HashでRails側に保持すると便利です。

導入

Gemfile

gem 'active_hash'
bundle install

下準備 (モデル作成)

--skip-migrationメソッド

モデルファイル作成時にマイグレーションファイルの作成を行わないオプション。 以下の例は、記事投稿時の「ジャンル」をActive Hashで作成する。DB保存はしないので、マイグレーションは不要。

rails g model genre --skip-migration

ActiveHash::Base

モデルファイルのクラスをActiveHash::Baseから継承することで、モデル内で定義したオブジェクトに対してActiveRecordのメソッドが使えるようになる。 →つまりテーブルとして扱えるようになる

self.dataにハッシュの配列を格納することでテーブルのような構造にする

class Genre < ActiveHash::Base
 self.data = [
   { id: 1, name: '--' },
   { id: 2, name: '経済' },
   { id: 3, name: '政治' },
   { id: 4, name: '地域' },
   { id: 5, name: '国際' },
   { id: 6, name: 'IT' },
   { id: 7, name: 'エンタメ' },
   { id: 8, name: 'スポーツ' },
   { id: 9, name: 'グルメ' },
   { id: 10, name: 'その他' }
 ]
 end

他のテーブルから参照する

他のテーブルからActive Hashを参照するには、integerでid項目を持つようにすればOKです。

ex) 記事テーブルからジャンル(Active Hash)を参照する時のマイグレーションファイル

class CreateArticles < ActiveRecord::Migration[6.0]
  def change
    create_table :articles do |t|
      t.string     :title        , null: false
      t.text       :text         , null: false
      t.integer    :genre_id     , null: false
      t.timestamps
    end
  end
end

マイグレーション実行

rails db:migrate

Active Hashでのアソシエーション設定

belongs_to_active_hashメソッド

Active Hashを使って作成したモデルに対するアソシエーション設定ができるメソッド。 使用するためには、extend ActiveHash::Associations::ActiveRecordExtensionsの記述が必要。 Active Hashのモデル側にはアソシエーション定義不要です。

models/article.rb

class Article < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :genre

  validates :title, :text, :genre, presence: true
  validates :genre_id, numericality: { other_than: 1 }
end

尚、上の例ではさらにgenre_idが1以外でないと保存できないようなバリデーションを加えています。(id:1は「--」なので)

Active Hashを利用して記事一覧表示機能をつくってみる

ルーティング

Rails.application.routes.draw do
  root to: 'articles#index'
  resources :articles
end

コントローラ

rails g controller articles index new
class ArticlesController < ApplicationController
  def index
    @articles = Article.order("created_at DESC")
  end

  def new
  end
end

ビュー

articles/index.html.erb

<h1>記事一覧</h1>
<%= link_to "投稿する", new_article_path, class:"post" %>

<% @articles.each do |article| %>
  <div class="article">
    <div class="article-genre">
      <%= article.genre.name %>
    </div>
    <div class="article-title">
      <%= article.title %>
    </div>
    <div class="article-text">
      <%= article.text %>
    </div>
  </div>
<% end %>

これで、記事のジャンル名を参照して表示できるようになりました。

Active Hashを利用して投稿機能をつくってみる

コントローラやルーティングの記述は割愛します。

新規投稿画面

<%= form_with model: @article, url:articles_path, local: true do |f| %>
  <div class="article-box">
    記事を投稿する
    <%= f.text_field :title, class:"title", placeholder:"タイトル" %>
    <%= f.text_area :text, class:"text", placeholder:"テキスト" %>
    <%= f.collection_select(:genre_id, Genre.all, :id, :name, {}, {class:"genre-select"}) %>
    <%= f.submit "投稿する" ,class:"btn" %>
  </div>
<% end %>

collection_selectメソッド

データをプルダウン形式で表示することができるヘルパーメソッドです。

<%= form.collection_select(保存されるカラム名, オブジェクトの配列, カラムに保存される項目, 選択肢に表示されるカラム名, オプション, htmlオプション) %>

上の新規投稿画面の例では、以下のようになっています。

引数
保存されるカラム名 :genre_id
オブジェクトの配列 Genreモデル全て(.all)
カラムに保存される項目 Genreのid
プルダウンに表示される項目 Genreのname
オプション なし
htmlオプション class="genre-select"

以上、ご参考になれば幸いです。