Help us understand the problem. What is going on with this article?

Railsでも堅いデータベース設計をする。堅いenumの使い方

More than 3 years have passed since last update.

はじめに

Rails4.1からenumが追加され、便利に使えるようになりました。

参考: いまさらながらRails4.1から導入されたEnumが便利なのでまとめてみた

しかしenumは、想定外の値が挿入されることをデータベースのレイヤーで防ぐことはできず、SQLアンチパターン の第10章「31のフレーバー」のアンチパターンに当てはまります。

このような時は、enumに挿入される値をあらかじめ設定するマスターテーブルのようなものを作る必要があります。

そのサンプルを紹介します。

サンプルコードは、こちらのgithubにアップロードしています。

開発環境

  • Rails: 5.0.0.1
  • Database: PostgreSQL 5.2
  • 環境はdocker-composeでセットアップします。

開発を始める際は、

git clone https://github.com/kawasin73/rails_enum_sample.git
cd rails_enum_sample.git
docker-compose up -d spring
docker-compose exec spring rails db:create
docker-compose exec spring rails db:migrate
docker-compose exec spring rails db:seed_fu
docker-compose up

主要なgem

seed_fu をマスターデータの挿入に利用します。

モデルの作成

今回は、id, title, mode, categoryの4つのカラムを持ったItemモデルを作成します。

  • mode は、integer を持つenum
  • category は、string を持つenum

にする予定です。

ItemMode モデル

mode のマスターデータを保持するための、ItemModeモデルを作成します。

db/migrate/20170120005654_create_item_modes.rb
class CreateItemModes < ActiveRecord::Migration[5.0]
  def change
    create_table :item_modes, id: :integer do |t|
      t.string :name, null: false
    end
  end
end

idinteger型ですが、AUTO_INCREMENT を無効にするために、create_table :item_modes, id: :integer do |t| と指定しています。

app/models/item_mode.rb
# == Schema Information
#
# Table name: item_modes
#
#  id   :integer          not null, primary key
#  name :string           not null
#

class ItemMode < ApplicationRecord
  validates :name, presence: true
end

ItemCategory モデル

category のマスターデータを保持するための、ItemCategoryモデルを作成します。

db/migrate/20170120005937_create_item_categories.rb
class CreateItemCategories < ActiveRecord::Migration[5.0]
  def change
    create_table :item_categories, id: :string do |t|
    end
  end
end
app/models/item_category.rb
# == Schema Information
#
# Table name: item_categories
#
#  id :string           not null, primary key
#

class ItemCategory < ApplicationRecord
end

Item モデル

最後にItemモデルを作成します。

db/migrate/20170120010046_create_items.rb
class CreateItems < ActiveRecord::Migration[5.0]
  def change
    create_table :items do |t|
      t.string :title, null: false
      t.integer :mode, null: false, default: 0
      t.string :category, null: false

      t.timestamps
    end

    add_foreign_key :items, :item_modes, column: :mode, primary_key: :id
    add_foreign_key :items, :item_categories, column: :category, primary_key: :id
  end
end

ここで重要となるのが、add_foreign_key 外部キー制約の設定です。これによって、マスターデータに設定されている値以外をデータベースに挿入できなくします。

app/models/item.rb
# == Schema Information
#
# Table name: items
#
#  id         :integer          not null, primary key
#  title      :string           not null
#  mode       :integer          default(0), not null
#  category   :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
# Foreign Keys
#
#  fk_rails_28ef78c62c  (mode => item_modes.id)
#  fk_rails_4fb37c41fb  (category => item_categories.id)
#

class Item < ApplicationRecord
  validates :title, presence: true

  enum mode: { pending: 0, completed: 1, error: 2 }
  enum category: { politics: 'Politics', economy: 'Economy', sports: 'Sports' }
end

enum を設定します。mode はデフォルト値が0のため、Item.newした段階で自動的にpendingになるのも嬉しいところです。

また、ItemModeと、ItemCategoryは、データベースの堅牢性のためだけに作成したモデルですので、 アプリケーションの中では利用しません。 そのため、belongs_tohas_many はあえて設定しません。

マスターデータの作成

マスターデータは、seed_fuで作成します。

db/fixtures/001_item_mode.rb
Item.modes.each do |name, id|
  ItemMode.seed do |item_mode|
    item_mode.id = id
    item_mode.name = name.to_s
  end
end
db/fixtures/002_item_category.rb
Item.categories.values.each do |id|
  ItemCategory.seed do |item_category|
    item_category.id = id
  end
end

この上で以下のコマンドを実行するとマスターデータが作成できます。

docker-compose up -d spring
docker-compose exec spring rails db:seed_fu

また、新しい値を設定する際は、Itemモデルのenumのハッシュを足して、rails db:seed_fuするだけでマスターデータに追加することができます。

最後に

以上の設定で、enumを堅いデータベース設計の中で利用することができます。
ぜひ参考にしてください。

参考URL

kawasin73
ソフトウェアエンジニアです。東京大学4年Go/Ruby/Swift/Python/Javascript/Java/C
https://kawasin73.hatenablog.com/
dmmcom
総合エンタテイメントサイト「DMM.com」を運営。会員数は2,900万人を突破。動画配信、FX、英会話、ゲーム、太陽光発電、3Dプリンタなど40以上のサービスを展開。沖縄での水族館事業参入、ベルギーでのサッカークラブ経営など、様々な事業を手掛ける。また2018年より若手起業家の支援を強化、「DMM VENTURES」による出資や、M&Aなどを積極的に展開している。
https://dmm-corp.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away