😺

知らなきゃ実務でヤバイ❗️Railsでカラムの追加と削除の方法(誤ってカラム追加の対処法など実務ベースで解説)

2023/12/16に公開

概要

カラムを追加したり削除する機会があると思います。
その際に新たにカラムを変更するためのマイグレーションスクリプトを作成します。
少しややこしいとこなので解説していきます。

カラム追加

まずカラムの追加方法からです。以下のコマンドを使ってマイグレーションファイルを作成します。

rails generate migration AddColumnNameToTableName column_name:data_type

実際の例を出して見てきます。

rails generate migration AddEmailToUsers email:string

するとdb/migrate/YYYYMMDDHHMMSS_add_email_to_users.rbのファイルができます。

class AddEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :email, :string
  end
end

その後マイグレーションを実行します。

rails db:migrate

カラム削除

カラムの削除方法をお伝えしていきます。マイグレーションファイルの作成のやり方です。

rails generate migration RemoveColumnNameFromTableName column_name:data_type

今回はDailyReportモデルのtimestampsのカラムの削除方法です。

rails generate migration RemoveTimestampsFromDailyReport timestamps:datetime

このコマンドを実行すると以下のファイルができました。db/migrate/YYYYMMDDHHMMSS_remove_timestamps_from_daily_report.rbのようなファイルができるはずです。

class RemoveTimestampsFromDailyReport < ActiveRecord::Migration[7.0]
  def change
    remove_column :daily_reports, :timestamps, :datetime
  end
end

カラムの削除コマンドを使うと以下の削除用のマイグレーションファイルができます。

class RemoveColumnNameFromTableName < ActiveRecord::Migration[6.0]
  def change
    remove_column :table_name, :column_name, :data_type
  end
end

その後にマイグレーションを実行します。

rails db:migrate

実務でどう使ったか

実際に実務でこんなようなことがありました。
curlでAPIにデータを取得すると

 id: 1,
  user_id: 1,
  timestamps: null,
  deleted_at: nil,
  created_at: 〇〇,
  updated_at: 〇〇,

みたいなデータがありました。timestampsがnullになっているのがおかしかったので、
これをカラムから削除しました。

自分は最初上の削除のやり方でカラムを削除しました。するとこんなレビューをもらいました。
まだ本番環境で公開していないのでuserのmigrationのようにrollbackをして削除でOKでいいですよ!」
というレビューでした。

rails db:rollback

DBのマイグレーションを一つ前の状態に戻すためのコマンドで、DBのスキーマ変更
テーブルの作成、削除などの操作を実行するために使われます。

これで不要なカラムが削除されました。

リリースした環境でrollbackはNG❗️

リリース済みの環境でDBのカラムを削除する場合rails db:rollback使用することは通常避けるべきです。以下はその理由です:

  1. データの損失
    カラムを削除すると、そのカラムに関連するデータが消える場合があり、
    本番環境ではすでに多くのデータがあるため、データの損失になりかねません。

2.データベースの一貫性が無くなる
アプリケーションのコードが削除したカラムを参照している時、それを削除することでエラーが発生する可能性があります。

3.デプロイの複雑性
ロールバックは通常、開発やテスト環境での使います。
本番環境ではすでに多くのユーザーが利用しているため、デプロイの途中で予期せぬ問題が発生する可能性があり、サービスが落ちてしまう可能性があります。

リリースした環境でrollbackを使ったケース

実務でユーザーが〇〇の購入日(最初に購入した日)を画面に表示させるタスクを任されました。
〇〇モデルにpurchase_dateカラムを追加しました。(上のrailsコマンドでカラムを追加)
しかし、purchase_dateカラムを追加せずとも、created_atが購入日という指摘をもらいました。
直接migrate(年日付_add_〇〇_to_〇〇.rbみたいなファイル)を直接削除してしまうと、
カラムがDB上に残ってしまうので、違うやり方でやることにしました。
(すでに rails db:migrateは実行していた)
なので、まずは以下のコマンドでmigrationの状況を確認します。

rails db:migrate:status
#中略
  up     年月日                   Add 〇〇 to 〇〇
  up     年月日                    Add 〇〇 to 〇〇
  up     年月日                   Add 〇〇 to 〇〇
 up     20240206152613  Add purchase date to 〇〇

この状態だと、カラムがmigrationに反映された状態です。1番下に表示されていて、
最新版でした。そこでrollbackを使って前の状態に戻します。

rails db:rollback

再度以下のコマンドを実行します。

rails db:migrate:status
#中略
  up     年月日                   Add 〇〇 to 〇〇
  up     年月日                    Add 〇〇 to 〇〇
  up     年月日                   Add 〇〇 to 〇〇
 up     20240206152613  Add purchase date to 〇〇

これでpurchase_dateカラムはmigrationに反映されなくなりました。

rollback補足

上のrollbackは1つ前のrollbackですが、下のコマンドは2つ前と3つ前のrollbackです。
STEP=を使うとこが違うとこです。

rails db:rollback STEP=2
rails db:rollback STEP=3

null falseの追加

マイグレーションが以下のようにnull falseがない状態になってしまいました。
その際に、null falseを追加しました。

class CreateArticles < ActiveRecord::Migration[7.0]
  def change
    create_table :articles do |t|
      t.string :title,null false
      t.text :content,null false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end
  • 補足
    null falseは記事にタイトルと文章がnull(タイトルと文章を書かないといけない)になってはいけない設定です。
    修正後マイグレーションを実行します。
rails db:migrate

実行後コンソールに入ります。

$ rails c

以下のようなコマンドで、falseが出たら設定ができたということです。

Article.columns_hash["title"].null # => false
Article.columns_hash["content"].null # => false
  • schema.rbを確認
    ここで設定されていたらOKです。
ActiveRecord::Schema[7.0].define(version: 2024_07_19_152440) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "articles", force: :cascade do |t|
    t.string "title", null: false
    t.text "content", null: false
    t.bigint "user_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["user_id"], name: "index_articles_on_user_id"
  end

資料

https://qiita.com/azusanakano/items/a2847e4e582b9a627e3a

Discussion