はじめに
大まかな流れ
- MySQLの準備
- Rubyのインストール
- ridgepoleのインストール
- ridgepoleでのテーブル操作
- ちょっと応用編
注意点
-
git
,docker
,docker-compose
は導入済みとして進めていきます- 既存のMySQLや、非コンテナのMySQLを使用する場合は
docker
,docker-compose
を使用する必要ありません
- 既存のMySQLや、非コンテナのMySQLを使用する場合は
- 環境的にUbuntu18.04を使用しているので
apt
が出てきますが、適宜読みかえてください - Rubyインストールに使用する
rbenv
のインストールには公式のrbenv-installerを使用するのが速いのですが、brew
導入済みのマシンでは作業が必要となります(後述)- 面倒な作業がしたくない場合は
brew
でいけます
- 面倒な作業がしたくない場合は
環境
ソフトウェア等 | バージョン | 備考 |
---|---|---|
Ubuntu | 18.04 | |
git | 2.31.1 | 脆弱性対応で2.30.2以降を入れるべきです |
docker | 20.10.5 | MySQLをコンテナで動かす場合 |
docker-compose | 1.28.5 | 同上 |
MySQL | mysql:5.7 | dockerコンテナのmysql:5.7をdocker-composeで実行 |
rbenv | 1.1.2 | |
ruby-build | 20210309 | |
Ruby | 2.4.0 | 特に理由なし |
ridgepole | 0.8.13 | 0.9系だと変更がなくてもテーブルオプションの差分がでてしまったので |
最終的なDir構成
<作業ディレクトリ>
|-- mysql
| |-- .bundle # bundleの設定ファイルが格納される
| |-- bundle # bundleのインストール先(--path指定した場合)
| |-- database.yml # mysql接続設定
| |-- Gemfile # インストールしたいGemを記述するファイル
| | # nodeで言うpackage.jsonみたいなもの?
| |-- Gemfile.lock # 依存性含め実際にインストールしたGemが記載されたファイル
| | # nodeで言うpackage-lock.jsonとかyarn.lockみたいなもの?
| |-- Schemafile # mysqlスキーマ定義ファイル
| `-- .env # ちょっと応用編で使う環境設定ファイル
|
`-- docker-compose.yml # mysqlのコンテナ起動用
手順
MySQLの準備
MySQLコンテナの作成
既存のMySQLを使用する場合は不要です
version: '3.8' # docker-composeのバージョンとかで変わります
services:
mysql:
image: mysql:5.7
environment:
- MYSQL_USER=root
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
ports:
- "3306:3306"
volumes: # 必要に応じてマウントしてください
- ./mysql/sql:/docker-entrypoint-initdb.d/ # 初回起動時に動作するSQLを入れる場合はこのマウントが必要
- ./mysql/conf/:/etc/mysql/conf.d/ # MySQLの設定
- ./mysql/data/:/var/lib/mysql/ # データの格納場所
# docker-compose.ymlのあるDirで実行
docker-compose up -d
# コンテナ起動状態を確認
docker-compose ps
接続先DBの設定
既存のMySQLを使用する場合は既存のMySQLに設定を合わせて修正してください
adapter: mysql2
encoding: utf8mb4
charset: utf8mb4
collation: utf8mb4_general_ci
database: sample
username: root
password: ''
host: 127.0.0.1
databaseを作成
mysqlにログインしてdatabaseを作成しておく
docker ps # mysql5.7のコンテナIDを確認
docker exec -it `mysql5.7のコンテナID` bash # VSCodeでdockerの拡張機能使ってログインとかでも良いです
mysql # mysqlに入ってdatabaseを作成しておく
> create database sample;
Rubyのインストール
rbenv
,ruby-build
を利用してRubyをインストールします。インストールの方法は下記のどちらかです
(apt
では、古いバージョンのrbenv
,ruby-build
しか入りませんでした。)
- rbenv-installerのスクリプトでインストール
brew install rbenv ruby-build
※brew
導入済みマシンでrbenv-installerスクリプトを使用するにはちょっと作業が必要です(後述)
rbenv-installerを使う場合
brew
でインストールしたい場合はbrewを使う場合まで読み飛ばしてください
インストール先 | バージョン | |
---|---|---|
rbenv | ~/.rbenv/bin/rbenv |
1.1.2-44-gd604acb |
ruby-build | ~/.rbenv/plugins/ruby-build/bin/rbenv-build |
20210309 |
※brew導入済みの場合はインストール先はbrewを使う場合と同じになります
brew導入済みマシンの場合
-
brew
を導入済みマシンでrbenv-installerを使用する場合はこの手順が必要となります。(brew
を導入していない場合はrbenv-installerの実行まで読み飛ばしてください)
rbenv-installerのREADME.mdには
If Homebrew is detected, installation will proceed using brew install/upgrade.
と書かれていています(ざっくり言うと「brew
があるならbrew
使ってインストールするよ」)が、実際に動かすとエラーになってしまいます。
これは、スクリプトに書かれている--without-ruby-build
が原因となっていてIssueやPullRequestを見ると2019年から発覚しているのものの、解決される気配はありません。
スクリプト内の--without-ruby-build
を消せば良いだけなので、rbenv-installer
のリポジトリをPullして、該当箇所を消したスクリプトを使えばbrew
とrbenv-installer
を共存させることが出来ます。(一応インストールだけは動作確認しました)
# rbenv-installerのリポジトリをcloneする
cd ~ # clone先はどこでも良いです
git clone https://github.com/rbenv/rbenv-installer.git
# rbenv-installerを修正する
vi ~/rbenv-installer/bin/rbenv-installer # もちろんviでなくてもOKです
下記の箇所を修正した上で、次の手順に進んでください。(--without-ruby-build
を消すだけです)
diff --git a/bin/rbenv-installer b/bin/rbenv-installer
index f426db6..dba12f5 100755
--- a/bin/rbenv-installer
+++ b/bin/rbenv-installer
@@ -42,7 +42,7 @@ if [ -n "$rbenv" ]; then
brew update >/dev/null
if [ "$(./rbenv --version)" < "1.0.0" ] && brew list rbenv | grep -q rbenv/HEAD; then
brew uninstall rbenv
- brew install rbenv --without-ruby-build
+ brew install rbenv
else
brew upgrade rbenv
fi
@@ -56,7 +56,7 @@ else
if [ -n "$homebrew" ]; then
echo "Installing rbenv with Homebrew..."
brew updat
- brew install rbenv --without-ruby-build
+ brew install rbenv
rbenv="$(brew --prefix)/bin/rbenv"
else
echo "Installing rbenv with git..."
rbenv-installerの実行
# rbenv/ruby-buildのインストール
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer -O- | bash # wgetを使う場合
~/rbenv-installer/bin/rbenv-installer # cloneしたrbenv-installerを使う場合
# PATHを通して、rbenv initする
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
eval "$(rbenv init -)"
# インストール状態の確認
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor -O- | bash # wgetを使う場合
~/rbenv-installer/bin/rbenv-doctor # cloneしたrbenv-doctorを使う場合
brewを使う場合
インストール先 | バージョン | |
---|---|---|
rbenv | /home/linuxbrew/.linuxbrew/bin/rbenv |
1.1.2 |
ruby-build | /home/linuxbrew/.linuxbrew/bin/ruby-build |
20210309 |
brewをインストール
導入済みの人はrbenv,ruby-buildをインストールまで読み飛ばしてください
# brewのインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# PATHを通す
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# メッセージにでたコマンドで推奨されたものを入れる
sudo apt install -y build-essential
brew install gcc
rbenv,ruby-buildをインストール
# rbenv,ruby-buildのインストール
brew install rbenv ruby-build
# rbenv initする
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
eval "$(rbenv init -)"
# インストール状態の確認
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor -O- | bash # wgetを使う場合
Rubyをインストール
rbenv install
で任意のバージョンのRubyをインストールします
# 今のバージョンを確認
ruby --version
# インストール可能なバージョンを確認
rbenv install --list # 最新の安定バージョンに限定した一覧
rbenv install --list-all # 全バージョン一覧
# とりあえず2.4.0を入れてみる(結構時間かかる)
rbenv install 2.4.0
# rbenv installが失敗したので下記を実行してから再度rbenv install
sudo apt install zlib1g-dev
# バージョンを確認(2.4.0は入ったが、設定していないのでまだ使えない)
rbenv versions
ruby --version
# ruby --versionでrubyが見つからなかったらrbenv initをし直すために下記を実行してみる
. ~/.bashrc
# バージョンを切り替える
rbenv global 2.4.0 # マシン全体のrubyバージョンを指定する場合
rbenv local 2.4.0 # カレントディレクトリ以下のrubyバージョンを指定する場合
ruby --version
ridgepoleのインストール
bundleのインストール・初期化
gem install bundle # sudo必要かも
. ~/.bashrc
cd ~ # プロジェクトを作るディレクトリに移動する
bundle init # カレントディレクトリにGemfileが生成される
Gemfileに追記
gem 'ridgepole', '< 0.9.0' # 0.9系を避けてます
gem 'mysql2'
Gemfileに従ってインストール
# libmysqlclient-devを先に入れておかないとエラーが出てしまいます(環境依存? ubuntu限定かも? )
sudo apt install -y libmysqlclient-dev
bundle install
bundle installのパス指定について
調べているとbundle install --path vender/bundle
としている記載されているサイト等が多かったのですが
--path
オプションは現在非推奨になっているようで、下記のようなメッセージが出てきます。
[DEPRECATED] The
--path
flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please usebundle config set --local path 'vender/bundle'
, and stop using this flag
インストール先を指定したい場合はbundle config set --local path 'vender/bundle'
を使った方が良いようです。
ridgepoleでのテーブル操作
ざっくり主要操作まとめ
# スキーマ構造の出力
bundle exec ridgepole -c database.yml --export -o Schemafile
# スキーマ作成・更新(差分確認)
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run
# スキーマ作成・更新
bundle exec ridgepole -c database.yml -f Schemafile --apply
テーブル定義の出力
既存のMySQLを使っている場合などには下記コマンドでテーブル定義をSchemafileに出力可能です
ridgepoleの実行
bundle exec ridgepole -c database.yml --export -o Schemafile
# 実行時の表示
Export Schema to `Schemafile`
DBに何もないとこんな感じで出力されます
# -*- mode: ruby -*-
# vi: set ft=ruby :
何かしらテーブル構造が入っている場合はこんな感じで出力されます
# -*- mode: ruby -*-
# vi: set ft=ruby :
create_table "shops", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗", force: :cascade do |t|
t.string "shop_name", null: false, comment: "店舗名"
t.datetime "created_at", null: false, comment: "作成日時"
t.datetime "updated_at", null: false, comment: "更新日"
end
create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
t.string "user_name", null: false, comment: "ユーザ名"
t.string "email", comment: "メールアドレス"
t.string "address", comment: "住所"
t.datetime "created_at", null: false, comment: "作成日時"
t.datetime "updated_at", null: false, comment: "更新日"
end
テーブル定義の作成
スキーマ定義を作成
Schemafileにテーブル定義を記載します
# encoding: utf-8
create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
t.string "name", null: false, comment:"ユーザ名"
t.string "email", null: false, comment:"メールアドレス"
t.datetime "created_at", null: false, comment:"作成日"
t.datetime "updated_at", null: false, comment:"更新日"
end
ridgepoleの実行
--dry-run
オプションを使うと(実際には実行せず)実行した場合の変更内容が確認できます
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run
# 事前の差分表示
Apply `Schemafile` (dry-run)
create_table("users", **{:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"ユーザー"}) do |t|
t.column("name", :"string", **{:null=>false, :comment=>"ユーザ名", :limit=>255})
t.column("email", :"string", **{:null=>false, :comment=>"メールアドレス", :limit=>255})
t.column("created_at", :"datetime", **{:null=>false, :comment=>"作成日"})
t.column("updated_at", :"datetime", **{:null=>false, :comment=>"更新日"})
end
# CREATE TABLE `users` (
# `id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
# `name` varchar(255) NOT NULL COMMENT 'ユーザ名',
# `email` varchar(255) NOT NULL COMMENT 'メールアドレス',
# `created_at` datetime NOT NULL COMMENT '作成日',
# `updated_at` datetime NOT NULL COMMENT '更新日')
# ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'ユーザー'
問題なければ--dry-run
を外して変更を適用します
bundle exec ridgepole -c database.yml -f Schemafile --apply
# 実行時の表示
Apply `Schemafile`
-- create_table("users", {:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"ユーザー"})
-> 0.0071s
テーブル定義の変更
コマンドはテーブル定義の作成と同じです。ridgepoleがSchemafileとDBの定義間の差分を反映してくれます。
スキーマ定義を変更
Schemafileのスキーマ定義を変更します
# encoding: utf-8
create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
t.string "user_name", null: false, comment:"ユーザ名", renamed_from: "name"# カラム名変更
t.string "email", null: true, comment:"メールアドレス" # 定義変更
t.string "address", null: true, comment:"住所" # カラム追加
t.datetime "created_at", null: false, comment:"作成日時" # コメント変更
t.datetime "updated_at", null: false, comment:"更新日" # そのまま
end
# テーブル追加
create_table "shops", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗", force: :cascade do |t|
t.string "shop_name", null: false, comment:"店舗名"
t.datetime "created_at", null: false, comment:"作成日時"
t.datetime "updated_at", null: false, comment:"更新日"
end
ridgepoleの実行
--dry-run
オプションで一度差分を確認します
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run
# 事前の差分表示
Apply `Schemafile` (dry-run)
create_table("shops", **{:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"店舗"}) do |t|
t.column("shop_name", :"string", **{:null=>false, :comment=>"店舗名", :limit=>255})
t.column("created_at", :"datetime", **{:null=>false, :comment=>"作成日時"})
t.column("updated_at", :"datetime", **{:null=>false, :comment=>"更新日"})
end
add_column("users", "address", :string, **{:null=>true, :comment=>"住所", :after=>"email", :limit=>255})
rename_column("users", "name", "user_name")
change_column("users", "email", :string, **{:null=>true, :comment=>"メールアドレス", :default=>nil, :unsigned=>false})
change_column("users", "created_at", :datetime, **{:null=>false, :comment=>"作成日時", :default=>nil, :unsigned=>false})
# CREATE TABLE `shops` (
# `id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
# `shop_name` varchar(255) NOT NULL COMMENT '店舗名',
# `created_at` datetime NOT NULL COMMENT '作成日時',
# `updated_at` datetime NOT NULL COMMENT '更新日')
# ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '店舗'
# ALTER TABLE `users` ADD `address` varchar(255) COMMENT '住所' AFTER `email`
# ALTER TABLE `users` CHANGE `name` `user_name` varchar(255) NOT NULL
# ALTER TABLE `users` CHANGE `email` `email` varchar(255) DEFAULT NULL COMMENT 'メールアドレス'
# ALTER TABLE `users` CHANGE `created_at` `created_at` datetime NOT NULL COMMENT '作成日時'
問題なければ--dry-run
を外して変更を適用します
bundle exec ridgepole -c database.yml -f Schemafile --apply
# 実行時の表示
Apply `Schemafile`
-- create_table("shops", {:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"店舗"})
-> 0.0089s
-- add_column("users", "address", :string, {:null=>true, :comment=>"住所", :after=>"email", :limit=>255})
-> 0.0103s
-- rename_column("users", "name", "user_name")
-> 0.0046s
-- change_column("users", "email", :string, {:null=>true, :comment=>"メールアドレス", :default=>nil, :unsigned=>false})
-> 0.0148s
-- change_column("users", "created_at", :datetime, {:null=>false, :comment=>"作成日時", :default=>nil, :unsigned=>false})
-> 0.0048s
ちょっと応用編
複数の接続設定
ridgepole
で-E
オプションを利用するとdatabase.yml
に定義された複数の接続先設定を読み分けて実行することが出来るようになり、複数環境のデータベース定義ファイルが統一出来ます。
# 基本的な情報だけはこっちに定義しておく
base: &base
adapter: mysql2
encoding: utf8mb4
charset: utf8mb4
collation: utf8mb4_general_ci
# 環境別の情報は個別に定義や上書きする
development:
<<: *base
database: sample
username: root
password: ''
host: 127.0.0.1
# 検証ということで一旦databaseを分けるだけでやっています
test:
<<: *base
database: sample2
username: root
password: ''
host: 127.0.0.1
# development用(実行結果は省略します)
bundle exec ridgepole -c database.yml -E development -f Schemafile --apply
# test用(実行結果は省略します)
bundle exec ridgepole -c database.yml -E test -f Schemafile --apply
dotenvを使って環境依存設定を分離
dotenv
を導入すると、database.yml
から環境依存情報やパスワードをなくすことが出来ます。
Gemfileに追記
gem 'dotenv-rails'
bundleインストール
bundle install
環境情報の分離
database.yml
を修正
# 変えた所のみを抜粋
development:
<<: *base
database: <%= ENV.fetch('DB_NAME') %>
username: <%= ENV.fetch('USER_NAME') %>
password: <%= ENV.fetch('PASSWORD') %>
host: <%= ENV.fetch('HOST')%>
# 変数が存在しない時のデフォルト値はENV.fetch('HOGEHOGE','defaultValue')で設定できるようです
.env
を作成
export DB_NAME='sample'
export USER_NAME='root'
export PASSWORD=''
export HOST='127.0.0.1'
ridgepoleの実行
bundle exec dotenv -f '.env' bundle exec ridgepole -c database.yml -E development -f Schemafile --apply
dotenv
を導入した上で、例えば下記のような工夫をしておくとリリースミスなどの発生は抑えられるかもしれませんね。
- リポジトリ管理する
.env
にはローカル環境や開発環境用の設定しか記載しない - 試験環境や本番環境の設定については
.env
ファイルのファイル名を変えておく-
.env-staging
とか.env-prod
とか?
-
※実運用する際にはdatabase.yml
と.env
についてどう記載して複数環境を分離するかはちゃんと検討したほうが良さそうです。
さいごに
以上の手順によって、Schemafileを原本としてテーブル定義を行うことが出来るようになったため、Schemafileでテーブル構造を設計すれば「実際のテーブル構造とデータベース設計書に差分が発生してしまう」なんて事はなくなりました。
テーブル定義としてのSchemafile
今回実施した「カラム名の変更」ではrenamed_from:
を指定しないと削除→追加となってしまうようです。
つまり「テーブル定義のスナップショット」として存在させたいSchemafileに、「○○から●●への変更」という「スナップショット的でない要素」が入ってしまいます。
# Schemafileにrename_fromの指定が無い場合は下記のようにカラム削除→追加となってしまいます
add_column("users", "user_name", :string, **{:null=>false, :comment=>"ユーザ名", :after=>"id", :limit=>255})
remove_column("users", "name")
# ALTER TABLE `users` ADD `user_name` varchar(255) NOT NULL COMMENT 'ユーザ名' AFTER `id`
# ALTER TABLE `users` DROP COLUMN `name`
例えば初期リリース前の開発中などではテーブル構造に変更が入ることも多いですが、こういった場合にはリリース手順やSchemafileの管理に工夫をすることになります。
(リリース用のSchemafileと、リリース後のスナップショットとしてのSchemafileをそれぞれ用意するとかでしょうか?)
※カラム追加などではないテーブル構造の変更については、バグの発生原因になりますので特にリリース後は実施を避けるべきかと思います