マイグレーションについて

#はじめに
先週からエンジニアとして働き始めました。

といってもずっとgitの使い方でつまづいていて何もしてないんですが....


マイグレーションに関して少し指摘を受けて忘れないように忘備録として残します。

自分は結構おっちょこちょいなのでうっかり大切なデータ消しちゃったとかやりそうなのでマイグレーションとかは特に気を付けなければ…

何か間違っている点やアドバイスなどがあればコメントしていただけるととても助かります。

#マイグレーションファイルの作り方

マイグレーションファイルを作成するときはあんま何も考えず

> とりあえず変更出来てればよいか…

位の考えだったのですが実際はそんなことはなく

 
 
 
```
rails db:migrate
rails db:rollback
```

が問題なく実行できることを確認する必要があるようです。


理由に関してはあんまりうまく理解できなかったのですが....


#マイグレーションの流れ

マイグレーションの流れとしては


まず

```
rails db:migrate:status
```

で今の状況を確認します

そうするとこんな感じに履歴が見れます


```sql
database: tt_db

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190224051828  Create users
   up     20190224052935  Add address to users
   up     20190224053300  Change address column
   up     20190224054338  Add birthday not null
   up     20190224055456  Add good to users
```

このupというのはmigrationされた状態ということ

今では5個のマイグレーションファイルがありすべてマイグレーションが実行されている状態であることが分かります。


そしてそれを確認したら次に

```
rails db
```

でデータベースにアクセスします。

そして

```sql
select * from schema_migrations;
```

を実行します

そうすると

```sql
tt_db=> select * from schema_migrations;
    version
----------------
 20190224051828
 20190224052935
 20190224053300
 20190224054338
 20190224055456
(5 rows)
```

こういった感じでマイグレーションファイル情報が出てきます。


これはrails db:migrate:statusで出てきたupの情報と一致しているはずです。


次に現在のテーブル定義を確認しておきます

```sql
tt_db=> \d users;
                                           Table "public.users"
     Column      |            Type             | Collation | Nullable |              Default
-----------------+-----------------------------+-----------+----------+-----------------------------------
 id              | bigint                      |           | not null | nextval('users_id_seq'::regclass)
 name            | character varying           |           |          |
 email           | character varying           |           |          |
 password        | character varying           |           |          |
 created_at      | timestamp without time zone |           | not null |
 updated_at      | timestamp without time zone |           | not null |
 address         | text                        |           |          |
 birthday        | date                        |           |          |
 password_digest | character varying           |           |          |
 good            | character varying           |           |          |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
```

こんなかんじでpostgresqlの場合は\d テーブル名でテーブルの情報が確認できます。


そして次に\qでデータベースから離脱してマイグレーションファイルを作成していきます。

今回はheightという身長を保存するカラムを作成していきます。

その場合は

```
rails g migration add_height_to_users
```

これを実行

そうするとdb/migrate以下にマイグレーションファイルがこんな感じで作成されているはずです。


```ruby 
class AddHeightToUsers < ActiveRecord::Migration[5.2]
  def change
  end
end
```

ここに変更する内容を書いていきますがrails db:migrate rails db:rollbackを確実に実行できるようにしていくために

```ruby 
class AddHeightToUsers < ActiveRecord::Migration[5.2]
  def up
　　　add_column :users, :height, :string
  end

  def down
     remove_column :users, :height
  end 
end
```

みたいにmigrateされるときとrollbackされるときの処理を別々に書いていきます。


rails db:migrateを実行する前に現在のマイグレーションファイルの状態をrails db:migrate:statusで確認します

そうすると

```
database: tt_db

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190224051828  Create users
   up     20190224052935  Add address to users
   up     20190224053300  Change address column
   up     20190224054338  Add birthday not null
   up     20190224055456  Add good to users
  down    20190224063318  Add height to users
```

こんな感じになっているはずです。

この最後のdownがいま作ったマイグレーションファイルです。

これを確認したらrails db:migrateを実行します

成功するのでrails dbでデータベースにアクセスして問題なく変わっているか確認します。


```
tt_db=> \d users;
                                           Table "public.users"
     Column      |            Type             | Collation | Nullable |              Default
-----------------+-----------------------------+-----------+----------+-----------------------------------
 id              | bigint                      |           | not null | nextval('users_id_seq'::regclass)
 name            | character varying           |           |          |
 email           | character varying           |           |          |
 password        | character varying           |           |          |
 created_at      | timestamp without time zone |           | not null |
 updated_at      | timestamp without time zone |           | not null |
 address         | text                        |           |          |
 birthday        | date                        |           |          |
 password_digest | character varying           |           |          |
 good            | character varying           |           |          |
 height          | character varying           |           |          |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
```

こんな感じで最後にheightカラムが作成されています。


ついでに

　
select * from schema_migrations;で問題なく情報が一つ増えていることを確認します。

```sql
tt_db=> select * from schema_migrations;
    version
----------------
 20190224051828
 20190224052935
 20190224053300
 20190224054338
 20190224055456
 20190224063318
(6 rows)
```

マイグレーションを実行する前は5rowsだったので問題なくできています。

そしたらデータベースから離脱して

rails db:migrate:statusでupが6つになっていることを確認します。


