Elixir ต่อฐานข้อมูลด้วย Ecto ตอนที่ 2 สร้าง table ด้วย migration script
Tags: elixir, ecto, programming
Published: 2021-01-16 00:05:40

วันนี้จะลองสร้าง table ผ่าน Ecto Migration กัน

เริ่มแรกเราต้อง config repository ให้กับ otp app ของเราก่อนเพื่อให้ mix task ของ ecto รู้ว่าเวลาจะจัดการ DB นั้นจะเชื่อมต่อ DB ผ่าน Repository module ไหน

```elixir
# config/config.exs

config :ecto_sample, ecto_repos: [EctoSample.Repo]
```

จากนั้นใช้คำสั่ง

```
mix ecto.gen.migration create_posts_table
```

โดย `create_posts_table` คือชื่อ migration file ที่เราต้องการ แล้ว task นี้จะสร้างไฟล์ให้เราที่ path `priv/repo/migrations` แบบนี้

```
priv/repo/migrations/20210115233819_create_posts_table.exs
```

จะเห็นว่าจะมี timestamp ที่ด้านหน้าชื่อที่เราส่งให้ตอนสั่ง generate migration file

เมื่อเราเปิดไฟล์ขึ้นมาจะเห็นโค้ดที่ถูก generate มาไว้ให้แล้วเบื้องต้นแบบนี้

```elixir
defmodule EctoSample.Repo.Migrations.CreatePostsTable do
  use Ecto.Migration

  def change do

  end
end
```

