マイグレーションで外部キーを設定する2通りの方法

In: Ruby / Rails| technology

6 5月 2008

マイグレーション、やればやるほど便利ですね。
単純に、テーブル操作だけじゃなくて、ファイル保存先の変更とか、内部仕様の変更に対しても使えそうな印象を受けます。

基本的なやり方・書き方は、巷にあふれかえっていたので、そこは調べていただくとして。
今回は、特に、マイグレーションでの外部キーの設定の仕方について、メモ。

マイグレーション、基礎

そもそも、マイグレーションってどうやるの?って人は、以下のところを参照されるとわかりやすいかと思います。

1 : SQLを利用する場合

ひとつめのやり方として、外部キーの設定のところだけ、SQLで呼び出すやり方があります。
たとえば、こんな感じ。

  def self.up
    options = {
      :force => true,
      :options => "ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;"
    }
    create_table(:images, options) do |t|
      t.column(:user_id, :integer, :null => false)
      t.column(:image, :string, :null => false)
      t.column(:comment, :text)
      t.column(:created_at, :timestamp)
      t.column(:updated_at, :timestamp)
    end
    execute "alter table images add constraint fk_images_users foreign key (user_id) references users(id)"
  end

ユーザーがアップロードした画像を管理するテーブルのイメージでお願いします。
各ユーザーに画像のデータがぶら下がってる感じです。

とはいえ、こんなんじゃ、外部キーを設定しているのはわかるけど、いまいちわかりにくいですね。
それに、外部キーを設定するのはこれ一個とは限らないので、外部キーの設定のところだけ汎用化してしまいたいところです。

1′ : SQLを利用する場合・改

汎用化したいので、libフォルダ直下に、新しくヘルパー用のクラスを作ってしまいます。
参照元の丸パクリで恐縮ですが、MigrationHelperというクラス名で作ってみましょう。

module MigrationHelper

  # 外部キー作成メソッド.
  # migrationで外部キーを実行する場合のSQLを見えないようにして、migrationファイルの可視性アップを狙う.
  def foreign_key(from_table, from_column, to_table)
    constraint_name = "fk_#{from_table}_#{to_table}"
    
    execute "alter table #{from_table} add constraint #{constraint_name} foreign key (#{from_column}) references #{to_table}(id)"
  end

end

で、さっきのマイグレーション用のクラスのソースをこう直します。

require 'migration_helper'
class CreateImages < ActiveRecord::Migration

  # 外部キー用のモジュールを使えるように拡張する.
  extend MigrationHelper

  # imagesテーブルを作成する.
  def self.up
    options = {
      :force => true,
      :options => "ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;"
    }
    create_table(:images, options) do |t|
      t.column(:user_id, :integer, :null => false)
      t.column(:image, :string, :null => false)
      t.column(:comment, :text)
      t.column(:created_at, :timestamp)
      t.column(:updated_at, :timestamp)
    end
    foreign_key(:images, :place_id, :places)
  end

  # imagesテーブルを削除する.
  def self.down
    drop_table :images
  end
end

注意するのは、requireとextendのところを忘れないこと。
でないと、NoMethodErrorとかになります。

これで、見た目直感的で、保守がしやすそうなソースになりましたね。

2 : プラグインを利用する場合

中には、それプラグインで、という人もいらっしゃるかと思われますので、ふたつめのやり方としてそちらをばご紹介。

結論から言えば、Foreign Key Migrationsというプラグインがございます。
これは、詳しくはリンク先のページ(英語)もしくは、日本語訳されてるこちらを当たってもらえればと思いますが、railsの規約に沿っていれば、外部キーの設定は書かなくても、勝手に外部キーの設定もするよ、というステキプラグインです。

なので、さっきのソースで行くと、これだけで同じことができてしまいます。

  def self.up
    options = {
      :force => true,
      :options => "ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;"
    }
    create_table(:images, options) do |t|
      t.column(:user_id, :integer, :null => false)
      t.column(:image, :string, :null => false)
      t.column(:comment, :text)
      t.column(:created_at, :timestamp)
      t.column(:updated_at, :timestamp)
    end
  end

ちなみに、このプラグイン。
Foreign Key MigrationsRedHill on Rails Coreという二つのプラグインをインストールしないと動きませんので、インストールする場合はご注意ください。

また、使ってみてわかりましたが、テーブルをdropしようとすると、エラーになってうまく消せないみたいです。
(※ただし、MySQL Administratorとかのアプリからdropする分には、何も問題がありません)
原因を深く追いかけなかったのでアレなんですけど、スキーマをダウングレードするのもちゃんとできなきゃイヤ! って人には向かないかと思われます。。

個人的には

1′のやり方が一番しっくり来ますね。
何でもかんでもプラグイン頼みしてしまうのは、それだけブラックボックスなソースを受け入れることでもあるし、考え物なのかもな、と、この一件で思ってしまったので。。。

Via:


2 Responses to マイグレーションで外部キーを設定する2通りの方法

Avatar

Red cat

7月 27th, 2012 at 12:49:09

ここに書かれている 1′ の方法で試したのですが、rake db:migrate を実行すると foreign_key が undefined method だと言われてテーブルが作成できません。

Avatar

Red cat

7月 27th, 2012 at 15:26:04

マイグレーションファイルで module を extend で呼び出しているところは include にしないと駄目なようです。しかし sqlite3 には外部キー制約がないため syntax error となってしまいました。この方法による外部キー制約の付与はあきらめます。

Comment Form

About this blog

ゆるーく、ふわーっと、興味のままに。

自分のかたわらに置いておくメモ代わり。

Photostream