iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 8
2

ORM 是現在動態網站架構中幾乎不可或缺的一部分
在Rails有 ActiveRecord、Laravel 是Eloquent,Django則是 QuerySet
而在Phoenix我們使用Ecto

在繼續深入之前,我們先介紹一下 ORM
這邊引用ihower的介紹

ORM (Object-relational mapping ) 是一種對映射關聯式資料與物件資料的程式技術。物件導向和從數學理論發展出來的關聯式資料庫,有著顯著的區別,而 ORM 正是解決這個不匹配問題所產生的工具。它可以讓你使用物件導向語法來操作關聯式資料庫,非常容易使用、撰碼十分有效率,不需要撰寫繁瑣的SQL語法,同時也增加了程式碼維護性。

(不過Elixir並不是物件導向程式語言)

我在第三天介紹Laravel時也有提到過去的經驗
簡言之ORM是一種便捷強大的資料互動工具
壞處是你會漸漸忘記怎麼寫原生的SQL 查詢語法
一般來說,ORM產生的SQL語法會比你寫的更有效率

假設我們需要產生一個User的Model:

$ mix phx.gen.schema User users name:string email:string
* creating lib/hello/user.ex
* creating priv/repo/migrations/20171211072512_create_users.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate

他會產生兩個檔案:user.ex20171211072512_create_users.exs
前者是schema檔(同時也是Model),後者是migration檔
後面提示訊息提醒你別忘了執行migrate
我們來看看檔案的內容:

# lib/hello/user.ex
defmodule Hello.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Hello.User

  schema "users" do
    field :email, :string
    field :name, :string

    timestamps()
  end

  @doc false
  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :email])
    |> validate_required([:name, :email])
  end
end

上面引入的相關的元件讓程式得以作用
中間顧名思義為schema
下半部changeset定義了model基本的結構與預設的資料驗證

你可以看到我們的user 已經有兩個欄位:nameemail
這是因為產生時我們在後面加註name:string email:string
前面是欄位名稱,後面是欄位型態
因為資料型態預設是字串,所以後面的:string其實可以不用加
也就是

$ mix phx.gen.schema User users name email

這樣有一模一樣的效果

migration則與rails大同小異:

defmodule Hello.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string

      timestamps()
    end

  end
end

timestamps()會自動產生inserted_atupdated_at兩個時間戳記
在資料新增或更新時自動更新
不需要的話也可以拿掉

實務上不會在新增時就把所有欄位都想清楚
在migrate之前直接修改檔案都是可以的
就算已經migrate,也可以rollback以後修改
切記:

修改已經migrate後的migration是不會生效的!

比較Rails相同目的的指令:

$ rails g model user name:string email:string
Running via Spring preloader in process 97514
      invoke  active_record
      create    db/migrate/20171211081938_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

可以看到一樣產生了一個migration檔
一個同名的model,以及測試的檔案(但實務上會把自動產生test_unit關閉,因為都用rspec)
這邊有蠻多不同點可以與Rails做比較討論:

Schema的不同

比較 Rails Phoenix
1.產生時機 migrate後 migrate前
2.檔案數量 所有model合併在同一個schema檔 一個model一個檔案
3.檔案內容 schema與model為不同檔案 schema與model在同一個檔案
4.指令 使用單數Model名稱 需要分別指定名稱

1. Schema產生時機

要討論時機不同的差異,首先我們要了解migrate的意涵:
migrate這個指令是把migration的內容實際作用到資料庫當中
在migrate之前,migration就像是一個計畫書
執行migrate以後,就會按照計劃書開始對資料庫動工
所以如果你在migration有bug,也會等到migrate的時候才會告訴你

從這個角度來看,如果我們將schema視為資料庫結構的寫照
理應在migrate之後才產生比較合理
在migrate之前產生的schema其實與migration無異
都只是我們的預期
但寫程式的我們必須接受一個現實:

預期與現實常常是有落差的

因此我認為Rails在migrate之後產生schema比較合理


從另外一個角度我想繼續談資料庫與程式碼之間的差異:
新手常常會有一個誤會
就是以為版本控制(git)會聯同資料庫結構一起被追蹤
比方說切換不同分支(Branch)或git pull最新版本
會連著資料庫結構一起變更,事實上不會
版本控制只能追蹤程式碼,與資料庫是兩回事

切換版本的時候如果想同時更新資料庫狀態
實務上的做法是先把當前版本的migration 還原(rollback)
切換後再migrate新的migration(假如有的話)
假如沒有按照程序做的話
可能會遇到一些奇奇怪怪的資料庫不一致的問題
要特別留意!

在Rails的情況下,migrate後的schema就是資料庫當前的結構
但在Phoenix卻是事實上與資料庫脫節的
這也是為何我認為Rails的處理較為合理的原因

