Rails6 のちょい足しな新機能を試す56（create_table if_not_exists編）

# はじめに

Rails 6 に追加されそうな新機能を試す第56段。 今回は、 `create_table if_not_exists` 編です。
Rails 6 では、 `create_table` に `:if_not_exists` オプションが追加されました。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は `gem install rails --prerelease` でインストールできます。

```shell
$ rails --version
Rails 6.0.0.rc1
```

# マイグレーションファイルを作る

```shell
$ bin/rails g migration CreateUser name
```

# マイグレーションファイルを修正する

`:if_not_exists` オプションを指定します。

```ruby:db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def change
    create_table :users, if_not_exists: true do |t|
      t.string :name
    end
  end
end
```

# データベースを作成する

データベースを作成します。

```shell
$ bin/rails db:create
```

# psql でテーブルを作ります

先に users テーブルを psql で作成します。
ちょっと手抜きで id カラムは省略します。

```
app_development=# create table users ( name varchar(256) )
CREATE TABLE
```

テーブルができたことを確認しておきます。カラムの情報も確認します。

```
app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
 public | users                | table | postgres
(3 rows)

app_development=# \d users;
                       Table "public.users"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 name   | character varying(256) |           |          |
```

# マイグレーションを実行する

マイグレーションを実行します。エラーは発生しません。

```
$ bin/rails db:migrate
== 20190713221959 CreateUser: migrating =======================================
-- create_table(:users, {:if_not_exists=>true})
   -> 0.0019s
== 20190713221959 CreateUser: migrated (0.0019s) ==============================
```

psql で users テーブルを確認すると id カラムが無いので、マイグレーションによってテーブルはできなかったことがわかります。

```
app_development=# \d users;
                       Table "public.users"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 name   | character varying(256) |           |          |
```

# ロールバックする

ロールバックしてみます。

```shell
$ bin/rails db:rollback
== 20190713221959 CreateUser: reverting =======================================
-- drop_table(:users, {:if_not_exists=>true})
   -> 0.0031s
== 20190713221959 CreateUser: reverted (0.0043s) ==============================
```

psql で確認すると users テーブルが削除されていることがわかります。

```
app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
(2 rows)
```

# マイグレーションを実行する

users テーブルが無い状態でマイグレーションしてみます。

```shell
$ bin/rails db:migrate
```

psql で確認するとテーブルが作られていることがわかります。

```
app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
 public | users                | table | postgres
(3 rows)
```

カラムの情報を確認すると id カラムも存在しており、 migration によって作られたことがわかります。

```
app_development=# \d users;
                                 Table "public.users"
 Column |       Type        | Collation | Nullable |              Default
--------+-------------------+-----------+----------+-----------------------------------
 id     | bigint            |           | not null | nextval('users_id_seq'::regclass)
 name   | character varying |           |          |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
```

# テーブルを削除してからロールバックする

psql で users テーブルを削除します。

```
app_development=# drop table users
```

ロールバックするとエラーが発生します。

```shell
$ bin/rails db:rollback
== 20190713221959 CreateUser: reverting =======================================
-- drop_table(:users, {:if_not_exists=>true})
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  table "users" does not exist
```

# エラーを回避するには

エラーを回避するには、

```ruby:db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def change
    create_table :users, if_not_exists: true, if_exists: true do |t|
      t.string :name
    end
  end
end
```

とするか

```ruby:db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def up
    create_table :users, if_not_exists: true do |t|
      t.string :name
    end
  end

  def down
    drop_table :users, if_exists: true
  end
end
```

とすれば良いようです。後者の方が直感的でわかりやすいので、個人的にはオススメです。
（というか前者は訳わからんし、将来、 ActiveRecord の動作が変わったりすると痛い目に合いそうです。）

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try056_create_table_if_not_exists


# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Add an :if_not_exists option to create_table](https://github.com/rails/rails/pull/31382)