Help us understand the problem. What is going on with this article?

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

概要

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

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

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

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

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

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

terminal
  $ mix ecto.gen.migration create_people
  * creating priv/repo/migrations
  * creating priv/repo/migrations/20200521003013_create_people.exs
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
  $ 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側で確認してみます。

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で表現したものにあたり、データベースのテーブルに関連付けがされます。

実装は以下の通りです。

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で実行します。

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_namelast_nameを持たない名無しの方だったので、こんどはfirst_namelast_nameを持ってる人のデータで実行します。

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)

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_namelast_nameage、すべて持っている人のデータが登録されました。

おまけ: テーブルカラムの修正(NOT NULL制約を追加)

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

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

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

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

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しておきます。

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

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

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_namelast_nameへのNOT NULL適用変更が反映されているか見てみます。

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さんで実行してみます。

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を実行します。

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)

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のクエリを書いて実行していきます。

im_miolab
「kokura.ex」「KitaQもくもく会」管理人してます / Elixir, Python, JavaScript / 音楽, 大東流・兵法二天一流, 鉄道 / 得意技 電気設計(強電)
https://twitter.com/im_miolab
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした