RailsからPhoenixに来た人にはActiveRecordというとてつもないORMの経験が染み付いていると思います。
rails g scaffold ~~~
なんかすると基本的なことは概ね出来上がった状態でモデルが生成されちゃいます。
ElixirのORMとしてはEctoがとても有名ですがPhoenixのコマンドでできたファイルにはCRUDのコードがハードコードされた状態で生成されます。
前から気になってたのですが雨で暇なのでActiveRecordっぽい感じに共通の実装を単一のモジュールから呼び出せないかやってみました。
useマクロとは?
Elixirにはuseマクロという他のモジュールを利用して呼び出し元のモジュールの定義を変更する方法が提供されています。
モジュールの中でuse句を書いてから使いたいモジュールを書くだけです
defmodule Hoge do
use Fuga
end
useされる側のモジュールはこの時こんな感じ
defmodule Fuga do
defmacro __using__(_opts) do
quote do
# なにか書く
end
end
end
この quote do ~ end
の中に関数を定義すればuseした側でも使うことができる
よく使いそうなやつ関数を書いてみる
全体像
defmodule TestApp.ActiveRecord do
require IEx
defmacro __using__(schema: schema) do
quote do
alias TestApp.Repo
import Ecto.Query
def all do
unquote(schema)
|> Repo.all()
end
def find(id) do
unquote(schema)
|> Repo.get!(id)
end
def find_by(conds) do
unquote(schema)
|> Repo.get_by(conds)
end
def where(conds) do
from(unquote(schema), where: ^conds)
|> Repo.all()
end
def insert(attr) do
%unquote(schema){}
|> unquote(schema).changeset(attr)
|> Repo.insert()
end
def insert!(attr) do
%unquote(schema){}
|> unquote(schema).changeset(attr)
|> Repo.insert!()
end
def insert_all(maps, opts \\ []) do
now = DateTime.utc_now() |> NaiveDateTime.truncate(:second)
changesets =
maps
|> Enum.map(fn map -> Map.merge(%{inserted_at: now, updated_at: now}, map) end)
Repo.insert_all(unquote(schema), changesets, opts)
end
def update(account, attr) do
account
|> unquote(schema).changeset(attr)
|> Repo.update()
end
def update!(account, attr) do
account
|> unquote(schema).changeset(attr)
|> Repo.update!()
end
def delete(account), do: Repo.delete(account)
def delete!(account), do: Repo.delete!(account)
def delete_all(), do: Repo.delete_all(unquote(schema))
end
end
end
こんな感じで書いてみた
__using__
マクロの引数で schema: schema
としているのがポイントで呼び出し側で関連するschemaを指定してやることでActiveRecordっぽくクエリを実行することができる。
なので呼び出し元はこんな感じになる
defmodule TestApp.User do
use TestApp.ActiveRecord, schema: TestApp.User.Account
end
あとは必要に応じて追加で関数を書いたりすれば良し
お知らせ
個人的に使ってみた感じ結構便利だったのでhex.pmに公開しました
hex.pm/itaco
とりあえず個人用で公開したのでドキュメントとかはこれからなんですがよかったらお使いください。