rake taskを使ったデータベース移行を行ったので記録として書いておこうと思います。ruby初心者なので後々振り返られるようになるべくやったこととエラーを時系列で書いていこうと思います。
#やりたいこと
現状では同じページ内で扱っているデータが違うテーブル内にあることからviewが乱雑になりがちになっていました。そこで今回はDBのテーブルをきれいに整理すると同時に新たにpriorityというカラムを作ってそのpriorityベースでデータを配列内で管理するための下準備を行いたいと思っています。ざっくり言えばrake taskを使ってのデータベースの移行。移行にあたってテーブル名やレコードの値の変更などです。
#やらないといけないこと
具体的には:
- データベースにpriorityコラムの追加
- モデルの書き換えとpriorityの反映、validationの確認
- rake_taskの作成
- controllerの編集priorityに1がついているものを優先的にもってくる
- viewの編集
くらいだと思います。DBからviewにそって作業を行っていけば途中でエラーが起きても対処しやすく確認作業も行いやすいのではと思ったことがこの順番にした主な理由です。
Railsでカラムのデータ型を変更する場合の手順が今回の作業を進めていく上で非常に参考になりました。
#データベースにpriorityコラムの追加
参考にしたリンク:
rails g migration AddPriorityToCategory_Contents priority:integer
db/migrate内に新たに出来たファイルに
class AddPriorityToCategoryContents < ActiveRecord::Migration
def change
add_column :category_contents, :priority, :integer
end
end
がちゃんと書き込まれていることを確認した後、rake db:migrate
を実行。ちなみにmysql.server start
コマンドでサーバーをオンにしていないとエラーが起きます。
実際にmysqlにログインして確認してみる。
#モデルの書き換えとpriorityの反映、validationの確認
このへんを参考にしながら
class CategoryContent < ActiveRecord::Base
belongs_to :category
validates :category_id, presence: true
validates :title, presence: true
validates :content, presence:true
validates :is_public, presence: true, inclusion: {in: [true, false] }
validates :priority, presence: true, numericality: {only_integer: true}
end
に書き換える。
#rake_taskの作成
この辺を読みながら作ってみました。
- Rake タスクの作り方(引数を複数設定してみる)
-
Rakeタスクをつくる
migration task関係の記事を見つけたので以下も参考になるかと思います。 - Rails で信頼性の高い Migration を書くには
やりたいことは:
- category_path_contentsからcategory_contentsへの移行。
-
description
の個数は必ず1かdescription
自体がそもそも存在しない(=nil) -
text
の個数は複数もしくはそもそも存在しない - 移行の際、テキストデータが
.description
のものであればpriority
を1に。そうではない.text
のものであればpriority
を0に
##DBについて考えたこと
text
とdesc
をそれぞれ統合して一つの要素を作るというよりはtext
という要素とdesc
を持った要素が数に応じて存在しておりそれぞれcategory_id
によって紐付けされているという感覚です。理由はmysqlデータベースの形として出来るだけ行(カラムの数)を増やすのではなく列を増やすことによってデータベースへの負担や長期的に起きうる不安要素を事前に排除できるからです。具体的には:
{category_id:1, description:hoge, text1:hoge, text2:hoge}
だと第一にtext
が何個入ってくるかわからないためそれに対応して新たにコラムを作ることになってしまいます。またレコードによってはそのコラムが必要無い場合もあるわけです。つまり
category_id | description | text1 | text2 |
---|---|---|---|
1 | hoge | hogehoge | hogehogehoge |
1 | nil | hogehoge | nil |
2 | hoge | nil | nil |
3 | hoge | hoge | hoge |
あまりきれいな形ではありませんね。そこで
category_id | description |
---|---|
1 | hoge |
2 | hog |
3 | hoge |
というdescription
を管理するテーブルと
category_id | text |
---|---|
1 | hoge |
1 | hoge |
1 | hoge |
2 | hoge |
というtext
を管理するテーブルを別々に用意してあげれば、数に応じて新しくレコードを増やすだけなので負担が少なく済みます。
詳しい解説は達人に学ぶDB設計徹底指南書を参考にすると良いかもしれません。
##スクリプト
desc "migrate category to new db"
task migrate_category: :environment do
GakkiManiaCategoryPathContent.find_each do |gmc|
if gmc.text != nil
gmc.public_state = true
else
gmc.public_state = false
end
CategoryContent.create(
category_id: gmc.id,
title: gmc.headline,
content: gmc.text,
is_public: gmc.public_state
)
end
GakkiManiaCategoryPathSetting.find_each do |gms|
if gms.text != nil
gms.public_state = true
else
gms.public_state = false
end
CategoryContent.create(
category_id: gms.id,
title: gms.name,
content: gms.description,
is_public: gms.public_state
)
end
end
end
end
end
bundle exec rspec
でテストしてみる。
#エラーと対処
問題: rake_taskプログラムは動いているのにDBに反映されていない。
原因: modelでデータを判別をしている際にvalidation
周辺に引っかかっているのではないか。
validates :category_id, presence: true
validates :title, presence: true
validates :content, presence:true
validates :is_public, presence: true, inclusion: {in: [true, false] }
##実際に行ってみたこと:
実際にputs gmc.is_public
でコンソールに流してみると全て0
だった。色々辿っていくと旧DBではレコードがINT扱いに対して新DBではブーリアンに変わっていることに気づく。そこでenum public_state: {draft: 0, closed: 10, published: 20}
を参考にenum
辺りが理解できれば直るかもしれないとのこと。(enumについてはいまさらながらRails4.1から導入されたEnumが便利なのでまとめてみたを参考にしました。)
データベースのレコードへのアクセスの仕方に関しては、
- データベースのデータを表示する。
- Ruby on Rails Tutorial: Creating a Rails Instance from an Existing MySQL DB
- rails 既存DBを使ってrailsしてみた。の「モデルのテーブル名を変更」について書かれているあたり。
- 【Rails初心者必見!】ひたすら丁寧にデータ取得を説明(find, where)
が参考になると思います。
原因はブーリアンを持ってくるはずのis_public
になぜか数字が送られてきていたためでしたが。。