Posted at

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

More than 1 year has 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