PhoenixでDBスキーマの変更を行う

# やりたいこと
現在、↓のようなmigrationファイルを元に

```priv/repo/migrations/20160208022404_create_post.exs
defmodule PhoenixSample.Repo.Migrations.CreatePost do
  use Ecto.Migration

  def change do
    create table(:posts) do
      add :title, :string
      add :content, :string

      timestamps
    end

  end
end
```

↓のようなテーブルが生成されている。

```
MariaDB [phoenix_sample_dev]> SHOW COLUMNS FROM	posts;
+-------------+---------------------+------+-----+---------+----------------+
| Field       | Type                | Null | Key | Default | Extra          |
+-------------+---------------------+------+-----+---------+----------------+
| id          | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| title       | varchar(255)        | YES  |     | NULL    |                |
| content     | varchar(255)        | YES  |     | NULL    |                |
| inserted_at | datetime            | NO   |     | NULL    |                |
| updated_at  | datetime            | NO   |     | NULL    |                |
+-------------+---------------------+------+-----+---------+----------------+
```

このままでも問題ないのだが、contentカラムの方をTEXT型に変更したい。
そこで、Phoenixの(Ectoの？）Migration機能を使って変更してみようと思う。

なお、Migrationについてのより詳しい情報は[こちらのマニュアル](https://hexdocs.pm/ecto/Ecto.Migration.html)を参照してください。

# Migrationを行う

## 空のmigrationファイルを作成する
mixコマンドを使い、空のmigrationファイルを生成する。
名前は何でもいいのですが、出来る限り内容が想像できるような名前を選んでおくのがよいでしょう。

```
$ mix ecto.gen.migration modify_contents_to_post
* creating priv/repo/migrations
* creating priv/repo/migrations/20160210073915_modify_contents_to_post.exs
````

## migrationファイルを編集する
生成されたmigrationファイルを以下のように編集。

```priv/repo/migrations/20160210073915_modify_contents_to_post.exs
defmodule PhoenixSample.Repo.Migrations.ModifyContentsToPost do
  use Ecto.Migration

  def change do
    alter table(:posts) do
      modify :content, :text

    end

  end
end
```

テーブル生成ではなくて変更なので、alter tableを、
カラムの型の変更なのでmodifyを使っています。
マニュアルによると、他にもadd, remove, renameなど様々な関数が定義されているようです。

ところで、マニュアルには、addやmodifyはFunctonsのセクションで説明されている一方で
create/alter tableについてはMacroのセクションで説明されている。
このMacroってやはPaulGrahamが「Lispの力強さの秘密はマクロにある」ってうたってた、あのマクロのことかな？
なんだか怖いものに出くわしてしまった気分だ…

## migration実行
mixコマンドを使ってmigrationを実行します。

```
$ mix ecto.migrate

17:03:22.722 [info]  == Running PhoenixSample.Repo.Migrations.ModifyContentsToPost.change/0 forward

17:03:22.722 [info]  alter table posts

17:03:22.742 [info]  == Migrated in 0.1s
```

変更後のテーブル定義を確認すると。

```
MariaDB [phoenix_sample_dev]> show columns from	posts;
+-------------+---------------------+------+-----+---------+----------------+
| Field       | Type                | Null | Key | Default | Extra          |
+-------------+---------------------+------+-----+---------+----------------+
| id          | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| title       | varchar(255)        | YES  |     | NULL    |                |
| content     | text                | YES  |     | NULL    |                |
| inserted_at | datetime            | NO   |     | NULL    |                |
| updated_at  | datetime            | NO   |     | NULL    |                |
+-------------+---------------------+------+-----+---------+----------------+
```

無事変更されていることが確認されました。

# ところで、migrationファイルではどんな型を使用できるんだい？

## 型の定義を求めて
で、今回のつまづきどころ。
カラムの型をvarchar->textに変えるためには、migrationファイルでどんな型を指定してやればよいか調べて見たところ、いまいちまとまって説明されているところが見つからず（マニュアル含む）
結局適当に:textを指定したところ、うまく行ってしまったという経緯があった。
このままにしておくのも気持ちが悪いので、もう少し調べてみるとする。

色々検索していくうちに、[Ecto.Migrationのソースのコメント](https://github.com/elixir-lang/ecto/blob/master/lib/ecto/migration.ex#L511)にぶち当たる。

>  This function also accepts Ecto primitive types as column types
>  and they are normalized by the database adapter. For example,
>  `string` is converted to varchar, `datetime` to the underlying
>  datetime or timestamp type, `binary` to bits or blob, and so on.
>
>  However, the column type is not always the same as the type in your
>  model. For example, a model that has a `string` field, can be
>  supported by columns of types `char`, `varchar`, `text` and others.
>  For this reason, this function also accepts `text` and other columns,
>  which are sent as is to the underlying database.

この関数は、カラムの型をEctoのプリミティブ型として受け取り、DBごとの適切な型に変換する。
（例えば、EctoのstringかたはDBのVarchar型に変換されるし、Ectoのdatetime型はDBのdatatime型に変換される）
ここで定義されたカラムの型とモデルで定義されたフィールドの型は必ずしも一対一には対応しない。
（たとえば、DB上ではchar,varchar,textと異なる型で定義されたカラムがあったとしても、モデルのフィールドではすべてstring型として表現される）

訳は間違っているかもしれませんが、とりあえずadd関数が受け取るtypeはEctoのprimitive型のようである。
ecto+primitive+typeで検索を進めていくと、やはり[ソース](https://github.com/elixir-lang/ecto/blob/master/lib/ecto/type.ex#L88)にぶち当たる。

```
  @type t         :: primitive | custom

  @typedoc "Primitive Ecto types (handled by Ecto)."
  @type primitive :: base | composite

  @typedoc "Custom types are represented by user-defined modules."
  @type custom    :: atom

  @typep base      :: :integer | :float | :boolean | :string | :map |
                      :binary | :decimal | :id | :binary_id |
                      :datetime | :date | :time | :any
  @typep composite :: {:array, base} | {:embed, Ecto.Embedded.t}

  @base      ~w(integer float boolean string binary decimal datetime date time id binary_id map any)a
  @composite ~w(array embed)a
```

まだ不慣れな言語なせいもあって、理解が追いつかない記述ですが、一見BNF記法に見えるので、そう考えて進めていきます。
となると、primitive型はこいつらのことになりそう。

```
  @typep base      :: :integer | :float | :boolean | :string | :map |
                      :binary | :decimal | :id | :binary_id |
                      :datetime | :date | :time | :any
```

## 実際に試してみた
試しに, :idと:binary_id(uuid用の型)以外の型を全部使ったテーブルを作ってみる。
migrationファイルを用意して…

```
defmodule PhoenixSample.Repo.Migrations.CreateSample do                                                                                                                                          
  use Ecto.Migration                                                                                                                                                                             
                                                                                                                                                                                                 
  def change do                                                                                                                                                                                  
    create table(:hoges) do                                                                                                                                                                      
      add :one, :integer
      add :two, :float
      add :three, :boolean
      add :four, :string
      add :five, :map
      add :six, :binary
      add :seven, :decimal
      add :eight, :datetime
      add :nine, :date
      add :ten, :time

    end

  end
end
```

maigration実行。
結果はこんな感じ。

```
MariaDB [phoenix_sample_dev]> show columns from hoges;
+-------+---------------------+------+-----+---------+----------------+
| Field | Type                | Null | Key | Default | Extra          |
+-------+---------------------+------+-----+---------+----------------+
| id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| one   | int(11)             | YES  |     | NULL    |                |
| two   | double              | YES  |     | NULL    |                |
| three | tinyint(1)          | YES  |     | NULL    |                |
| four  | varchar(255)        | YES  |     | NULL    |                |
| five  | text                | YES  |     | NULL    |                |
| six   | blob                | YES  |     | NULL    |                |
| seven | decimal(10,0)       | YES  |     | NULL    |                |
| eight | datetime            | YES  |     | NULL    |                |
| nine  | date                | YES  |     | NULL    |                |
| ten   | time                | YES  |     | NULL    |                |
+-------+---------------------+------+-----+---------+----------------+
```

対応表をまとめると、こんな感じになります。

| Ecto.type | Mysql.type |
|:----------|-----------:|
| :integer  | int(11)    |
| :float    | double     |
| :boolean  | tinyint(1) |
| :string   | varchar(255)|
| :map      | text       |
| :binary   | blob       |
| :decimal  | decimal(10, 0)|
| :datetime | datetime   |
| :date     | date       |
| :time     | time       |

試してないのですが、map型のデータをそのままDBに投入できるみたいで、便利そうですね。


## ところで、キミが上の方で使っていた:text型はどこからきているの？
ここまで書いてきて、ふと気がつく。
自分が最初に使った:text型がectoのprimitive型として定義されていないことに。
さて、困った。
しばらく調べてみると、migrationファイルで使える型はectoのprimitive型だけではなく
自前で定義したカスタム型でも良いということがわかったが、この:text型がどこで定義されているのかは依然不明。
どなたか知っている方がおりましたら、情報お願いします。

## 追記：MigrationファイルでEctoのprimitive型以外を指定した場合、そのままDBの型だとみなされる
コメントの情報より。
@nikuさん、@uasiさん情報&解説ありがとうござます。

試しに

```
defmodule PhoenixSample.Repo.Migrations.AddSmallintToHoge do
  use Ecto.Migration

  def change do
    alter table(:hoges) do
      add :eleven, :smallint

    end
  end
end
```

こんな感じに、smallint型のカラムを追加してみると...

```
MariaDB [phoenix_sample_dev]> show columns from hoges;
+--------+---------------------+------+-----+---------+----------------+
| Field  | Type                | Null | Key | Default | Extra          |
+--------+---------------------+------+-----+---------+----------------+
| id     | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| one    | int(11)             | YES  |     | NULL    |                |
| two    | double              | YES  |     | NULL    |                |
| three  | tinyint(1)          | YES  |     | NULL    |                |
| four   | varchar(255)        | YES  |     | NULL    |                |
| five   | text                | YES  |     | NULL    |                |
| six    | blob                | YES  |     | NULL    |                |
| seven  | decimal(10,0)       | YES  |     | NULL    |                |
| eight  | datetime            | YES  |     | NULL    |                |
| nine   | date                | YES  |     | NULL    |                |
| ten    | time                | YES  |     | NULL    |                |
| eleven | smallint(6)         | YES  |     | NULL    |                | <- 追加された
+--------+---------------------+------+-----+---------+----------------+
```

と期待通りの挙動をすることが確認されました。
当然存在しない型を指定すると

```
defmodule PhoenixSample.Repo.Migrations.AddIntervalToHoges do
  use Ecto.Migration

  def change do
    alter table(:hoges) do
      add :twelve, :interval

    end
  end
end
```

エラーになる。

```
$ mix ecto.migrate

10:25:34.354 [info]  == Running PhoenixSample.Repo.Migrations.AddIntervalToHoges.change/0 forward

10:25:34.355 [info]  alter table hoges
** (Mariaex.Error) (1064): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'interval' at line 1
    (ecto) lib/ecto/adapters/sql.ex:185: Ecto.Adapters.SQL.query!/5
    (ecto) lib/ecto/adapters/mysql.ex:85: anonymous fn/4 in Ecto.Adapters.MySQL.execute_ddl/3
    (elixir) lib/enum.ex:1493: Enum."-reduce/3-lists^foldl/2-0-"/3
...
```

上側そのままで、DBだけリプレースみたいな事案に遭遇したことはないのですが、一応DB固有のデータ型を使うのは避けておいたほうがよいようです。