phoenix_guide

2017年6月17日

Phoenixの公式のGuideを読んだ時のメモ

Phoenix guide

create project

mix phoenix.new /path/to/project/directory/ mix phoenix.new --no-brunch /path/to/project/directory/

serve mix phoenix.sever iex -S mix phoenix.server

database mix ecto.create

Routes

[http://www.phoenixframework.org/docs/routing]

`(get|post|put|delete) "/", PageController`
GET "/" => PageController  :index

`resources "users", UserController`
GET     "/users"            :index
GET     "/users/:id/edit"   :edit
GET     "/users/new"        :new
GET     "/users/:id"        :show
POST    "/users"            :create
PATCH   "/users/:id"        :update
PUT     "/users/:id"        :update
DELETE  "/uusers/:id"       :delete

`resources "/users" UserController :only [:index, :show]`
GET     "/users"            :index
GET     "/users/:id"        :show

`resources "/users" UserController :except [:delete]`
GET     "/users"            :index
GET     "/users/:id/edit"   :edit
GET     "/users/new"        :new
GET     "/users/:id"        :show
POST    "/users"            :create
PATCH   "/users/:id"        :update
PUT     "/users/:id"        :update

Path Helper

iex> PhoenixSample.Router.Helpers.page_path(PhoenixSample.Endpoint, :index)
"/"

Ex: <a href="<%= page_path(@conn, :index) %>">To the Welcome Page!</a>

iex> import HelloPhoenix.Router.Helpers
iex> alias HelloPhoenix.Endpoint
iex> user_path(Endpoint, :index)
"/users"
iex> user_path(Endpoint, :show, 17)
"/users/17"
iex> user_path(Endpoint, :new)
"/users/new"
iex> user_path(Endpoint, :create)
"/users"
iex> user_path(Endpoint, :edit, 37)
"/users/37/edit"
iex> user_path(Endpoint, :update, 37)
"/users/37"
iex> user_path(Endpoint, :delete, 17)
"/users/17"
iex> user_path(Endpoint, :show, 17, admin: true, active: false)
"/users/17?admin=true&active=false"
path => urlでfullpath
iex(3)> user_url(Endpoint, :index)
"http://localhost:4000/users"

scoped

scope "/admin", HelloPhoenix.Admin, as: :admin do
  pipe_through :browser

  resources "/images",  ImageController
  resources "/reviews", ReviewController
  resources "/users",   UserController
end

Plug stack

pipe_through :browser phoenixはデフォルトで:apiと:browserのパイプラインを作成している

Endpoint Plugsについて

順番

  • Pulg.Static
  • Plug.Logger
  • Phoenix.CodeReloader
  • Plug.Parser
  • Plug.MethodOverride
  • Plug.Head
  • Plug.Session
  • Plug.Router

:browser and :api

:browser pipeline

plug :accepts, ["html"]    # request format
plug :fetch_session                 # fetch session data
plug :fetch_flash                     # regrieves any flash messages
plug :protect_from_forgery               # protect from posts from crosssite forgery
plug :put_secure_browser_headers # ---

:api pipeline

plug :accepts, ["json"]

custom new pipeline

pipeline :new_pipeline  do
  plug : ~~~
  plug : ~~~
end

scope */new_pipeline_scope*, HogePhoenix do
  pipe_through :new_pipeline

  resources "/". ReviewController
end

Channel Routes

endpoint.ex

defmodule HelloPhoenix.Endpoint do
  use Phoenix.Endpoint

  socket "/socket", HelloPhoenix.UserSocket
  socket "/admin-socket", HelloPhoenix.AdminSocket
end

web/channel/user_socket.ex

defmodule HelloPhoenix.UserSocket do
  use Phoenix,Socket

  channel "rooms:*", HelloPhoenix.RoomChannel
end

channel/3

1st arg “topic:subtopick”の形

"rooms:*", "rooms:kitchen", "rooms:lobby"

2nd arg Module like a HelloPhoenix.RoomChannel

3rd arg via PhoenixはlongPollingとWebSocketを抽象化したSocketで表現している 片方を指定したい場合にOptionalとしてviaを使用する

channel "rooms:*", HelloPhoenix.RoomChannel, via: [Phoenix.Transports.WebSocket]
channel "foods:*", HelloPhoenix.FoodChannel

Routingまとめ

  • HTTPメソッド(GET/POST/PUT/DELETE/etc…)から始まるのマッチするメソッドに展開
  • resourcesから始まるものを 8つのマッチするメソッドに展開 index, edit, new, show, create, update(PATCH), update(PUT), delte
  • resourcesはonly: except:で制限できる
  • ネストできる
  • スコープがパスになる
  • as: を使うと記述の重複を防げる
  • スコープありのルーティングにhelperオプションを使うと、到達できないパスが削除される

Plug

Webアプリケーション間のモジュールをつなぐ仕様で、他のwebサーバとのコネクションのアダプタとしての抽象レイヤーでもある。 Connectionの概念を統一するためのアイデア

他のHTTP middlewareとの違い(特徴) requestとresponseがmiddlewere stackから分離されている

Functionとして,Moduleとしての側面がある

Function Plug

%Plug.Conn{}構造体と、optionのオプションを受け取って%Plug.Conn{}を返す必要がある Optionを使って、Plug.Connを変換するためのもの

example

def put_headers(conn, key_values) do
  Enum.reduce key_values, conn, fn {k, v}, conn ->
    Plug.Conn.put_resp_header(conn, to_string(k), v)
  end
end

put_header/2, put_layout/2, action/2

このPlugを使った書き換えすごい

改良前

defmodule HelloPhoenix.MessageController do
  use HelloPhoenix.Web, :controller

  def show(conn, params) do
    case authenticate(conn) do
      {:ok, user} ->
        case find_message(params["id"]) do
          nil ->
              conn |> put_flash(:info, "メッセージないよ") |> redirect(to: "/")
          message ->
            case authorize_message(conn, params["id"]) do
              :ok ->
                render conn, :show, page: find_message(params["id"])
              error ->
                conn |> put_flash(:info, "You can't access that page") |> redirect(to: "/")
            end
        end
      :error ->
        conn |> put_flash(:info, "You must be logged in") |> redirect(to: "/")
    end
  end
end

改良後

defmodule HelloPhoenix.MessageController do
  use HelloPhoenix.Web, :controller

  plug :authenticate
  plug :find_message
  plug :authorize_message

  def show(conn, params) do
    render conn, :show, page: find_message(params["id"])
  end

  defp authenticate(conn), do: ...
  defp authenticate(conn, _) do
    case Authenticator.find_user(conn) do
      {:ok, user} ->
         assign(conn, :user, user)
      :error ->
         conn |> put_flash(:info, "ログインしてね") |> redirect(to: "/") |> halt
    end
  end

  defp find_message(id), do: ...
  defp find_message(conn, _) do
    case find_message(conn.params["id"]) do
      nil ->
        conn |> put_flash(:info, "That message wasn't found") |> redirect(to: "/") |> halt
      message ->
        assign(conn, :message, message)
    end
  end

  defp authorize_message(conn, _) do
    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do
      conn
    else
      conn |> put_flash(:info, "You can't access that page") |> redirect(to: "/") |> halt
    end
  end
end

Module Plugs

Connectionを変換するものとは別。

以下を定義する

  • init/1
arg: default
  • call/2 arg1: conn arg2: default
defmodule HelloPhoenix.Plugs.Locale do
  import Plug.Conn

  @locales ["en", "fr", "de"]

  def init(default), do: default

  def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do
    assign(conn, :locale, loc)
  end
  
  def call(conn, default), do: assign(conn, :locale, default)
end

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug HelloPhoenix.Plugs.Locale, "en"
  end
  ...
end