6
7

【Rails】テーブルを作り直すときのMigrationがややこしかった

Posted at

はじめに

本記事では、業務でテーブルを作り直したいときにややこしかったことについて紹介していきます。

背景

開発を行なっていた際に、過去作成されていたテーブル名が機能改修後の機能と意味合いがずれており、今後開発で触れる時にわかりづらいということでテーブル名を変更したいと考えました。

  1. 既存のテーブルのテーブル名を変更したい
  2. 既存のテーブルにはリレーション関係がある
    a. 子テーブルのテーブル名も親テーブルと合わせて変更したい
    b. primary_keyやforeign_keyも変更する必要がある

→ テーブルを作り直した方がわかりやすいということで、作り直すことになりました。

ややこしかったところ

ややこしかったところは、下記で述べる「OldTableクラスとChildOfTableクラスをMigrationFileで定義しなくていけない理由はわかったけど、NewTableクラスとChildOfNewTableクラスは定義しなくていい」という点です。順を追って説明していきます。

以下のコードはレビューをいただく前のコードです。

migration_file
class ChangeOldTableToNewTable < ActiveRecord::Migration[version]
  def change
    reversible do |r|
      r.up do
        create_table :new_table, primary_key: :new_table_id do |t|
          ...
        end
        add_foreign_key ...

        create_table :child_of_new_table, primary_key :child_of_new_table do |t|
          ...
        end
        add_foreign_key ...

        ActiveRecord::Base.transaction do
          OldTable.all.each do |old|
            # NewTableとChildOfNewTableへレコードをコピー
            ...
          end
        end

        drop_table :old_table
        drop_table :child_of_old_table
      end

      r.down do
        # upと逆の処理
        ...
      end
    end
  end
end

このコードには以下の問題点がありました。
問題点)私以外のエンジニアがmigrateするときにOldTableクラスとChildOfTableクラスが存在しないことです。

↓ 整理すると...

// 子テーブルも同様
<開発前>
OldModelFile
OldTable
MigrationFileなし
↓
<開発中>
OldModelFile
NewModelFile
OldTable
MigrationFileあり
↓
<開発完了時>
// OldModelFileがない -> OldTableクラスがないため、OldTable.all.eachのような処理が書けない
NewModelFile
OldTable
MigrationFileあり
↓
<migrationする>
NewModelFile
NewTable
MigrationFileあり

そのため、MigrationFile内でクラスを定義する必要があります。結論、この時定義するのはOldTableクラスとChildOfOldTableクラスのみで大丈夫です。

migration_file
class ChangeOldTableToNewTable < ActiveRecord::Migration[version]
  class OldTable < ApplicationRecord
    self.table_name = :old_table
    self.primary_key = :old_table_id

    has_many ...
  end

  class ChildOfOldTable < ApplicationRecord
    self.table_name = ...
    self.primary_key = ...

    belongs_to ...
  end

  def change
    ...
  end
end

しかし、レビューをいただいた時は「NewTableクラスとかは定義しなくていいのはなんで??」と混乱しました。こういう時は実際に書き出してみて整理してみることが有効です。

<rollbackする>
NewTableModelFile
OldTable
MigrationFileあり
↓
<開発コミットを消す>
OldTableModelFile
OldTable
MigrationFileなし

整理してみたことで、rollbackする時にNewTableクラスとChildOfNewTableクラスは存在しているから定義する必要がないということがわかりました。

おまけ

開発に入った当初、開発経験があまりなく分からないことだらけでした。その内の1つを紹介します。

・migrationする度にbase-branchとの間でschemaに変更した覚えのない差分が生じる問題
問題点)
schemaがズレることで起こることとして以下のことが挙げられます。

  1. 本物のDB情報がわからなくなる
  2. git(主にステージング)が面倒くさくなる
  3. テストが通らなくなる

原因)前の開発等でDBの情報を変更し、そのまま次の開発に入ったから

開発でDBの情報を変更
↓
そのまま次の開発へ ← 原因はここ
↓
migration
↓
shemaにはDBの情報が反映される
↓
base-branchと差分が生じる

解決策)DBの情報をbase-branchに合わせれば良いので下記のようなSQL文を叩いてDBの情報を更新します。

よく使うコマンドtips
-- テーブル削除(migration:rollbackでもいい)
drop table <テーブル名>;

-- カラム追加(NOT NULL制約をつけたい時はデータ型の後ろにnot nullをつける)
alter table <テーブル名> add column <カラム名> <データ型> after <追加するカラムの上にあるカラム名>;

-- カラム削除
alter table <テーブル名> drop column <カラム名>;

-- カラム名の変更
alter table <テーブル名> change column <変更前のカラム名> <変更後のカラム名> <データ型>;

-- カラムの順番修正(この方法でできないときは、カラム削除とカラム追加を組み合わせればいい)
alter table <テーブル名> modify <カラム名> <データ型> after <移動したい場所の上にあるカラム名>;

-- インデックス追加(unique制約をつけたい時はaddの後ろにuniqueをつける)
alter table <テーブル名> add index <インデックス名> (<カラム名1>, <カラム名2>, ...);

-- インデックス削除
alter table  <テーブル名> drop index <インデックス名>;

-- string型のカラムにlimit付与(例はカラム追加時)
alter table <テーブル名> add column <カラム名> varchar(100);
--=> schema: t.string "カラム名", limit: 100

-- デフォルト値の追加
alter table <テーブル名> alter <カラム名> set default <デフォルト値>;

-- not nullの追加
alter table <テーブル名> modify <カラム名> <データ型> not null;

おわりに

最後まで読んでくださりありがとうございました!
私自身頭の中でイメージして考えることが苦手なので、このように書き出して整理してみることが有効であると考えています。メンバーの良いところを真似しつつ、自分に合った開発の仕方を模索していくことが大切であると日々感じています。

ご指摘等ございましたら、コメントなどで教えてください!

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7