Programming Phoenix勉強その3

Programming Phoenix勉強その3

その3です。 その2の続きです。 今回からChpater3です. このChapterではまず rumbl と呼ばれるアプリを作ります. ビデオにたいしてリアルタイムでコメントを付けられるアプリになる予定らしい.

準備

Chapter1と同様に以下のコマンドでPhoenixの新しいプロジェクトを作成します.(詳細は割愛)

$ mix phoenix.new rumbl
* creating rumbl/ config/ config.exs
  ...

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running npm

$ cd rumbl
rumbl $ mix ecto.create
==> connection
Compiling 1 file (.ex)
Generated connection app
...

Modelの追加

実際はコマンドで自動生成されるものを手製で実装します. web/models/user.ex を以下の内容で実装します.

defmodule Rumbl.User do
  defstruct [:id, :name, :username, :password]
endD

これは id, name, username, password 構造体として持つUserモジュールです.

Repositoryの変更

現段階では,RepositoryはRDBからではなく独自にハードコーディングします. このようにすることで,データの概念とデータベースの概念が分離されていることがわかります. ( Repo と Model として) まず, lib/rumbl/repo.ex を以下のように変更します.

defmodule Rumbl.Repo do
  @moduledoc """
  In memory repository.
  """

  def all(Rumbl.User) do
    [%Rumbl.User{id: "1", name: "Jose", username: "josevalim", password: "elixir"},
    %Rumbl.User{id: "2", name: "Bruce", username: "redropids", password: "7longs"},
    %Rumbl.User{id: "3", name: "Chris", username: "chrismccord", password: "phx"}]
  end

  def all(_module), do: []

  def get(module, id) do
    Enum.find all(module), fn map -> map.id == id end
  end

  def get_by(module, params) do
    Enum.find all(module), fn map ->
      Enum.all?(params, fn {key, val} -> Map.get(map, key) == val end)
    end
  end
end

Ecto を使わないようにしたので, lib/rumbl.ex を編集して上記の repo.ex をプロセス管理対象から外します.

# Start the Ecto repository
# supervisor(Rumbl.Repo, []), # これをコメントアウト

上手く行っているか試すにはコンソールでプロジェクトフォルダに移動して iex -S mix コマンドで iex を起動します.

rumbl $ iex -S mix
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Compiling 7 files (.ex)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> alias Rumbl.User
Rumbl.User
iex(2)> alias Rumbl.Repo
Rumbl.Repo
iex(3)> Repo.all User
[%Rumbl.User{id: "1", name: "Jose", password: "elixir", username: "josevalim"},
 %Rumbl.User{id: "2", name: "Bruce", password: "7longs", username: "redropids"},
 %Rumbl.User{id: "3", name: "Chris", password: "phx", username: "chrismccord"}]
iex(4)> Repo.all Rumbl.Other
[]
iex(5)> Repo.get User, "1"
%Rumbl.User{id: "1", name: "Jose", password: "elixir", username: "josevalim"}
iex(6)> Repo.get_by User, name: "Brunce"
nil
iex(7)> Repo.get_by User, name: "Bruce"
%Rumbl.User{id: "2", name: "Bruce", password: "7longs", username: "redropids"}
iex(8)>

Controllerの実装

上記で作成した Repository を扱う Controller を実装します. まず,専用のルーティング設定を web/router.ex に設定します.

scope "/", Rumbl do
  pipe_through :browser # Use the default browser stacks.

  get "/users", UserController, :index     # 追加
  get "/users/:id", UserController, :show  # 追加
  get "/", PageController, :index
end

みて分かる通り UserControler の index アクションと show アクションに対応するルーティング設定を行います. get マクロはHTTPメソッドのGETで呼び出されることを想定されています. 次に,設定したルーティングに対応する Controller を実装します.

defmodule Rumbl.UserController do
  use Rumbl.Web, :controller

  def index(conn, _params) do
    users = Repo.all(Rumbl.User)
    render conn, "index.html", users: users
  end
end

hello アプリで作成したものと対して変わらないと思います. 違いは Repo.all/1 関数でユーザ一覧を取ってきてることくらいだと思います. この時点でもまだ View がないとエラーになるので, View の実装をします.

Viewの実装

web/views/user_view.ex を以下の内容で実装します.

defmodule Rumbl.UserView do
  use Rumbl.Web, :view
  alias Rumbl.User

  def first_name(%User{name: name}) do
    name
    |> String.split(" ")
    |> Enum.at(0)
  end
end

単純に名前を名字と名前で分解しているだけの関数です. View モジュール名は Controller 名から自動で推測されます. ( UserController なら UserView といった具合)

Templateの実装

web/templates/user/index.html.eex を以下の内容で実装します.

<h1>Listing Users</h1>

<table class="table">
  <%= for user <- @users do %>
    <tr>
      <td><b><%= first_name(user) %></b> (<%= user.id %>)</td>
      <td>><%= link "View", to: user_path(@conn, :show, user.id) %></td>
    </tr>
  <% end %>
</table>

Template は View 名から自動で推測されます. ( UserView なら user フォルダといった具合) ここまでくれば http://localhost:4000/users でユーザ一覧が表示されます. EEx のハイライトないので  ERB でハイライトしてます.

Viewのuse Rumbl.Web, :viewについて

view に記述した use Rumbl.Web, :view の実体は web/web.ex に存在します.

defmodule Rumbl.Web do
  …
  def view do
    quote do
      use Phoenix.View, root: "web/templates"

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import Rumbl.Router.Helpers
      import Rumbl.ErrorHelpers
      import Rumbl.Gettext
    end
  end
  …
end

Phoenix.HTML をHTML周りのことを色々やってくれているようです. また,これによって生成されるHTMLは安全で,XSS対策なども行ってくれているようです. ここには勝手に関数を書くのはNG.書きたいなら真似して import を使うこと.

まとめ

今回は前回より具体的に各機能を実装しました. 個人的には今までよくわからなかった Repository と Model の関係がちょっとわかったのが収穫でした. 他のフレームワーク触ってると, View と Template が分離しているのが一瞬戸惑いそうだとおもいました.