スポンサーリンク

2015年7月19日

PhoenixのPlugについて分かったこと

Goal

Phoenix - Guide Plugを実施する。
Plugの機能を検証する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

Rails Tutorialの第九章でPlugを使う必要が出てきたので、
PhoenixのGuideを検証してみる。
検証用に新しくプロジェクトを作成した。
>mix phoenix.new plug_sample
Fetch and install dependencies? [Yn] y
>mix phoenix.server
>ctrl+c
この記事では以降、プロジェクトと言ったら
plug_sampleを指し示す。

Index

  1. What is Plug
  2. Function Plugs
  3. Module Plugs
  4. When action in
  5. Extra

1. What is Plug

Plugとは何でしょうか?
PlugのGithubリポジトリにあるREADMEにはこう書いてありました。
  1. Webアプリケーション間での構成可能なモジュールの仕様
  2. ErlangのVM内の別のWebサーバーの接続アダプタ
PhoenixのGuideでは上記に加えて・・・
プラグインの基本的な考え方は、私たちが動作する「接続」の概念を統一すること。
これは、要求と応答をミドルウェア·スタック内で分離されるような
Rackなどの他のHTTPのミドルウェア層とは異なります。
とあります。
結局何ができるの?
最も単純なレベルでのプラグの仕様は2種類ある。
機能プラグとモジュールプラグです。
簡単に言うと関数とモジュールのプラグがあると言うこと。
実際に手を動かして実行&検証で見ていきます。

2. Function Plugs

まずは機能プラグから触ってみましょうか。
プラグインとして機能するために、
関数はシンプルな接続構造体(%Plug.Conn{})とオプションを受け入れる必要があります。
また接続構造体を返す必要があります。
これらの基準を満たす任意の関数で行います。
ここからは、Guideに則らない形で進めます。
具体的に言うと実行しているソースコードが違います。
誤解なきよう書いておきますが、
Guideに載っているソースコードはシンプルで非常に良いです。
この記事では、本当に最低限の実行しか行いません。
file: web/controllers/page_controller.ex
defmodule PlugSample.PageController do
  use PlugSample.Web, :controller

  plug :put_info_message
  plug :action

  def index(conn, _params) do
    render conn, "index.html"
  end

  defp put_info_message(conn, _) do
    conn |> Phoenix.Controller.put_flash(:info, "execute: put_info_message")
  end
end
Description1:
put_info_message/2を定義しました。
“plug :put_info_message”で使用する旨をコントローラに定義しています。
Description2:
定義したメソッドの引数には、connとアンダーバーになっています。
アンダーバーはオプションに相当しますが、
今回定義した関数ではオプションは必要なかったのでアンダーバーを記述しています。
実行してflash機能でメッセージが出ているか確認しましょう。

3. Module Plugs

これは以前の記事で使用したことがある機能ですね。
ここでおさらいをしておきましょう!
モジュールプラグでは、二つの機能を実装する必要があります。
  1. 任意の引数やオプションを初期化するためinit/1を実装する。call/2に渡されます。
  2. 接続変換を行うcall/2を実装する
以前実行した時と同じようにファイルを作成します。
ファイル: lib/plugs/module_plug_sample.ex
defmodule PlugSample.Plugs.ModulePlugSample do
  import Plug.Conn

  def init(default), do: default  def call(conn, _) do
    conn |> assign(:message, "execute: ModulePlugSample call/2")
  end
end
ファイル: file: web/controllers/page_controller.ex
defmodule PlugSample.PageController do
  use PlugSample.Web, :controller

  plug PlugSample.Plugs.ModulePlugSample
  plug :put_info_message
  plug :action

  def index(conn, _params) do
    render conn, "index.html"
  end

  defp put_info_message(conn, _) do
    conn |> Phoenix.Controller.put_flash(:info, "execute: put_info_message")
  end
end
ファイル: web/views/page_view.ex
defmodule PlugSample.PageView do
  use PlugSample.Web, :view

  def module_plug_sample_message(conn) do
    conn.assigns[:message]
  end
