コンフリクトしたschema.rbをversionを考慮しつつmergeする方法

## 概要

複数人でRails開発を行なう上でどうしても発生してしまうコンフリクト。その中でも、schema.rbのコンフリクトは本番環境へのdeployを正しく行うためにも他ブランチとの競合を矛盾なく修正し、mergeする手順が必要になってきます。
今日は、コンフリクトが発生したschema.rbをどのように正しくmergeすれば良いのかその手順を説明します。

## いきなりだけど参考文献

[https://qiita.com/jnchito/items/494a0499b808f109e0a8](https://qiita.com/jnchito/items/494a0499b808f109e0a8)

基本的には上記のQiitaブログが非常に良く書かれており、問題の起因、コンフリクトが発生した場合の解決手順など、私としても非常に助かっている記事であります。
ただ一つの懸念点として、時系列によっては、対象ブランチでDBに変更を加えたmigrationよりも後に他ブランチでDBに変更を加える場合もあります。

## 想定シナリオ

時系列による作業フローは以下のようなものを想定します。

| A | B |
| ---- | ---- |
| |feature/example2ブランチを作成|
| feature/example1ブランチを作成 | カラム追加 |
| カラム追加 |  |
| GitHubにpush | |
| pull request作成 |  GitHubにpush |
| masterにマージ |   |
| 本番環境にデプロイ | pull request作成 |
| | コンフリクト発生 |


この場合にいざブランチを統合しようとすると、schema.rbのversion的には、masterブランチにmergeされたものが新しいものになります。そのため、 feature/example2ブランチにcheckoutし該当migrateをやり直してもmigrateは実行されたがschema.rbのversionは変わらないままとなってしまいます。更に、近年増えてきている(?)masterにmergeされた直後に自動的にデプロイを実行するような機能の中では、既に本番環境でmigrateが走ってしまった可能性もあります。そのため、 schema.rbのversionが変わらないままと言うのは言い変えると、 `現状の本番環境で既に動いてるschema.rb` となってしまいます。

schema.rbについてはこちら -> [https://techracho.bpsinc.jp/hachi8833/2014_02_07/15390](https://techracho.bpsinc.jp/hachi8833/2014_02_07/15390)
そのため、feature/example2ブランチの変更も取り入れるためにschema.rbのversionは新しくしたものにコミットします。

因みに[Rails Guide](http://edgeguides.rubyonrails.org/active_record_migrations.html#schema-dumping-and-you)では、schema.rb の取り扱いについてこのように書かれています。

```
Merge conflicts can occur in your schema file when two branches modify schema. To resolve these conflicts run rails db:migrate to regenerate the schema file.
```

なので、基本的にはschema.rbは `db:migrate` で 自動的に更新するようにしたいですね。

## masterブランチに既に新しいmigrationファイル、schema.rbが入っており、コンフリクトが発生した場合のmerge手順


ここでは、 Bのシナリオをやっているという前提とします。
まずは、`feature/example2` ブランチの migrationのstatusを確認します。 

```
$ bundle exec rake db:migrate:status
   (中略)
   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   up     20180515072008  Create plans
```

`feature/example2` ブランチでは Plansテーブルを作成するmigrationファイルを追加しました。
一旦、このmigrationは `rollback` で実行前に戻しましょう。

```
$ bundle exec rake db:rollback
$ bundle exec rake db:migrate:status
   (中略)
   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   down   20180515072008  Create plans
```

続いて `master` ブランチの方のmigrationを確認します。

```
$ git checkout master
$ bundle exec rake db:migrate:status
   (中略)
   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   up     20180516011951  Add index to credits
```

最新のmigrationファイルのタイムスタンプが `feature/example2` ブランチのものよりも新しいですね。このまま`feature/example2` をmergeしてしまうとmigrateはしたのにschema.rbは変わらないという状態になってしまうので一旦こちらもrollbackしましょう。

```
$ bundle exec rake db:rollback
$ bundle exec rake db:migrate:status
   (中略)
   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   down   20180516011951  Add index to credits
```

この状態で `feature/example2` ブランチへ `master` ブランチを mergeします。

```
$ git merge master

Auto-merging db/schema.rb
CONFLICT (content): Merge conflict in db/schema.rb
Automatic merge failed; fix conflicts and then commit the result.
```

コンフリクトが発生してしまうのでschema.rbの変更を取り消して元に戻します。

```
$ git reset HEAD db/schema.rb

Unstaged changes after reset:
M   db/schema.rb

$ git checkout db/schema.rb
```

この状態で `merge`を続行すればコンフリクトは解消されます。

```
$ git add db/schema.rb
$ git merge --continue
```

さて、ここで migrateのstatusを見てみましょう。

```
$ bundle exec rake db:migrate:status

   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   down   20180515072008  Create plans
   down   20180516011951  Add index to credits
```

二つの未実行なmigrationファイルが入りました。このままmigrateを実行すると、schema.rbのversionが変更しないので `creditsテーブルを作成するmigrationファイルのタイムスタンプを変更します。`

```
$ mv db/migrate/20180515072008_create_plans.rb db/migrate/20180516072008_create_plans.rb
```

再度migrateのstatusを確認してみると

```
$ bundle exec rake db:migrate:status

   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   down   20180516011951  Add index to credits
   down   20180516072008  Create plans
```

`Create plan` が `Add index to credits` の後に来ていますね。
この状態でmigrateをしてみます。

```
$ bundle exec rake db:migrate
$ bundle exec rake db:migrate:status

   up     20180420023453  *******
   up     20180423022233  *******
   up     20180424011228  *******
   up     20180428230049  *******
   up     20180515031520  *******
   up     20180516011951  Add index to credits
   up     20180516072008  Create plans
```

```ruby:db/schema.rb
+ ActiveRecord::Schema.define(version: 20180516072008) do
(中略)

+ create_table "plans", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" do |t|
+ t.integer  "id",   limit: 4,   null: false 
```

無事、schema.rbのversionが更新されつつPlansテーブルが作られるようになりましたね。

## まとめ

と言うわけで、schema.rbのversionを考慮しつつのmerge方法でした。基本的にはタイムスタンプを変更しただけですが、Railsの開発が長いせいか、こういうテクニック(?)もレールに乗っかった修正じゃないのような感じもしますね。
他にも良い方法があればどなたか教えていただけますと幸いです。

今回は、ここまで。