ActiveRecord 4.1+MySQLで任意のカラムをPKにする


ActiveRecord 4.1+MySQLで、特別な拡張を入れなくても任意の名前・型のカラムをPKにできたのでメモしておきます。

書き方

名前だけ変える

primary_key: 'my_id'

create_table "items", primary_key: 'my_id', force: true do |t|
  ...
end

型だけ変える

id: 'bigint primary key auto_increment'

create_table "items", id: 'bigint primary key auto_increment', force: true do |t|
  ...
end

名前・型を変える

primary_key: 'my_id', id: 'bigint primary key auto_increment'

create_table "items", primary_key: 'my_id', id: 'bigint primary key auto_increment', force: true do |t|
  ...
end

ダンプ

ダンプすると名前の情報は出力されますが型の情報(id: xxx)は出力されません。

create_table "items", primary_key: "my_id", force: true do |t|
  ...
end

解説

create_tableでPKの定義を生成しているのが以下の箇所:

schema_statements.rb#L190

unless options[:id] == false
  pk = options.fetch(:primary_key) {
    Base.get_primary_key table_name.to_s.singularize
  }

  td.primary_key pk, options.fetch(:id, :primary_key), options
end

:idfalseでないとき、:primary_keyの値を名前、:idの値を型としてPKを定義しています。

MySQLのネイティブな型の定義は以下の通り:

abstract_mysql_adapter.rb#L152

NATIVE_DATABASE_TYPES = {
  :primary_key => "int(11) auto_increment PRIMARY KEY",
  :string      => { :name => "varchar", :limit => 255 },
  :text        => { :name => "text" },
  :integer     => { :name => "int", :limit => 4 },
  :float       => { :name => "float" },
  :decimal     => { :name => "decimal" },
  :datetime    => { :name => "datetime" },
  :timestamp   => { :name => "datetime" },
  :time        => { :name => "time" },
  :date        => { :name => "date" },
  :binary      => { :name => "blob" },
  :boolean     => { :name => "tinyint", :limit => 1 }
}

実際にSQLを発行するときはtype_to_sqlメソッドでネイティブな型に変換していますが

if native = native_database_types[type.to_sym]
  ...
else
  type.to_s
end

native_database_typesにないものはそのままSQLの型として使われるので、:idに「任意の型」+primary keyを設定すると、任意の型でPKを作成できます。