LoginSignup
4
2

More than 1 year has passed since last update.

PhoenixのcontextをActiveRecordっぽく動かせるか試す

Last updated at Posted at 2021-04-29

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に公開しました :tada:
hex.pm/itaco
とりあえず個人用で公開したのでドキュメントとかはこれからなんですがよかったらお使いください。

4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2