ElixirでMySQLを使う #2（Ecto/マイグレーション〜スキーマ作成〜カラム修正）

# 概要

- [ElixirでMySQLを使う #1（Ecto/MyXQLセットアップ〜DB作成）](https://qiita.com/im_miolab/items/d27a1231b80e5872117c)

の続きです。
前回、__Elixir__で`Ecto`を介して__MySQL__に接続し、データベースを作成しました。

- 本記事では、マイグレーション〜スキーマ作成の実装から行っていきます。
- また、Ectoで実際にレコードをMySQLに食わせていきます。
- オマケ的に、Ectoによるテーブルカラム修正の実装も行っていきます。

なお、本記事シリーズにおける__実行環境__等については、前回の記事をご参照ください。

# マイグレーション〜テーブル作成

Ectoでマイグレーションを実行していきます。

マイグレーションをつくることで、データベース内のテーブル及びインデックスの作成や更新をする仕組みを整えていきます。

  ```terminal:terminal
  $ mix ecto.gen.migration create_people
  * creating priv/repo/migrations
  * creating priv/repo/migrations/20200521003013_create_people.exs
  ```

  ```elixir:priv/repo/migrations/20200521003013_create_people.exs
  def change do
    create table(:people) do    --> add
      add :first_name, :string  --> add
      add :last_name, :string   --> add
      add :age, :integer        --> add
    end
  end
  ```

データ型の定義を行いました。
ちなみに、デフォルト値の設定やNOT NULL（NIL）などバリデーション関係は書いていませんが、一旦このまま進めます。
（NOT NULL制約の適用修正については後述します）

`mix ecto.migrate`を実行します。

  ```terminal:terminal
  $ mix ecto.migrate

  09:32:21.356 [info]  == Running 20200521003013 Friendsmysql.Repo.Migrations.CreatePeople.change/0 forward

  09:32:21.358 [info]  create table people

  09:32:21.370 [info]  == Migrated 20200521003013 in 0.0s
  ```

テーブル`people`が作成できました！

### 結果確認（MySQL）

この時点で、念のためデータベースの状況をMySQL側で確認してみます。

```mysql:terminal(MySQL)
mysql> show tables;
+-----------------------------+
| Tables_in_friendsmysql_repo |
+-----------------------------+
| people                      |
| schema_migrations           |
+-----------------------------+
2 rows in set (0.00 sec)

mysql> desc people;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(255)        | YES  |     | NULL    |                |
| last_name  | varchar(255)        | YES  |     | NULL    |                |
| age        | int(11)             | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
```

テーブル`people`がデータベースに入ってます。

# スキーマ作成

つづいて、`Ecto.Scheme`のセッティングをしていきます。

__Scheme__（スキーマ）は、データベースのデータをElixirで表現したものにあたり、データベースのテーブルに関連付けがされます。

実装は以下の通りです。

  ```elixir:lib/friends/person.ex
  defmodule Friendsmysql.Person do
    use Ecto.Schema

    schema "people" do
      field :first_name, :string
      field :last_name, :string
      field :age, :integer
    end
  end
  ```

# レコード作成（Create）〜MySQLにデータを渡す

それでは、以下の手順でElixir側からMySQL側へレコードを渡してみます。
`IEx`で実行します。

  ```elixir:terminal
  $ iex -S mix

  iex(1)> person = %Friendsmysql.Person{age: 28}
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 28,
    first_name: nil,
    id: nil,
    last_name: nil
  }

  iex(2)> Friendsmysql.Repo.insert(person)

  09:39:55.834 [debug] QUERY OK db=0.9ms decode=0.8ms queue=1.8ms idle=321.1ms
  INSERT INTO `people` (`age`) VALUES (?) [28]
  {:ok,
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 28,
    first_name: nil,
    id: 1,
    last_name: nil
  }}
  ```

`person = %Friendsmysql.Person{age: 28}`で、_person_という人のデータをつくり、`insert()`でデータをDBテーブルに渡しました。

つづけて、もう1レコード用意してみます。
先ほどの_person_さんは`age: 28`だけで、`first_name`と`last_name`を持たない名無しの方だったので、こんどは`first_name`と`last_name`を持ってる人のデータで実行します。

  ```elixir:terminal
  iex(3)> im = %Friendsmysql.Person{first_name: "im", last_name: "miolab", age: 28}
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 28,
    first_name: "im",
    id: nil,
    last_name: "miolab" 
  }

  iex(4)> Friendsmysql.Repo.insert(im)

  09:42:41.351 [debug] QUERY OK db=1.5ms queue=1.6ms idle=1841.3ms
  INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [28, "im", "miolab"]
  {:ok,
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 28,
    first_name: "im",
    id: 2,
    last_name: "miolab"
  }}
  ```

### 結果確認（MySQL）

  ```mysql:terminal(MySQL)
  mysql> select * from people;
  +----+------------+-----------+------+
  | id | first_name | last_name | age  |
  +----+------------+-----------+------+
  |  1 | NULL       | NULL      |   28 |
  |  2 | im         | miolab    |   28 |
  +----+------------+-----------+------+
  2 rows in set (0.00 sec)
  ```

`first_name`、`last_name`、`age`、すべて持っている人のデータが登録されました。

# おまけ： テーブルカラムの修正（NOT NULL制約を追加）

ところで、名前が`NULL`の人をinsertできることに関して気持ちがよくないので、`NOT NULL`制約を適用しておきます。

`apply_notnull_to_names`という名前で、マイグレーションファイルを新しく作ります。

```terminal:terminal
$ mix ecto.gen.migration apply_notnull_to_names
* creating priv/repo/migrations/20200522004701_apply_notnull_to_names.exs
```

以下のようにコード記述します。

```elixir:priv/repo/migrations/20200522004701_apply_notnull_to_names.exs
  def change do
    alter table(:people) do    --> add
      modify :first_name, :string, null: false    --> add
      modify :last_name, :string, null: false     --> add
    end
  end
```

なお、さきほど一番はじめにデータベースへ登録された「名前がNULLの人」は、MySQL側でdeleteしておきます。

```mysql:terminal(MySQL)
mysql> delete from people where id="1";
Query OK, 1 row affected (0.00 sec)
```

ここまで準備が整ったら、`mix ecto.migrate`します。

```terminal:terminal
$ mix ecto.migrate

09:54:06.756 [info]  == Running 20200522004701 Friendsmysql.Repo.Migrations.ApplyNotnullToNames.change/0 forward

09:54:06.758 [info]  alter table people

09:54:06.899 [info]  == Migrated 20200522004701 in 0.1s
impc:friendsmysql im$ iex -S mix
Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
```

### 結果確認（MySQL）

MySQL側で、`first_name`と`last_name`へのNOT NULL適用変更が反映されているか見てみます。

```mysql:terminal(MySQL)
mysql> desc people;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(255)        | NO   |     | NULL    |                |
| last_name  | varchar(255)        | NO   |     | NULL    |                |
| age        | int(11)             | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
```

変更されていることを確認できました。

### MySQLにデータを渡す（NOT NULL制約適用後）

ふたたび`IEx`で、MySQLにデータを投げてみます。

まずは__失敗パターン__想定で、先ほどと同じ_名無しのpersonさん_で実行してみます。

```elixir:terminal
iex(1)> person = %Friendsmysql.Person{age: 28}
%Friendsmysql.Person{
  __meta__: #Ecto.Schema.Metadata<:built, "people">,
  age: 28,
  first_name: nil,
  id: nil,
  last_name: nil
}

iex(2)> Friendsmysql.Repo.insert(person)

09:55:02.022 [debug] QUERY ERROR db=10.6ms queue=2.2ms idle=145.0ms
INSERT INTO `people` (`age`) VALUES (?) [28]
** (MyXQL.Error) (1364) (ER_NO_DEFAULT_FOR_FIELD) Field 'first_name' doesn't have a default value
    (ecto_sql) lib/ecto/adapters/myxql.ex:242: Ecto.Adapters.MyXQL.insert/6
    (ecto) lib/ecto/repo/schema.ex:661: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:263: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
```

ぶじ失敗を確認できました。
`first_name`を持たないため、errorで弾かれています。

つづいて、__成功パターン__想定でデータinsertを実行します。

```elixir:terminal
iex(2)> ryan_bigg = %Friendsmysql.Person{first_name: "Ryan", last_name: "Bigg"}         
%Friendsmysql.Person{
  __meta__: #Ecto.Schema.Metadata<:built, "people">,
  age: nil,
  first_name: "Ryan",
  id: nil,
  last_name: "Bigg"
}

iex(3)> Friendsmysql.Repo.insert(ryan_bigg)                                    

09:55:11.713 [debug] QUERY OK db=1.3ms queue=0.7ms idle=1849.1ms
INSERT INTO `people` (`first_name`,`last_name`) VALUES (?,?) ["Ryan", "Bigg"]
{:ok,
 %Friendsmysql.Person{
   __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
   age: nil,
   first_name: "Ryan",
   id: 3,
   last_name: "Bigg"
 }}
```

こんどは問題なく登録できました。

### 結果確認（MySQL）

  ```mysql:terminal(MySQL)
  mysql> select * from people;
  +----+------------+-----------+------+
  | id | first_name | last_name | age  |
  +----+------------+-----------+------+
  |  2 | im         | miolab    |   28 |
  |  3 | Ryan       | Bigg      | NULL |
  +----+------------+-----------+------+
  2 rows in set (0.00 sec)
  ```

# 終わり ＆ 次回

マイグレーション〜スキーマ作成を行って、Elixir側からデータをMySQLに渡すことができました。
また、Ectoを介してちょっとしたテーブルカラムの修正も行いました。

次回は、Elixirでの__CRUD__操作（データ作成・読み出し・更新・削除）を、Ectoのクエリを書いて実行していきます。

- [ElixirでMySQLを使う #3（Ecto/CRUD・クエリ作成）](https://qiita.com/im_miolab/items/850d75079fa4d4e66baa)