テーブルのカラムを別テーブルのカラムに移す
テーブルのカラムを別テーブルのカラムに移す
あるカラムを他のテーブルのカラムに移したときのメモです。
やりたいこと
brands
テーブルが持っている shop_id
カラムを消して、shops
テーブルに brand_id
を持たせたい。
現状
brands
テーブルが shop_id
を持っていて、brands
テーブルと shops
テーブルは 1対1
の関係です。
# db/schema.rb
ActiveRecord::Schema.define(version: 20190000000000) do
create_table "brands", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ブランド" do |t|
t.bigint "shop_id", null: false
t.string "address", limit: 255, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["shop_id"], name: "index_brands_on_shop_id"
end
create_table "shops", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗" do |t|
t.string "name", limit: 255, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "brands", "shops", name: "fk_brands_on_shop_id", on_update: :cascade
end
理想
shops
テーブルが brand_id
を持っていて、brands
テーブルと shops
テーブルの 1対1
の関係は現状のままにしたい。
# db/schema.rb
ActiveRecord::Schema.define(version: 20190000000000) do
create_table "brands", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "位置情報" do |t|
- t.bigint "shop_id", null: false
t.string "address", limit: 255, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["shop_id"], name: "index_brands_on_shop_id"
end
create_table "shops", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗" do |t|
+ t.bigint "brand_id", null: false
t.string "name", limit: 255, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.index ["brand_id"], name: "index_shops_on_brand_id"
end
- add_foreign_key "brands", "shops", name: "fk_brands_on_shop_id", on_update: :cascade
+ add_foreign_key "shops", "brands", name: "fk_shops_on_brand_id", on_update: :cascade
end
良くなかった解決方法
マイグレーションファイル内でカラムの追加と同時に、shops
テーブルの brand_id
カラムにデータを入れました。
下のコードでとりあえず、やりたかったことはできました。
# rails_app/db/migrate/20190514062242_move_brand_shop_id_column_to_shop_brand_id_column.rb
class MoveBrandShopIdColumnToShopBrandIdColumn < ActiveRecord::Migration[5.1]
def up
add_column :shops, :brand_id, :bigint, after: :id
add_index :shops, :brand_id, name: :index_shops_on_brand_id
add_foreign_key :shops, :brands, name: :fk_shops_on_brand_id, on_update: :cascade
Shop.all.each do |shop|
shop.update!(brand: Brand.find_by(shop: shop))
end
change_column :shops, :brand_id, :bigint, null: false
remove_foreign_key :brands, :shops
remove_index :brands, :shop_id
remove_reference :brands, :shop
end
def down
add_column :brands, :shop_id, :bigint, after: :id
add_index :brands, :shop_id, name: :index_brands_on_shop_id
add_foreign_key :brands, :shops, name: :fk_brands_on_shop_id, on_update: :cascade
Brand.all.each do |brand|
brand.update!(shop: Shop.find_by(brand: brand))
end
change_column :brands, :shop_id, :bigint, null: false
remove_foreign_key :shops, :brands
remove_index :shops, :brand_id
remove_reference :shops, :brand
end
end
何が問題だったのか
マイグレーションが失敗してしまうかもしれない
$ rails db:migrate:reset
したときに操作しようとしたモデル(ブランド、店舗)を今後使わなくなって削除した場合、このマイグレーションが失敗してしまう。
時間がかかる
このマイグレーションを実行するたびに店舗とブランドの全データを更新する時間がかかる。
解決方法
Rake
タスクでデータを移すなど、マイグレーションファイル以外でデータの変更を行うようにします。
リリースを 2 回に分ける必要があります。
1 回目のリリース
shops
テーブルに brand_id
を追加する。
# rails_app/db/migrate/20190514062242_add_brand_id_column_to_shops.rb
class AddBrandIdColumnToShops < ActiveRecord::Migration[5.1]
def change
add_column :shops, :brand_id, :bigint, after: :id
add_index :shops, :brand_id, name: :index_shops_on_brand_id
add_foreign_key :shops, :brands, name: :fk_shops_on_brand_id, on_update: :cascade
end
end
カラムのデータを移動させる Rake
タスクを作成する。
# lib/tasks/shop.rake
namespace :shop do
desc 'Shop に brand_id 挿入'
task add_brand_id: :environment do
Shop.all.each do |shop|
shop.update!(brand: Brand.find_by(shop: shop))
end
end
end
Rake
タスクを実行する。
$ bundle exec rails shop:add_brand_id
2 回目のリリース
shops
ターブルの brand_id
カラムの null
を許可しないようにする。
brands
テーブルの shop_id
カラムを消す。
# rails_app/db/migrate/20190514062242_move_brand_shop_id_column_to_shop_brand_id_column.rb
class MoveBrandShopIdColumnToShopBrandIdColumn < ActiveRecord::Migration[5.1]
def change
change_column :shops, :brand_id, :bigint, null: false
remove_foreign_key :brands, :shops
remove_index :brands, :shop_id
remove_reference :brands, :shop
end
end
まとめ
マイグレーションファイル内にテーブルのデータを変更する処理を書いてしまうと変更対象のテーブルを削除した場合、 マイグレーションが失敗してしまいます。マイグレーションファイル内にテーブルのデータを変更するような処理は書かないようにしましょう。