お尻に挿入する方法とプラグ

[だいたいここに載ってる](https://hexdocs.pm/elixir/Module.html)。

モジュールの末尾に関数ないしマクロを挿入したい場合、`@before_compile`を利用すればよい。

# before_compileとは

>A hook that will be invoked before the module is compiled.

>Accepts a module or a `{module, function_or_macro_name}` tuple. The function/macro must take one argument: the module environment. If it’s a macro, its returned value will be injected at the end of the module definition before the compilation starts.

>When just a module is provided, the function/macro is assumed to be `__before_compile__/1`.

([ドキュメント](https://hexdocs.pm/elixir/Module.html#module-compile-callbacks)より)

## 意訳
あるモジュールがコンパイルされる前に呼ばれる関数ないしマクロ。
`@before_compile Foo`などと指定すると、`Foo.__before_compile__/1`が呼び出される。引数は`env`。
関数/マクロの名前まで指定したいときはタプル形式で。
マクロを呼び出したとき、返り値のASTはモジュールの末尾に挿入される。

## 嬉しいところ
モジュールの末尾に関数を挿入できるということは、それまでに定義したアトリビュートを使えるということ。
例えば、こんな感じの雰囲気になる。

```elixir
defmodule Foo do
  defmacro __using__(_) do
    quote do
      require Foo
      @before_compile Foo
    end
  end

  defmacro __before_compile__(_env) do
    IO.puts "__before_compile__"
    quote do
      def hoge do
        IO.inspect @store
      end
    end
  end
end

defmodule Bar do
  use Foo

  @store [:foo]
  @store [:bar | @store]
end
```

この例では`@store`に値を貯め、末尾に挿入した`Bar.hoge/0`の中でその値を使っている。(こんなやり方より`Module.register_attribute/3`で`accumulate: true`を指定したほうがいいけど)
こんなものをきっとテストやPlugで見たことがあるはずだ。`plug/2`マクロをモジュールの中で呼んでいるとどういうわけか`conn`がそれらを潜っていくが、その秘密はここにある。

# Plugでの使われ方
ここからは[Plugのソース](https://github.com/elixir-plug/plug/blob/729cbb8cfe6e9dcad0244a57bc236b3704d72d27/lib/plug/builder.ex)からひたすら引用。

これは`Plug.Builder`の`__using__/1`。

```elixir
defmacro __using__(opts) do
  quote do
    @behaviour Plug
    @plug_builder_opts unquote(opts)

    def init(opts) do
      opts
    end

    def call(conn, opts) do
      plug_builder_call(conn, opts)
    end

    defoverridable init: 1, call: 2

    import Plug.Conn
    import Plug.Builder, only: [plug: 1, plug: 2]

    Module.register_attribute(__MODULE__, :plugs, accumulate: true)
    @before_compile Plug.Builder
  end
end
```

`Module.register_attribute/3`で`@plugs`をリストにしており、`@plugs hoge`などとするたび、上書きする代わりにリストに値が追加されるようになっている。
また、追加される`call/2`はデフォルトで`plug_builder_call`を呼び出すが、この関数は`Plug.Builder.__before_compile__/1`によってモジュール末尾に追加される。

```elixir
defmacro __before_compile__(env) do
  plugs = Module.get_attribute(env.module, :plugs)
  builder_opts = Module.get_attribute(env.module, :plug_builder_opts)

  {conn, body} = Plug.Builder.compile(env, plugs, builder_opts)

  quote do
    defp plug_builder_call(unquote(conn), _), do: unquote(body)
  end
end
```

`plug/2`は短いマクロで、単に`@plugs`にプラグを登録していくだけ。

```elixir
defmacro plug(plug, opts \\ []) do
  quote do
    @plugs {unquote(plug), unquote(opts), true}
  end
end
```

これで作り上げたパイプラインを`Plug.Builder.compile/3`によって実際に指定したプラグを次々と呼び出すシーケンスに変換される……と思うんだけど追うのがめんどくさくなってきたのでここで終わり。
`@plugs`の中にはプラグが`plug/2`を呼び出したのとは逆順に入っているけど、`Plug.Builder.compile/3`と、そこからパイプラインに入れたプラグの数だけ呼ばれる`Plug.Builder.quote_plug/4`によって`plug3(plug2(plug1(conn)))`みたいな感じに変換されるんだって。すっごーい！

>  # `acc` is a series of nested plug calls in the form of
>  # plug3(plug2(plug1(conn))). `quote_plug` wraps a new plug around that series
>  # of calls.