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.ex
與20171211072512_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 已經有兩個欄位:name
與email
這是因為產生時我們在後面加註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_at
與updated_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做比較討論:
比較 | Rails | Phoenix |
---|---|---|
1.產生時機 | migrate後 | migrate前 |
2.檔案數量 | 所有model合併在同一個schema檔 | 一個model一個檔案 |
3.檔案內容 | schema與model為不同檔案 | schema與model在同一個檔案 |
4.指令 | 使用單數Model名稱 | 需要分別指定名稱 |
要討論時機不同的差異,首先我們要了解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的處理較為合理的原因
先講結論,我覺得Phoenix這邊的處理我比較喜歡
一個專案動輒二三十個model,schema全寫在同一個檔案
查找上有些不方便
分散為同名的檔案確實方便管理多了
Phoenix將schema與model在同一個檔案做法
我目前還不確定是好是壞
可能要有更多開發經驗,感覺一下才能知曉
Rails是兩個部分分開
按照程式的常規習慣
不同的東西通常放在不同地方
會比較有利於查找
指令有些相同也有相異的部分
先講不同之處:
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檔案比較多
如果想要查看所有的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檔
他就不會出現在清單上了