สิ่งที่เราต้องทำเพิ่มคือเขียนโค้ดเพื่อสร้าง table ตามที่เราต้องการ ซึ่งโค้ดนั้นก็ยังเป็นโค้ด Elixir โดยอาศัย macro จาก module [Ecto.Migration](https://hexdocs.pm/ecto_sql/Ecto.Migration.html) ช่วยในการเขียนโค้ดจัดการสร้าง table ตัวอย่างเช่น

```elixir
defmodule EctoSample.Repo.Migrations.CreatePostsTable do
  use Ecto.Migration

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

      timestamps()
    end
  end
end
```

เราใช้ `create` macro เพื่อบอกว่าเราต้องการสร้างสิ่งใหม่ให้กับ DB แล้วก็ใช้ `table(:posts)` เพื่อบอกว่าสิ่งที่เราจะสร้างนั้นคือ table ชื่อ posts ส่วน `do ... end` block ที่เราเขียนลงไปจะเป็นรายละเอียดของ column ที่เราต้องการสร้างให้กับ table posts นั่นเอง

จากโค้ดเราสร้าง 2 columns คือ `title` กับ `body` โดยมี type เป็น `:string` ทั้งคู่ซึ่ง type ที่เราใช้ใน migration script จะถูกแปลงเป็น type แต่ละแบบของ DB ขึ้นอยู่กับ DB ที่เราใช้ และ adapter library ที่เราใช้

ส่วน `timestamps()` เป็นการบอกให้สร้างอีก 2 fields ชื่อ `inserted_at` กับ `updated_at` เป็น date time ทั้งคู่ สอง fields นี้จะมีผลกับบางคำสั่งที่ใช้ insert และ update ข้อมูลของ module Repo โดยจะทำการ stamp เวลาที่ใช้ insert กับ update ให้เอง

หลังจากเราเขียนโค้ด migration เสร็จแล้ว เราจะสั่ง `mix ecto.migrate` เพื่อ run script ที่เราเขียนสิ่งที่ได้คือ table ใน DB ตามที่เราเขียนไว้

```
$ mix ecto.migrate

06:51:54.202 [info]  == Running 20210115233819 EctoSample.Repo.Migrations.CreatePostsTable.change/0 forward

06:51:54.205 [info]  create table posts

06:51:54.235 [info]  == Migrated 20210115233819 in 0.0s
```

เมื่อเช็คใน MySQL จะเจอ tables ถูกสร้างมา 2 tables

```
mysql> show tables;
+-------------------+
| Tables_in_test    |
+-------------------+
| posts             |
| schema_migrations |
+-------------------+
2 rows in set (0.01 sec)
```

รายละเอียดของ table posts เป็นแบบนี้

```
mysql> desc posts;
+-------------+-----------------+------+-----+---------+----------------+
| Field       | Type            | Null | Key | Default | Extra          |
+-------------+-----------------+------+-----+---------+----------------+
| id          | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| title       | varchar(255)    | YES  |     | NULL    |                |
| body        | varchar(255)    | YES  |     | NULL    |                |
| inserted_at | datetime        | NO   |     | NULL    |                |
| updated_at  | datetime        | NO   |     | NULL    |                |
+-------------+-----------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
```

จะเห็นว่า migration script ที่เราเขียนเมื่อสั่ง migrate จะสร้าง column `id` เป็น primary key และ `auto_increment` ให้ด้วย

จากนั้นลองดู table `schema_migrations` เป็นแบบนี้

```
mysql> desc schema_migrations;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| version     | bigint   | NO   | PRI | NULL    |       |
| inserted_at | datetime | YES  |     | NULL    |       |
+-------------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> select * from schema_migrations;
+----------------+---------------------+
| version        | inserted_at         |
+----------------+---------------------+
| 20210115233819 | 2021-01-15 23:51:54 |
+----------------+---------------------+
1 row in set (0.00 sec)
```

จะเห็นว่ามี 2 columns ซึ่งเมื่อ select ดูก็จะพบเลข version ตรงกับ timestamp ของชื่อไฟล์ `20210115233819_create_posts_table.exs` นั่นเอง

หน้าที่ของ table นี้ก็คือให้ task `mix ecto.migrate` รู้ได้ว่าตอนนี้ schema ของ DB ที่เราจัดการมีการเปลี่ยนแปลงไปโดยขึ้นอยู่กับ migration script ล่าสุดไฟล์ไหนนั่นเอง

ซึ่งทำให้เราสามารถบอก ecto task ให้ทำการ rollback โครงสร้างของ DB ถอยกลับไป version ก่อนหน้าได้ ด้วยคำสั่ง

```
$ mix ecto.rollback

06:58:49.200 [info]  == Running 20210115233819 EctoSample.Repo.Migrations.CreatePostsTable.change/0 backward

06:58:49.204 [info]  drop table posts

06:58:49.230 [info]  == Migrated 20210115233819 in 0.0s
```

เมื่อเราสั่ง `mix ecto.rollback` จะเห็นว่ามันทำการ drop table posts ที่เราสร้างตอนสั่ง `mix ecto.migrate` ก่อนหน้านี้ พอเราไปดูขอมูลในตาราง `schema_migrations` อีกรอบจะเห็นแบบนี้

```
mysql> select * from schema_migrations;
Empty set (0.00 sec)
```

ข้อมูลถูกลบไปแล้วเพราะเราสั่ง `mix ecto.rollback` ไป

เราสามารถสั่ง `mix ecto.migrate` อีกรอบได้ ก็จะได้ posts table กลับมาและ version ใน schema_migrations ก็อัพเดทเป็น migration script ล่าสุดเช่นกัน

สรุป

เราได้เห็นตัวอย่างการใช้ Ecto migration สร้าง table กันไปแล้วซึ่งจริงๆการเปลี่ยนแปลงโครงสร้างเราสามารถใช้ migration script ช่วยได้ ทำให้เราจัดการ rollback ได้ง่ายๆเมื่อสิ่งที่ทำไปผิดหรือไม่ต้องการ

นอกจากนั้น Ecto.Migration module ยังมี macro อื่นๆอีกให้ใช้งานในการจัดการ schema ไม่ใช่แค่การ create table เท่านั้น


<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>