```
database: tt_db

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190224051828  Create users
   up     20190224052935  Add address to users
   up     20190224053300  Change address column
   up     20190224054338  Add birthday not null
   up     20190224055456  Add good to users
   up     20190224063318  Add height to users
```


次にためしにデータを作成します。


```ruby
irb(main):001:0> user = User.new
=> #<User id: nil, name: nil, email: nil, password: nil, created_at: nil, updated_at: nil, address: nil, birthday: nil, password_digest: nil, good: nil, height: nil>
irb(main):002:0> user.name = "pon"
=> "pon"
irb(main):003:0> user.email = "pon@gmail.com"
=> "pon@gmail.com"
irb(main):004:0> user.password ="pon"
=> "pon"
irb(main):005:0> user.height = "180cm"
=> "180cm"
irb(main):006:0> user.save
   (0.2ms)  BEGIN
  User Create (0.7ms)  INSERT INTO "users" ("name", "email", "password", "created_at", "updated_at", "height") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["name", "pon"], ["email", "pon@gmail.com"], ["password", "pon"], ["created_at", "2019-02-24 06:45:18.618771"], ["updated_at", "2019-02-24 06:45:18.618771"], ["height", "180cm"]]
   (7.5ms)  COMMIT
=> true
irb(main):007:0>
```

rails dbでデータベースに接続してデータを確認すると

```
 id | name |     email     | password |         created_at         |         updated_at         | address | birthday | password_digest | good | height
----+------+---------------+----------+----------------------------+----------------------------+---------+----------+-----------------+------+--------
  3 | pon  | pon@gmail.com | pon      | 2019-02-24 06:45:18.618771 | 2019-02-24 06:45:18.618771 |         |          |                 |      | 180cm
(1 row)
```

こんな感じで情報が出てきます。

ここまでしたら

rails db:rollbackで前の状態に戻します

```
== 20190224063318 AddHeightToUsers: reverting =================================
-- remove_column(:users, :height)
   -> 0.0007s
== 20190224063318 AddHeightToUsers: reverted (0.0013s) ========================
```

問題なくrollbackできるはず

rails db:migrate:statusを確認



```
database: tt_db

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20190224051828  Create users
   up     20190224052935  Add address to users
   up     20190224053300  Change address column
   up     20190224054338  Add birthday not null
   up     20190224055456  Add good to users
  down    20190224063318  Add height to users
```


一つdownになっているやつがいまrollbackしたものです。


ここでrails dbでデータベースにアクセスしてusersの情報をみてみると

```sql
tt_db=> select * from users;
 id | name |     email     | password |         created_at         |         updated_at         | address | birthday | password_digest | good
----+------+---------------+----------+----------------------------+----------------------------+---------+----------+-----------------+------
  3 | pon  | pon@gmail.com | pon      | 2019-02-24 06:45:18.618771 | 2019-02-24 06:45:18.618771 |         |          |                 |
(1 row)
```

heightのカラムがなくなっています。

ここまでは良いのですがこのあとまたrails db:migrateを実行すると


```sql
tt_db=> select * from users;
 id | name |     email     | password |         created_at         |         updated_at         | address | birthday | password_digest | good | height
----+------+---------------+----------+----------------------------+----------------------------+---------+----------+-----------------+------+--------
  3 | pon  | pon@gmail.com | pon      | 2019-02-24 06:45:18.618771 | 2019-02-24 06:45:18.618771 |         |          |                 |      |
(1 row)
```

こんな感じでheightの情報が消えています。

こんな感じでカラムが一度消えてしまうと情報も消えてしまいます。

これはカラムの内容を変更するときに特に気を付けなくてはいけないです。





emailというカラムをmailというカラムに変更したい場合に

rails g migration change_users_email_to_mail

として

```ruby
class ChangeUsersEmailToMail < ActiveRecord::Migration[5.2]
  def up
          remove_column :users, :email
          add_column :users, :mail, :string
  end

  def down
          remove_column :users, :mail
          add_column :users, :email, :string
  end
end
```

としてmigrate rollbackが通るようにして

rails db:migrate:statusで現在の状況を確認して追加しようとしているのがdownとして問題なく入っているのを確認して

rails db:migrateを実行すると

usersテーブルの中身は

```sql
tt_db=> select * from users;
 id | name | password |         created_at         |         updated_at         | address | birthday | password_digest | good | height | mail
----+------+----------+----------------------------+----------------------------+---------+----------+-----------------+------+--------+------
  3 | pon  | pon      | 2019-02-24 06:45:18.618771 | 2019-02-24 06:45:18.618771 |         |          |                 |      |        |
(1 row)
```

こんな感じでmailの中身が消えた状態になってしまっています。


これが本番の環境で大切な情報が消えてしまったとなったら大変です....


なので気を付けるようにしましょう。

#追記

カラム名の変更であれば

rename_columnをつかえるっぽいです。

なのでrename_columnを使えばデータは消えずに問題ないですがカラムの型を変更する場合はどうしたらよいかわからないです。


#ちなみに

既にデプロイしているプロジェクトの大切な情報を含んでいるカラムを変更したい場合どうしたらよいのでしょうか？

わかる方教えてください( ;∀;)