# Orator ORM 的 Seeding 機制

<p style="background-color: hsla(0, 0%, 96%, 1.0); padding: 1rem; text-align: center;">
本文轉載自 <a href="https://editor.leonh.space/2021/seeding-in-orator-orm/">Leon 的網誌</a>
</p>

之前寫過一篇〈[初探 Orator ORM](<@/2021/2021-02-01-初探 Orator ORM/index.md>)〉，有介紹了 [Orator ORM](https://orator-orm.com/) 的基礎操作，而此篇文章就專門介紹 Orator ORM 的 seeding 機制，本篇的範例也都延續自〈初探 Orator ORM〉，還沒讀過的朋友請趕快手刀點擊閱讀。

<figure>

![阿湯哥手刀狂奔](https://cdn.hashnode.com/res/hashnode/image/upload/v1616119958400/U6f3gKRE_.jpeg)
<figcaption>來源：<a href="https://ent.ltn.com.tw/news/breakingnews/1386824">自由娛樂</a></figcaption>
</figure>

# Seeding

Seeding 用於在資料庫內建出一堆資料，這些資料可以是用於資料庫初始化的資料，例如郵遞區號，也可以是用於測試的假資料，而 Orator ORM 使用的是 [Faker](https://github.com/joke2k/faker) 套件當作假資料產生器。

## Orator ORM 的 Seeding 操作

再次回顧一下在〈初探 Orator ORM〉文中的專案結構：

```
project1
├── app.db
├── oratordemo
│   ├── database.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.p
│   └── models
│        └── __init__.py
├── poetry.lock
└── pyproject.toml
```

分別有放 migration 腳本檔的 migrations/ 和放 model 腳本檔的 models/，理所當然的 seeding 腳本就會放在 seeds/ 裡面，因此產生過 seeding 腳本之後的目錄結構就會長這樣：

```
project1
├── app.db
├── oratordemo
│   ├── __init__.py
│   ├── database.py
│   ├── config
│   │   └── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models
│   │   └── __init__.py
│   └── seeds
│        └── __init__.py
├── poetry.lock
└── pyproject.toml
```

與 migrations/ 和 models/ 一樣，這個 seeds/ 不用手動建立，Orator ORM 會替我們代勞。

另一個新的 config/ 則是放 model factory 腳本的地方，後面會再提到。

### 建立 Seeding 腳本

```shell
(porject1) ~/project1/oratordemo> orator make:seed user_table_seeder

database_seeder created successfully.
user_table_seeder created successfully.
```

進去 seeds/ 看一下，注意到除了預期的 user_<wbr>table_<wbr>seeder.py 外，Orator ORM 還另外建了個 database_<wbr>seeder.py，這個 database_<wbr>seeder.py 只會建立一次，關於這個檔案的作用我們後面會再提到，先關心 user_<wbr>table_<wbr>seeder.py：

```Python
from orator.seeds import Seeder

class UserTableSeeder(Seeder):

    def run(self):
        """
        Run the database seeds.
        """
        pass

```

裡面只有一個空的 `run()` 函式，這個函式就是在跑 seeding 時會被呼叫的函式，函式內放的就是建立 User 資料的敘述，但架構上我們會把實際產生假資料的邏輯抽離，在這邊我們引入 model factory 的概念，顧名思義，model factory 就是實際產生假資料的「工廠」，而 xxx_<wbr>seeder.py 則負責向工廠下單。

### 定義 Model Factory

建立 oratordemo/<wbr>config/<wbr>factories.py 檔案：

```Python
from orator.orm import Factory
from oratordemo.models.user import User

factory = Factory()

@factory.define(User)
def users_factory(faker):
    return {
        'name': faker.name(),
        'age': faker.random_int(min=1, max=115),
        'birthday': faker.date_of_birth(minimum_age=0, maximum_age=115).strftime('%Y-%m-%d')
    }
```

這邊會帶入一個 Orator ORM 自行產生的 Faker 物件，而 Faker 的用法也頗直覺。Faker 可以產出的假資料有許多種類，可以閱讀 Faker 的文件了解。

有了 model factory，我們回到 seeder 寫向工廠下單的部份。

### 呼叫 Model Factory

把 user_<wbr>table_<wbr>seeder.py 加上呼叫 model factory 的部份：

```Python
from orator.seeds import Seeder
from oratordemo.config.factories import factory
from oratordemo.models.user import User

class UserTableSeeder(Seeder):

    factory = factory

    def run(self):
        """
        Run the database seeds.
        """
        self.factory(User, 50).create()
```

從上面可以看出 seeder 和 model factory 的分工，model factory 負責實際的生產邏輯，而 seeder 負責向 model factory 下訂數量。

### 執行 Seeder

若要執行特定的 seeder，執行這樣的命令：

```shell
(project1) ~/project1> env PYTHONPATH='/home/leon/project1' orator db:seed --config=oratordemo/database.py --path=oratordemo/seeds/ --seeder=user_table_seeder

Are you sure you want to seed the database?:  (yes/no) [no] yes
Database seeded!
```

會這麼落落長是有原因的，[Orator ORM 在跑 seeder 內如果有 `import` 敘述會引發 `ModuleNotFoundError` 這個問題](https://github.com/sdispater/orator/issues/41)，因此必須多[加一個環境變數](https://ithelp.ithome.com.tw/articles/10196901)讓 Python 能找到我們要 import 的模組。

若要執行一連串的 seeder，則可以把要跑的 seeder 定義在 database_<wbr>seeder.py 內，這個檔案在初次建立 seeder 時會一併建立，目前還是空的，把它加上 `call()` 敘述來加入要跑的 seeder 們：

```Python
from orator.seeds import Seeder
from oratordemo.seeds.user_table_seeder import UserTableSeeder

class DatabaseSeeder(Seeder):

    def run(self):
        """
        Run the database seeds.
        """
        self.call(UserTableSeeder)
```

如果前面跑 seeder 的命令，把指定特定 seeder 的參數拿掉，按照約定它就會去跑這支 database_<wbr>seeder.py：

```shell
(project1) ~/project1> env PYTHONPATH='/home/leon/project1' orator db:seed --config=oratordemo/database.py --path=oratordemo/seeds/

Are you sure you want to seed the database?:  (yes/no) [no] yes

Seeded: UserTableSeeder
Database seeded!
```

跑完可以去看一下資料庫，應該可以看到確實有 seeding 的資料在內。

## 結語

Orator ORM 沿襲了 ActiveRecord 的約定大於配置的風格，因此和 Laravel 的 Eloquent ORM 也有著類似的使用體驗，雖然在 Python 的世界 SQLAlchemy 由於歷史因素還是有較高的使用率，但對新專案來說 Orator ORM 是一個可以被認真考慮的選項。

覺得這篇文章對您有幫助請幫我們一鍵三連，拍手、訂閱、分享，小小的鼓勵是支撐我們分享的原動力，感謝讀完本文的您，也歡迎留下意見與我們交流。