end
ファイル: web/templates/page/index.html.eex
<div class="jumbotron">
  <%= if module_plug_sample_message(@conn) do %>
    <h2><%= module_plug_sample_message(@conn) %></h2>
  <% end %>
</div>
これでメッセージが表示されます。

4. When action in

最後にちょっとした応用を実施して終わりにしたいと思います。
特定のアクションが実行される時だけ動作させたいと思いませんか?
そういった時に対応できる記述があります。
ファイル: web/router.ex
defmodule PlugSample.Router do
  use PlugSample.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", PlugSample do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/test", PageController, :test
  end

  # Other scopes may use custom stacks.
  # scope "/api", PlugSample do
  #   pipe_through :api
  # end
end
ファイル: web/controllers/page_controller.ex
defmodule PlugSample.PageController do
  use PlugSample.Web, :controller

  plug PlugSample.Plugs.ModulePlugSample when action in [:index]
  plug :put_info_message when action in [:index]
  plug :action

  def index(conn, _params) do
    render conn, "index.html"
  end

  def test(conn, _params) do
    render conn, "index.html"
  end

  defp put_info_message(conn, _) do
    conn |> Phoenix.Controller.put_flash(:info, "execute: put_info_message")
  end
end
Description:
以下の記述に注目!!
when action in [:index]
これで、[]内のアクションが実行される時に動作しろと定義しています。
今回はindexアクションが実行される時にだけ動作するように定義しています。
実行してtestアクションでは動作しないことを確認してみて下さい。
閑話休題・・・
Phoenixで使われている機能を少し紹介をしようと思います。
“mix phoenix.gen.html”を使って自動生成されたコントローラで以下の記述を見たことがないでしょうか?
plug :scrub_params, "user" when action in [:create, :update]
Description:
create、updateアクションのパラメータに”user”があるかチェックしています。

5. Extra

最近、mix phoenix.serverを実行すると以下のようメッセージが出てくることがある。
postgrexで起きているようだが、mix.exsの内容を書き換えてあげれば特に問題はない。
* postgrex (Hex package)
  the dependency postgrex defined

  > In mix.exs:
    {:postgrex, ">= 0.0.0", [hex: :postgrex]}

  does not match the requirement specified

  > In deps/ecto/mix.exs:
    {:postgrex, "~> 0.8.3", [optional: true, hex: :postgrex]}

  Ensure they match or specify one of the above in your PlugSample.Mixfile deps and set `override: true`
** (Mix) Can't continue due to errors on dependencies
以下の記述を・・・
{:postgrex, ">= 0.0.0", [hex: :postgrex]}
こうして・・・
{:postgrex, "~> 0.8.3", [optional: true, hex: :postgrex]}
再度、依存関係の解決。
>mix deps.get
これで問題なく動作する。
,(カンマ)を忘れないように注意!!
おそらくなのだが、私のarchive.installで入っている
Phoenixがv0.13.1でdepsで取得しているPhoenixがv0.14.0なので、
その差異のため起こっていると思われる。
特に問題はない。
早くアップデートしろってことですね。
残念ながらRails Tutorialを終えるまではアップデートしませんが・・・

Speaking to oneself

今日初めて知ったんですけど・・・
Elixirのdefpってdef privateだったんですね。
これで公開したくない関数が記述できる。
今回実施した内容が何の役に立つかって?
説明しよう!!
例えば、AuthenticationやAuthorizationを実施する時に
全てのコントローラの全てのアクションでチェックする処理を記述したいですか?
私はいやです。
そんな面倒なことをするのは。
そこで今回やったPlugの機能が出てきます。
モジュールプラグで定義しておけば、
後はplugを利用するだけで実行できます。
また動作的には、コントローラのアクションが実行される前に実行されるので、
Railsにおけるbefore_actionのような動作をさせることもできるでしょう。
そしてRailsTutorialの第九章にはbefore_actionが出てきます。
そういうことです。
眠い・・・やっぱり深夜帯に更新するのはやめておこう。
次の日のダメージが大きい。
ちょっとテンションがおかしいので、
気に障る文章を書いていたらすいません・・・

Bibliography

人気の投稿