Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@weedslayer

rails初心者がデータベース移行試みたのでまとめてみた。

rake taskを使ったデータベース移行を行ったので記録として書いておこうと思います。ruby初心者なので後々振り返られるようになるべくやったこととエラーを時系列で書いていこうと思います。

やりたいこと

現状では同じページ内で扱っているデータが違うテーブル内にあることからviewが乱雑になりがちになっていました。そこで今回はDBのテーブルをきれいに整理すると同時に新たにpriorityというカラムを作ってそのpriorityベースでデータを配列内で管理するための下準備を行いたいと思っています。ざっくり言えばrake taskを使ってのデータベースの移行。移行にあたってテーブル名やレコードの値の変更などです。

やらないといけないこと

具体的には:
- データベースにpriorityコラムの追加
- モデルの書き換えとpriorityの反映、validationの確認
- rake_taskの作成
- controllerの編集priorityに1がついているものを優先的にもってくる
- viewの編集

くらいだと思います。DBからviewにそって作業を行っていけば途中でエラーが起きても対処しやすく確認作業も行いやすいのではと思ったことがこの順番にした主な理由です。

Railsでカラムのデータ型を変更する場合の手順が今回の作業を進めていく上で非常に参考になりました。

データベースにpriorityコラムの追加

参考にしたリンク:
- 新しいマイグレーションを追加してテーブルを変更
- ruby on railsのデータベースのテーブルにカラムを追加、削除する

rails g migration AddPriorityToCategory_Contents priority:integer

Screen Shot 2016-10-31 at 11.05.26 AM.png

db/migrate内に新たに出来たファイルに

class AddPriorityToCategoryContents < ActiveRecord::Migration
  def change
    add_column :category_contents, :priority, :integer
  end
end

がちゃんと書き込まれていることを確認した後、rake db:migrateを実行。ちなみにmysql.server startコマンドでサーバーをオンにしていないとエラーが起きます。

Screen Shot 2016-10-31 at 11.08.37 AM.png

実際にmysqlにログインして確認してみる。

Screen Shot 2016-10-31 at 11.09.54 AM.png

モデルの書き換えと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について考えたこと

textdescをそれぞれ統合して一つの要素を作るというよりは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周辺に引っかかっているのではないか。

category_content.rb
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が便利なのでまとめてみたを参考にしました。)

データベースのレコードへのアクセスの仕方に関しては、

が参考になると思います。

原因はブーリアンを持ってくるはずのis_publicになぜか数字が送られてきていたためでしたが。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?