Whatwasit 是一個跟蹤Ecto模型變化的一個包, 用於審計和版本化. 審計在某些狀況下是咱們很是須要的, 好比咱們須要知道誰在系統中修改了什麼, 能夠造成審計日誌備後期進行審查.html
注意: Whatwasit(讀做:
What was it
) 須要Elixir 1.2的支持, 因此要使用 Whatwasit 請首先升級到Elixir 1.2以上前端
使用 Whatwasit 很簡單, 只須要添加在模型中添加兩行代碼便可, 下面咱們來細說這個過程. 首先建立一個項目:git
這裏咱們只是測試如何使用Whatwasit, 因此去掉前端庫Brunch(--no-brunch
).github
mix phoenix.new whatwasit_example --no-brunch
在mix.exs
中增長依賴:web
defp deps do [ ... {:whatwasit, "~> 0.2.1"} ] end
切換到命令行執行數據庫
mix deps.get && mix compile
運行下面的用於建立存儲版本和變動的模型和數據庫遷移腳本segmentfault
➜ whatwasit_example mix whatwasit.install * creating priv/repo/migrations/20160801031533_create_whatwasit_version.exs * creating web/models/whatwasit/version.ex Add the following to your config/config.exs: config :whatwasit, repo: WhatwasitExample.Repo Update your models like this: defmodule WhatwasitExample.Post do use WhatwasitExample.Web, :model use Whatwasit # add this schema "posts" do field :title, :string field :body, :string timestamps end def changeset(model, params \ %{}) do model |> cast(params, ~w(title body)) |> validate_required(~w(title body)a) |> prepare_version # add this end end
執行數據庫遷移腳本瀏覽器
mix ecto.migrate
命令行提示你在模塊頭部添加 use Whatwasit
, 在changeset/2
方法的管道尾部添加 prepare_version
函數追蹤數據庫的變動. 版本存儲在 versions
表裏面, 其結構以下:session
圖中的 object
字段是一個JSON數據, 存儲了修改以前的快照版本. Whatwasit.Version
模型的定義以下:app
schema "versions" do field :item_type, :string field :item_id, :integer field :action, :string # ~w(update delete) field :object, :map # versioned schema stored as a map timestamps end
其對應的數據庫遷移腳本以下:
defmodule WhatwasitExample.Repo.Migrations.CreateWhatwasitVersion do use Ecto.Migration def change do create table(:versions) do add :item_type, :string, null: false add :item_id, :integer, null: false add :action, :string add :object, :map, null: false timestamps end end end
下面是一個博客的示例
defmodule WhatwasitExample.Post do use WhatwasitExample.Web, :model use Whatwasit alias WhatwasitExample.Repo schema "posts" do field :title, :string field :body, :string timestamps end def getbypk(id) do Repo.get(__MODULE__, id) end def updatebypk(changeset, changes) when is_map(changes) do changeset = changeset |> Ecto.Changeset.change(changes) changeset |> Repo.update end def user_changeset(struct, params \\ %{}) do # cast/3 把瀏覽器POST過來的數據強制轉換爲schema中定義的數據類型 # validate_required/3 驗證要求的字段, message選項爲錯誤提示 struct |> cast(params, [:title, :body]) |> validate_required([:title, :body], [message: "標題和內容是必須的"]) |> prepare_version end # def insert(map) do # Map.merge(%__MODULE__{}, map) |> Repo.insert # end def insert(params) do changeset = user_changeset(%__MODULE__{}, params) if changeset.valid? do Repo.insert(changeset) else raise "Changeset is invalid." end end @doc """ 更新一條記錄 """ def update(params) do # 從數據庫獲取一個 %WhatwasitExample.Post{} 結構 struct = getbypk(params[:id]) case struct do nil -> raise "Record not exists." struct -> fields = Map.delete(params, :id) # 經過瀏覽器傳過來的POST數據建立一個Ecto.Changeset changeset = user_changeset(struct, fields) if changeset.valid? do changeset |> Repo.update else raise "Changeset is invalid when update." end end end @doc """ 按主鍵ID刪除一條記錄 """ @spec delete(map) :: Ecto.Schema.t | :no_return def delete(%{"id" => id}) do # 從數據庫獲取一個 %WhatwasitExample.Post{} 結構 # 從 %WhatwasitExample.Post{} 建立一個 Ecto.Changeset # 把這個 Ecto.Changeset 傳遞給 Ecto.Repo.delete!/2 Repo.get!(__MODULE__, id) |> __MODULE__.user_changeset |> Repo.delete! end end
首先須要添加 {:coherence, "~> 0.2.0"}
依賴, Coherence 是一個用戶管理包, 提供了用戶系統的經常使用功能, 包括:
註冊, 註冊新用戶
郵件激活, 生成郵件激活連接經過郵件發送給用戶
密碼恢復, 生成密碼找回鏈接經過郵件發送給用戶
登陸跟蹤, 爲每一個保存了每次登陸的時間, 次數, IP地址
鎖定, 登陸N次錯誤後自動鎖定用戶一段時間
解鎖, 生成一個解鎖鏈接經過郵件發送給用戶
初始化 Coherence
mix coherence.install --full-invitable
上述命令會執行以下步驟:
添加 coherence 的配置到 config/config.exs
文件的尾部.
若是用戶模型不存在, 添加新的用戶模型
添加數據庫遷移腳本文件
timestamp_add_coherence_to_user.exs 若是用戶模型已經存在
timestamp_create_coherence_user.exs 若是用戶模型不存在
timestamp_create_coherence_invitable.exs
在 web/views/coherence/
中添加相關的視圖
在 web/templates/coherence
添加相關的模板
在 web/emails/coherence
中添加電子郵件模板
添加 web/coherence_web.ex
文件
最後查看一下 config/config.exs
文件編輯電子郵件的Key, 這裏你能夠申請一個 mailgun 的郵件服務key用於測試.
完整的命令輸出
➜ whatwasit_example# mix coherence.install --full-invitable Your config/config.exs file was updated. Compiling 14 files (.ex) warning: unused import Ecto web/models/whatwasit/version.ex:7 Generated whatwasit_example app * creating priv/repo/migrations/20160801060750_create_coherence_user.exs * creating web/models/coherence/user.ex * creating priv/repo/migrations/20160801060751_create_coherence_invitable.exs * creating web/coherence_web.ex * creating web/views/coherence/coherence_view.ex * creating web/views/coherence/email_view.ex * creating web/views/coherence/invitation_view.ex * creating web/views/coherence/layout_view.ex * creating web/views/coherence/coherence_view_helpers.ex * creating web/views/coherence/password_view.ex * creating web/views/coherence/registration_view.ex * creating web/views/coherence/session_view.ex * creating web/views/coherence/unlock_view.ex * creating web/templates/coherence/email/confirmation.html.eex * creating web/templates/coherence/email/invitation.html.eex * creating web/templates/coherence/email/password.html.eex * creating web/templates/coherence/email/unlock.html.eex * creating web/templates/coherence/invitation/edit.html.eex * creating web/templates/coherence/invitation/new.html.eex * creating web/templates/coherence/layout/app.html.eex * creating web/templates/coherence/layout/email.html.eex * creating web/templates/coherence/password/edit.html.eex * creating web/templates/coherence/password/new.html.eex * creating web/templates/coherence/registration/new.html.eex * creating web/templates/coherence/session/new.html.eex * creating web/templates/coherence/unlock/new.html.eex * creating web/emails/coherence/coherence_mailer.ex * creating web/emails/coherence/user_email.ex Add the following to your router.ex file. defmodule WhatwasitExample.Router do use WhatwasitExample.Web, :router use Coherence.Router # Add this pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug Coherence.Authentication.Session, login: true # Add this end pipeline :public do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug Coherence.Authentication.Session # Add this end # Add this block scope "/" do pipe_through :public coherence_routes :public end # Add this block scope "/" do pipe_through :browser coherence_routes :private end scope "/", WhatwasitExample do pipe_through :public get "/", PageController, :index end scope "/", WhatwasitExample do pipe_through :browser # Add your protected routes here end end You might want to add the following to your priv/repo/seeds.exs file. WhatwasitExample.Repo.delete_all WhatwasitExample.User WhatwasitExample.User.changeset(%WhatwasitExample.User{}, %{name: "Test User", email: "testuser@example.com", password: "secret", password_confirmation: "secret"}) |> WhatwasitExample.Repo.insert! Don't forget to run the new migrations and seeds with: $ mix ecto.setup
刪除以前生成的數據庫遷移腳本
rm priv/repo/migrations/20160801064451_create_whatwasit_version.exs
運行以下命令, 從新生成模型和數據庫遷移腳本
whatwasit.install --whodoneit
從新建立數據庫
mix ecto.reset
|> prepare_version
修改成 |> prepare_version(opts)
, 傳入 opts
參數.
修改後的 Post 模型的 changeset 函數, 增長第三個參數 opts
:
def user_changeset(struct, params \\ %{}, opts \\ %{}) do # cast/3 把瀏覽器POST過來的數據強制轉換爲schema中定義的數據類型 # validate_required/3 驗證要求的字段, message選項爲錯誤提示 struct |> cast(params, [:title, :body]) |> validate_required([:title, :body], [message: "標題和內容是必須的"]) |> prepare_version(opts) end
上面的多個手工步驟能夠用 mix phoenix.gen.html Post posts title:string body:string
自動生成控制器, 視圖, 模型, 模板文件. 而後修改, 能夠少些不少代碼.
上述步驟都完成後, 能夠經過命令 mix phoenix.routes
查看全部的HTTP端點
➜ whatwasit_example mix phoenix.routes session_path GET /sessions/new Coherence.SessionController :new session_path POST /sessions Coherence.SessionController :create registration_path GET /registrations/:id/edit Coherence.RegistrationController :edit registration_path GET /registrations/new Coherence.RegistrationController :new registration_path POST /registrations Coherence.RegistrationController :create registration_path PATCH /registrations/:id Coherence.RegistrationController :update PUT /registrations/:id Coherence.RegistrationController :update registration_path DELETE /registrations/:id Coherence.RegistrationController :delete password_path GET /passwords/:id/edit Coherence.PasswordController :edit password_path GET /passwords/new Coherence.PasswordController :new password_path POST /passwords Coherence.PasswordController :create password_path PATCH /passwords/:id Coherence.PasswordController :update PUT /passwords/:id Coherence.PasswordController :update password_path DELETE /passwords/:id Coherence.PasswordController :delete unlock_path GET /unlocks/:id/edit Coherence.UnlockController :edit unlock_path GET /unlocks/new Coherence.UnlockController :new unlock_path POST /unlocks Coherence.UnlockController :create invitation_path GET /invitations/:id/edit Coherence.InvitationController :edit invitation_path GET /invitations/new Coherence.InvitationController :new invitation_path POST /invitations Coherence.InvitationController :create invitation_path POST /invitations/create Coherence.InvitationController :create_user invitation_path GET /invitations/:id/resend Coherence.InvitationController :resend session_path DELETE /sessions/:id Coherence.SessionController :delete page_path GET / WhatwasitExample.PageController :index post_path GET /posts WhatwasitExample.PostController :index post_path GET /posts/:id/edit WhatwasitExample.PostController :edit post_path GET /posts/new WhatwasitExample.PostController :new post_path GET /posts/:id WhatwasitExample.PostController :show post_path POST /posts WhatwasitExample.PostController :create post_path PATCH /posts/:id WhatwasitExample.PostController :update PUT /posts/:id WhatwasitExample.PostController :update post_path DELETE /posts/:id WhatwasitExample.PostController :delete
這樣一個基本的帶用戶註冊, 密碼找回, 用戶激活, 等功能的應用程序的基本結構就完成了. 在此基礎之上能夠擴展功能實現更加完整的Web應用程序.
目前模型裏面的changeset須要重命名, PROJECT_NAME.Whatwasit.Version
模塊中的changeset
函數和經過mix phoenix.gen.html
生成的模型中的changeset
函數名稱衝突, 建議修改模型中的changeset
函數爲post_changeset
mix whatwasit.install --whodoneit-map
和 mix whatwasit.install --whodoneit
區別是, mix whatwasit.install --whodoneit
在versions表中用兩個字段分別存儲用戶名稱和用戶ID, 這是對users
表的引用, mix whatwasit.install --whodoneit-map
, 用一個字段whodoneit
存儲的是一個除密碼以外的用戶全部信息的一個JSON對象. 後者不依賴於用戶信息的變動.
若是用戶模型的主鍵類型爲UUID, 可使用 mix whatwasit.install --whodoneit-id-type=uuid
最後咱們在瀏覽器中輸入 http://127.0.0.1:4000/posts/new 建立一條記錄, 並編輯, 編輯的日誌輸出爲.
這裏是這篇文章的倉庫地址: https://github.com/developerw...