2. 檔案數量

先講結論,我覺得Phoenix這邊的處理我比較喜歡
一個專案動輒二三十個model,schema全寫在同一個檔案
查找上有些不方便
分散為同名的檔案確實方便管理多了

3. 檔案內容

Phoenix將schema與model在同一個檔案做法
我目前還不確定是好是壞
可能要有更多開發經驗,感覺一下才能知曉

Rails是兩個部分分開
按照程式的常規習慣
不同的東西通常放在不同地方
會比較有利於查找

4. 指令

指令有些相同也有相異的部分
先講不同之處:
Rails的慣例是產生model時使用單數
如同上例我們使用user,會自動產生一個複數的table名稱users
程式中表示Class則是單數大寫User

Phoenix在創建Model(Phoenix稱為schema)時必須指定名稱
也就是上面提到的mix phx.gen.schema User users
前者會成為Class名稱,後者是table名稱
當然你也可以用完全無關的兩個字(但建議千萬不要這麼做)
建議遵循前者單數首字大寫;後者複數小寫的慣例
會比較愉快順利

相同之處也不少:
產生model時後面可以指定欄位,也可以略過
指定欄位時如果是String時,型態可以略過
也可以跳過Model,單純產生migration:

# Phoenix
$ mix ecto.gen.migration your_migration_name
# Rails
$ rails g migration your_migration_name

但Rails的migration名稱如果符合命名慣例會有一些神奇的魔法
這部分我不確定Phoenix有沒有
實務上都是產生後,直接編輯migration檔案比較多


Migrate 操作

如果想要查看所有的migration狀態,語法是:

# Phoenix
$ mix ecto.migrations
# Rails
$ rails db:migrate:status

想查看所有ecto指令,可以使用mix ecto

mix ecto.create        # Creates the repository storage
mix ecto.drop          # Drops the repository storage
mix ecto.dump          # Dumps the repository database structure
mix ecto.gen.migration # Generates a new migration for the repo
mix ecto.gen.repo      # Generates a new repository
mix ecto.load          # Loads previously dumped database structure
mix ecto.migrate       # Runs the repository migrations
mix ecto.migrations    # Displays the repository migration status
mix ecto.rollback      # Rolls back the repository migrations

一切的變更,都要等到migrate以後才會真正作用到資料庫

$ mix ecto.migrate
Compiling 1 file (.ex)
Generated hello app
[info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward
[info] create table users
[info] == Migrated in 0.0s

如同提示,他產生了一個名為users的table
我們可以用mix ecto.migrations來確認這件事

$ mix ecto.migrations

Repo: Hello.Repo

  Status    Migration ID    Migration Name
--------------------------------------------------
  up        20171211082116  create_users

假如你後悔了,或是還想做一些修改,當然可以rollback

$ mix ecto.rollback
[info] == Running Hello.Repo.Migrations.CreateUsers.change/0 backward
[info] drop table users
[info] == Migrated in 0.0s

這個時候再查一次,就會看到:

$ mix ecto.migrations

Repo: Hello.Repo

  Status    Migration ID    Migration Name
--------------------------------------------------
  down      20171211082116  create_users

唯一的migration狀態由up變為down

假如你要切換到另外一個分支,而那個分支中並沒有你當前的migration
最好要把那些migration都down下來,避免有奇怪的影響

反之當你切換或是pull一個最新版本時,裡面有新的migration
程式一般都會提醒你開啟server前先執行migrate
把那些資料庫變更都實現到你本地的資料庫

保持這些好習慣,就是新手與資深開發人員的差距唷!


後記:
躺在床上的時候想起有一種情境忘了講
起來趕快補上避免遺忘!

假如你沒有先把migration down下來
就算把它刪除,對資料庫的作用依然存在
刪除後如果查詢資料庫結構會看到

$ mix ecto.migrations

Repo: Hello.Repo

  Status    Migration ID    Migration Name
--------------------------------------------------

一片空白!
但事實上如果用iex下去查,schema還是存在的唷
這可能是Phoenix的一個Bug
因為我原本預期會跟Rails一樣

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20171211081938  ********** NO FILE **********

刪除後會顯示NO FILE,但同時還是up的唷!
事實上當你切換到沒有這個migration的分支
就本地端程式的視角,跟你刪除它是一樣的

反之,如果你是在down下來的情況下刪除migration檔
他就不會出現在清單上了


上一篇
Phoenix起步走:新增頁面與路由
下一篇
Phoenix起步走:快速產生CRUD頁面
系列文
新時代的網頁框架比較-- 淺談Rails、Django、Phoenix、Laravel31

尚未有邦友留言

立即登入留言