導入のきっかけ
どれだけ緻密にテーブル設計をしても、途中仕様変更や追加要望が入って、初期の見積もりから大幅にテーブル構造が変更されることは目に見えてる。
Railsの場合、スキーマを変更するにはスキーマファイルを追加していかないといけない。
でもスキーマの変更履歴は git
で追えるし、ぶっちゃけ最新のスキーマが見られればそれで十分なんだよね🤔
ということで無駄にスキーマファイルが増えるのを嫌い、ridgepole導入を決意しました。
めちゃ簡単だよ🙆♂️
動作環境
ruby 3.0.2
Rails 6.1.4
MySQL 8.0.25
導入手順
1. ridgepoleをインストール
gem 'ridgepole'
% bundle install
Gemfileに rigepole
を追記し bundle install
。
これで面倒な migrate
ではなく ridgepole
が使用できるようになりました。はやっ。
2. 試しにauthorsテーブルを作ってみる
ridgepole
に関わるファイルは1つの階層に纏めておきたいので、dbディレクトリ配下にschemas
ディレクトリを作成します。
(自分が分かりやすければ、名前はschemasじゃなくてもOK)
次に Schemafile
という名前のファイルをschemasディレクトリに作成。
そして同じ階層にauthors
テーブルのスキーマを定義したスキーマファイルを作成します。
% mkdir -p db/schemas
% touch db/schemas/Schemafile
% touch db/schemas/authors.schema.rb
authors
テーブルのスキーマは仮に下記のように定義しておくとしましょう。
create_table :authors, force: :cascade, charset: 'utf8mb4', collation: 'utf8mb4_0900_ai_ci', options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC' do |t|
t.string :name, null: false, default: ''
t.integer :age, null: false, default: 20
t.index :name, name: 'index_authors_on_name'
end
authors
テーブルのスキーマファイルが出来たら、それを Schemafile
で読み込んで。
require 'authors.schema.rb'
そして最後にridgepole
コマンドを実行!
% bundle exec ridgepole --config config/database.yml --env development --file db/schemas/Schemafile --apply
やや長ったらしいコマンドで分かりづらいので、少しだけridgepole
コマンドのオプションに関して説明します☝️
※ 詳しくは公式ドキュメントのHelp参照
オプション | 省略形 | 引数 | 役割 |
---|---|---|---|
--config | -c | config/database.yml | DBに関するconfigファイルの指定 |
--env | -E | development, test, production | 実行環境の指定 |
--file | -f | db/schemas/Schemafile | スキーマファイルの指定 |
--apply | -a | なし | apply(実行) |
つまり先程のコマンドは、
・ DBに関するconfigファイルは config/database.yml
・ 実行環境は development
・ スキーマファイルは db/schemas/Schemafile
以上の設定でridgepole
コマンドをapply(実行)します、という意味でした🤗
意外とシンプル笑
さて、本題に戻ります↩️
先程のコマンドを実行すると、下記のような実行結果が表示されてると思います。
Apply `db/schemas/Schemafile`
-- create_table("authors", {:charset=>"utf8mb4", :collation=>"utf8mb4_0900_ai_ci", :options=>"ENGINE=InnoDB ROW_FORMAT=DYNAMIC"})
-> 0.0501s
-- add_index("authors", ["name"], {:name=>"index_authors_on_name"})
-> 0.0101s
表示結果を見る限り、authors
テーブルが作られindex
も貼られているようです。
では実際にDBにログインしてテーブルが正しく作成されているか確認しましょう。
% mysql db名
ログイン出来たらテーブル一覧を表示し、authors
テーブルがあればテーブル構造を確認します。
mysql> show tables;
+-------------------------------+
| Tables_in_db_name |
+-------------------------------+
| ar_internal_metadata |
| authors |
| schema_migrations |
+-------------------------------+
3 rows in set (0.00 sec)
mysql> desc authors;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | MUL | | |
| age | int | NO | | 20 | |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
うんうん、無事に作られていたようですね!とても簡単◎
3. スキーマを変更したい場合
先程のauthors
テーブルですが、実はとっても大切なカラムを忘れていました。
そう、**timestamps
**です。
timestamps
とは、どのテーブルにも当たり前のようにあるcreated_at
とupdated_at
のことです。
レコードの作成日時と更新日時を保持する大事なカラムですね。
Railsデフォルトのmigrate
を使用していた場合、rails g migrate 〜
コマンドでスキーマファイルを追加する必要がありましたが、ridgepole
は違います。
なんと先程作ったauthors
スキーマを書き換え、再度ridgepole
コマンドを実行するだけでいいのです!簡単✨
ということで早速編集。
create_table :authors, force: :cascade, charset: 'utf8mb4', collation: 'utf8mb4_0900_ai_ci', options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC' do |t|
t.string :name, null: false, default: ''
t.integer :age, null: false, default: 20
+
+ t.timestamps
t.index :name, name: 'index_authors_on_name'
end
そしてridgepole
コマンド実行!
% bundle exec ridgepole --config config/database.yml -env development --file db/schemas/Schemafile --apply
以下、実行結果。
Apply `db/schemas/Schemafile`
-- add_column("authors", "created_at", :datetime, {:null=>false, :after=>"age"})
-> 0.0169s
-- add_column("authors", "updated_at", :datetime, {:null=>false, :after=>"created_at"})
-> 0.0145s
うん、無事に追加出来たようですね!
先程の手順と同様、DBにログインしてテーブル構造を確認してみます。
% mysql db名
mysql> desc authors;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | MUL | | |
| age | int | NO | | 20 | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
しっかり追加されていました!万歳🙌
4. おまけ
最後にridgepole
コマンドを短くする方法を紹介します。
これをすると下記のように短いコマンドで同じことを出来るようになります。
# 通常のridgepoleコマンド
% bundle exec ridgepole --config config/database.yml -env development --file db/schemas/Schemafile --apply
# rakeタスクを使ったridgepoleコマンド
% bundle exec rails ridgepole:apply
では実際の作業手順を説明していきます。
ridgepole
はとても便利だけどコマンドが少々長く覚えづらいのがデメリットです。
しかもconfigファイルやスキーマファイルを毎回コマンド実行のたびに指定しなきゃいけないのはちょっと無駄な気がする。。。
しかし、こんなデメリットはrakeタスク
を作ると解消できますよ🙆♂️
今回はridgepole
という名前のタスクにします。
% bundle exec rails g task ridgepole
するとlib/tasks
ディレクトリの配下にridgepole.rake
というファイルが作成されます。
このファイルを以下のように編集してください。
namespace :ridgepole do
desc 'Apply schema to database'
task apply: :environment do
run('--apply')
end
private
def run(*options)
config_file = 'config/database.yml'
schema_file = 'db/schemas/Schemafile'
rails_env = ENV['RAILS_ENV'] || Rails.env
base_command = "bundle exec ridgepole --config #{config_file} --env #{rails_env} --file #{schema_file}"
full_command = [base_command, *options].join(' ')
puts '=== run ridgepole... ==='
puts "[Running] #{full_command}"
system full_command
end
end
これでrakeタスク
を作ることが出来ました!簡単でしょ?
ちなみにridgepole
コマンドのオプションを省略形で書かないのは、誰が見ても分かるように(もしくは分かりやすく)するため。
今後ridgepole
コマンドを実行したいときは下記のコマンドで実行できます🎉
% bundle exec rails ridgepole:apply
5. さらにおまけ
ridgepole
はスキーマをDBに反映させる以外に、DBの内容をSchemafile
に反映することもできます。
その機能も使う場合にはrakeタスク
を下記のように変更しましょう。
namespace :ridgepole do
desc 'Apply schema to database'
task apply: :environment do
run('--apply', '--file')
end
desc 'Export schema to Schemafile'
task export: :environment do
run('--export', '--split', '--output')
end
private
def run(*options)
config_file = 'config/database.yml'
schema_file = 'db/schemas/Schemafile'
rails_env = ENV['RAILS_ENV'] || Rails.env
base_command = "bundle exec ridgepole --config #{config_file} --env #{rails_env}"
full_command = [base_command, *options, schema_file].join(' ')
puts '=== run ridgepole... ==='
puts "[Running] #{full_command}"
system full_command
end
end
使い方は以下の通り。
% bundle exec ridgepole:export
Enjoy ridgepole's life 👍