[Ridgepole/MySQL]Railsのreferencesとcreate_tableで作られるidカラムを必ずUNSIGNEDにする
Railsのマイグレーションでreferencesとcreate_tableで作られるidカラムはBIGINT型としてADD COLUMNされるため -9223372036854775808から9223372036854775807
までの値を格納できます。しかし主キー・外部キーには負数が入ることはないので(ないよね?w)UNSIGNEDをつけておきたいのですね。ただ毎回書くのが面倒ですし、つけ忘れてしまうことやレビューで指摘が漏れることがありました。
例えば、下記の場合は users.id はUNSIGNEDですが、posts.id, posts.user_id がただのBIGINT型のカラムになるので負数が格納できてしまいます。
# ./Schemafile
create_table(:users, unsigned: true) do |t|
t.timestamps
end
create_table(:posts) do |t|
t.references, :user, null: false
t.timestamps
end
ここを気にするのは面倒なので暗黙的にUNSIGNEDなカラムになるようにしてみようと思います。
どうやってやるのか
Ridgepoleを拡張するファイルを用意しておき、Schemafileの内部でそのファイルをrequireしてモンキーパッチ🐒を当てる形式を採用してます。
# ./Schemafile
+ require 'lib/ridgepole/extension'
- create_table(:users, unsigned: true) do |t|
+ create_table(:users) do |t|
t.timestamps
end
create_table(:posts) do |t|
t.references, :user, null: false
t.timestamps
end
こちらのモンキーパッチではデフォルトのマイグレーションと同じようにconfig/database.ymlのtable_optionsに記載している設定がテーブルに当たるようにもしています。
# lib/ridgepole/extension.rb
module UnsingedReferences
def references(*args)
options = args.extract_options!
options[:unsigned] = true
args.push(options)
super(*args)
end
Ridgepole::DSLParser::TableDefinition.prepend(self)
end
module WithDefaultTableOptions
DEFAULT_TABLE_OPTIONS = {
id: :unsigned_bigint,
options: YAML.safe_load(
ERB.new(File.read('config/database.yml')).result,
[],
[],
true
)[Rails.env]['table_options']
}.freeze
def create_table(table_name, options = {})
super(table_name, DEFAULT_TABLE_OPTIONS.dup.merge(options))
end
Ridgepole::DSLParser::Context.prepend(self)
end
ネームスペースが残るのが気になる方は下記のようにすると無名モジュールがモンキーパッチを当ててくれるはずです。(こちらは動作未確認です)
# lib/ridgepole/extension.rb
Module.new do
def references(*args)
options = args.extract_options!
options[:unsigned] = true
args.push(options)
super(*args)
end
Ridgepole::DSLParser::TableDefinition.prepend(self)
end
Module.new do
DEFAULT_TABLE_OPTIONS = {
id: :unsigned_bigint,
options: YAML.safe_load(
ERB.new(File.read('config/database.yml')).result,
[],
[],
true
)[Rails.env]['table_options']
}.freeze
def create_table(table_name, options = {})
super(table_name, DEFAULT_TABLE_OPTIONS.dup.merge(options))
end
Ridgepole::DSLParser::Context.prepend(self)
end
終わりに
モンキーパッチなのでRidgepoleの挙動が壊れてDDLの内容が変わったらどうするんだ!という方はRidgepoleが発行するDDLをGitHub PR上で確認できるようにすることができるので、Ridgepoleのバージョンアップ時にこちらで気づくことができるかと思います!
今回紹介したUNSIGNEDを暗黙的に付与する方法は会社の先輩であるalpaca_tcが教えてくれたものでした。いつもありがとうございます🙏
Discussion