Elixir / Phoenix で CRUD アプリを作成！


# 概要

Phoenix という次世代の WEB フレームワークを使って、シンプルな CRUD を作成します。

Elixir / Phoenix の魅力については以下の記事を参考にしてください
[[翻訳] Elixir - 次に来る大物Web言語](http://qiita.com/HirofumiTamori/items/0dfdbada30c7d8f183fd)

Ruby / Rails よりも**10倍くらい**早くて非常に可読性の高い言語なのでおすすめです。

ソースコードは以下よりご確認ください。
[phoenix-crud | GitHub](https://github.com/cohki0305/phoenix-crud)

## 環境構築

環境構築をしていない方は以下の記事を参考にしてください。

[Elixir / Phoenix の環境構築！HelloWorld！](http://cohki0305.com/programing/elixir-phoenix-env-helloworld)


# プロジェクトの作成

ブログの記事を投稿したり、削除したりするようなアプリを作っていきます。


以下のコマンドを実行することで、プロジェクトが作成されます。

```
$ mix phoenix.new blog_app
```

特に指定しなければ、DB は `postgres` になります。

`config/dev.exs` にて以下のように記述されていると思います。
以下の通りに postgres のユーザを作成するか、既存のユーザの username, password を入力して設定します。


```elixir:config/dev.exs

# Configure your database
config :blog_app, BlogApp.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "blog_app_dev",
  hostname: "localhost",
  pool_size: 10

```

設定が正しければ、データベースを作成できます。

```
$ mix ecto.create
#=> The database for BlogApp.Repo has been created
```

次に確認のために、サーバーを起動してみましょう。

```
$ mix phoenix.server
```

http://localhost:4000/ を訪れると、以下のようなページが表示されています。

![スクリーンショット 2017-08-28 22.52.55.png](https://qiita-image-store.s3.amazonaws.com/0/100997/334aa233-a3cc-500b-1f35-ad195cc8f350.png "スクリーンショット 2017-08-28 22.52.55.png")


# Post モデルの作成

```
$ mix phoenix.gen.model Post posts title:string body:text
```

実行すると以下の migration ファイルと model が作成されます。


```elixir:priv/repo/migrations/2017XXXXXXXXXX_create_post.exs

defmodule BlogApp.Repo.Migrations.CreatePost do
  use Ecto.Migration

  def change do
    create table(:posts) do
      add :title, :string
      add :body, :text

      timestamps()
    end
  end
end

```

`null: false` を追加して migrate します。

```diff


  def change do
    create table(:posts) do
-     add :title, :string
+     add :title, :string, null: false
-     add :body, :text
+     add :body, :text, null: false

      timestamps()
    end
  end

```

```
$ mix ecto.migrate
```

# New アクションの実装

## Router の設定

では、Post を作成するための機能を作っていきます。

ルーティングを設定します。特定のルーティングと Controller を合致させます。
ルーティングは、`web/router.ex` で行います。


```diff

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

    get "/", PageController, :index

+   resources "/posts", PostController
  end

```

以下のように表示されていれば問題なしです。

```
$ mix phoenix.routes

mix phoenix.routes is deprecated. Use phx.routes instead.
Compiling 6 files (.ex)
page_path  GET     /                BlogApp.PageController :index
post_path  GET     /posts           BlogApp.PostController :index
post_path  GET     /posts/:id/edit  BlogApp.PostController :edit
post_path  GET     /posts/new       BlogApp.PostController :new
post_path  GET     /posts/:id       BlogApp.PostController :show
post_path  POST    /posts           BlogApp.PostController :create
post_path  PATCH   /posts/:id       BlogApp.PostController :update
           PUT     /posts/:id       BlogApp.PostController :update
post_path  DELETE  /posts/:id       BlogApp.PostController :delete

```

## Controller の設定

次に Controller を作成します。役割は Rails のコントローラーと同じです。
ユーザからのリクエストを処理してページを表示したりデータベースを更新したりします。

```
$ touch web/controllers/post_controoler.ex
```


以下のようにして changeset を定義して、template に値を渡します。

```elixir:web/controllers/post_controller.ex

defmodule BlogApp.PostController do
  use BlogApp.Web, :controller
  alias BlogApp.Post

  def new(conn, _params) do
    changeset = Post.changeset(%Post{})
    render conn, "new.html", changeset: changeset
  end
end

```

Changeset について詳しく知りたい人は以下の記事を読むと良いでしょう。
[Ecto.Changeset | 公式ドキュメント](https://hexdocs.pm/ecto/Ecto.Changeset.html)

## View の設定

次に View を設定します。
Rails でいうところの Helper のようなものです。

```
$ touch web/views/post_view.ex
```

特に必要なメソッドがないので空の状態で ok です。

```elixir:web/views/post_view.ex

defmodule BlogApp.PostView do
  use BlogApp.Web, :view
end

```

## Template の設定

```web/templates/post/new.html.eex

<h1>New</h1>

<%= form_for @changeset, post_path(@conn, :create), fn f -> %>

<div class="form-group">
  <label>タイトル</label>
  <%= text_input f, :title, class: "form-control" %>
  <%= error_tag f, :title%>
</div>

<div class="form-group">
  <label>本文</label>
  <%= textarea f, :body, class: "form-control" %>
  <%= error_tag f, :body %>
</div>

<%= submit "Submit", class: "btn btn-primary" %>

<% end %>

```

`/posts/new` を訪れると以下のように表示されていると思います。

![スクリーンショット 2017-08-28 23.21.27.png](https://qiita-image-store.s3.amazonaws.com/0/100997/377d7b98-6075-1dfb-f61a-02a28877acd1.png "スクリーンショット 2017-08-28 23.21.27.png")

# Create アクションの実装

次に new で作った画面から記事を作成できるようにしましょう。

## Validation の追加

model の changeset メソッドにおいて validation を追加します。
changeset は受け取った params を Struct に変換して、validation を行う役割があります。

ここでは、title の長さを最大30までに制限します。

```diff

defmodule BlogApp.Post do
  use BlogApp.Web, :model

  schema "posts" do
    field :title, :string
    field :body, :string

    timestamps()
  end


  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:title, :body])
    |> validate_required([:title, :body])
+   |> validate_length(:title, max: 30)
  end
end


```

## Create アクションの追加

コントローラーに以下を追加します。

```elixir:web/controllers/post_controller.ex


  def create(conn, %{"post" => post_params}) do
    changeset = Post.changeset(%Post{}, post_params)
    case Repo.insert(changeset) do
      {:ok, _} ->
        conn
        |> put_flash(:info, "投稿しました")
        |> redirect(to: post_path(conn, :new))
      {:error, changeset} ->
        conn
        |> put_flash(:error, "投稿に失敗しました")
        |> render("new.html", changeset: changeset)
    end
  end


```

create アクションでは、第二引数でパターンマッチングを行い post_params を取得します。
そして、changeset メソッドによって params より構造体を作りレコードに保存しています。

投稿に成功すると以下のように表示されるはずです。

![スクリーンショット 2017-08-29 7.10.16.png](https://qiita-image-store.s3.amazonaws.com/0/100997/1bec2c7d-07cd-95f3-38ad-d37ebf7b6c23.png "スクリーンショット 2017-08-29 7.10.16.png")


失敗した場合は次のようになります。
ちゃんと失敗した理由まで表示されていますね。

![スクリーンショット 2017-08-29 7.13.49.png](https://qiita-image-store.s3.amazonaws.com/0/100997/f03c3ade-7b56-d035-0508-c797ac6c2468.png "スクリーンショット 2017-08-29 7.13.49.png")

# index アクション

次は投稿の一覧を表示する index を作っていきます。

controller に以下を追加します。
Phoenix も Rails 同様に Ecto と呼ばれる OR　マッパーがあります。
詳しくはこちらの記事を読むとよいでしょう。

[ActiveRecord vs Ecto | Rails使いがElixirのEctoを勉強した時のまとめ](http://qiita.com/techno-tanoC/items/c703a5a90e4133fbea82#activerecord-vs-ecto)


```elixir:web/controllers/post_controller.ex

  def index(conn, _params) do
    posts = Post |> Repo.all
    render conn, "index.html", posts: posts
  end

```

次にそれらを表示するための Template を作成します。


```web/templates/post/index.html.eex

<h1>All Posts</h1>

<%= for post <- @posts do %>
<div class="row">
  <h2><%= post.title %></h2>
</div>
<% end %>


```

![スクリーンショット 2017-08-29 7.28.05.png](https://qiita-image-store.s3.amazonaws.com/0/100997/24c303ab-cae4-69d8-f085-cf8b7de85433.png "スクリーンショット 2017-08-29 7.28.05.png")

最後に、Post 作成後のリダイレクトを index に変更します

```diff

  def create(conn, %{"post" => post_params}) do
    changeset = Post.changeset(%Post{}, post_params)
    case Repo.insert(changeset) do
      {:ok, _} ->
        conn
        |> put_flash(:info, "投稿しました")
-       |> redirect(to: post_path(conn, :new))        
+       |> redirect(to: post_path(conn, :index))
      {:error, changeset} ->
        conn
        |> put_flash(:error, "投稿に失敗しました")
        |> render("new.html", changeset: changeset)
    end


```

# Show 



controller に以下を追加する。

```

  def show(conn, %{"id" => id}) do
    post = Post |> Repo.get(id)
    render conn, "show.html", post: post
  end


```


パラメーターの id をパターンマッチングで取得します。
そして、Repo.get にて該当する　post を取得して、Template 側に渡しています。


template を追加します。


```html:web/templates/post/show.html.eex

<h1>Show</h1>

<div class="row">
  <%= @post.title %>
</div>

<div class="row">
  <%= @post.body %>
</div>


```



最後に index.html.eex にてリンクを追加する。


```diff

<%= for post <- @posts do %>
<div class="row">
  <h2><%= post.title %></h2>
+ <%= link 'detail', to: post_path(@conn, :show, post) %>
</div>
<% end %>


```


リンクをクリックすると以下のような画面が表示されているはずです。

![スクリーンショット 2017-08-29 7.36.50.png](https://qiita-image-store.s3.amazonaws.com/0/100997/82f431f8-b9df-5ab9-27c2-52c98e02a2c7.png "スクリーンショット 2017-08-29 7.36.50.png")

# Edit 

Controller にメソッドを追加します。


```elixir


  def edit(conn, %{"id" => id}) do
    post = Post |> Repo.get(id)
    changeset = Post.changeset(post)
    render(conn, "edit.html", post: post, changeset: changeset)
  end

```

以下のことをやっています

- post を id によって取得
- その post によって changeset を作成
- post と　changeset を template に渡す


```html:web/templates/post/show.html.eex


<h1>Edit</h1>

<%= form_for @changeset, post_path(@conn, :update, @post), fn f -> %>

<div class="form-group">
  <label>タイトル</label>
  <%= text_input f, :title, class: "form-control" %>
  <%= error_tag f, :title%>
</div>

<div class="form-group">
  <label>本文</label>
  <%= textarea f, :body, class: "form-control" %>
  <%= error_tag f, :body %>
</div>

<%= submit "Submit", class: "btn btn-primary" %>

<% end %>


```

template は path 以外は、new と一緒です。


最後に、index テンプレートに edit ページへのリンクを追加します。


```diff
<h1>All Posts</h1>

<%= for post <- @posts do %>
<div class="row">
  <h2><%= post.title %></h2>
  <%= link 'detail', to: post_path(@conn, :show, post) %>
+ <%= link 'edit', to: post_path(@conn, :edit, post) %>
</div>
<% end %>

```

index ページよりリンクを押して確認すると、画像のように表示されているはずです。

![スクリーンショット 2017-08-29 8.16.46.png](https://qiita-image-store.s3.amazonaws.com/0/100997/2f522f7f-6af2-82b7-867a-29c74dc0d6f5.png "スクリーンショット 2017-08-29 8.16.46.png")


## 部分テンプレートの使用

このままでも良いのですが、冗長的なので部分テンプレートを使います。

```
$ touch  web/templates/post/form.html.eex
```


Rails と使い方は同じです。部分テンプレート内の変数は元のテンプレートより渡す必要があります。

```html:web/templates/post/form.html.eex


<%= form_for @changeset, @path, fn f -> %>

<div class="form-group">
  <label>タイトル</label>
  <%= text_input f, :title, class: "form-control" %>
  <%= error_tag f, :title%>
</div>

<div class="form-group">
  <label>本文</label>
  <%= textarea f, :body, class: "form-control" %>
  <%= error_tag f, :body %>
</div>

<%= submit "Submit", class: "btn btn-primary" %>

<% end %>

```


edit template において部分テンプレートを使用します。

```diff:web/templates/post/edit.html.eex

<h1>Edit</h1>

+ <%= render "form.html", changeset: @changeset, path: post_path(@conn, :create) %>


- <%= form_for @changeset, post_path(@conn, :update, @post), fn f -> %>

- <div class="form-group">
-   <label>タイトル</label>
-   <%= text_input f, :title, class: "form-control" %>
-   <%= error_tag f, :title%>
- </div>

- <div class="form-group">
-   <label>本文</label>
-   <%= textarea f, :body, class: "form-control" %>
-   <%= error_tag f, :body %>
- </div>

- <%= submit "Submit", class: "btn btn-primary" %>

- <% end %>


```

new.html.eex においても同様に修正してください。
edit とは path のみが異なります。

```html:new.html.eex

<h1>New</h1>

<%= render "form.html", changeset: @changeset, path: post_path(@conn, :create) %>

```

# Update


controller を追加します。


```

  def update(conn, %{"id" => id, "post" => post_params}) do
    post = Post |> Repo.get(id)
    changeset = Post.changeset(post, post_params)

    case Repo.update(changeset) do
      {:ok, _} ->
        conn
        |> put_flash(:info, "投稿を更新しました")
        |> redirect(to: post_path(conn, :index))
      {:error, changeset} ->
        conn
        |> put_flash(:error, "更新に失敗しました")
        |> render("edit.html", changeset: changeset, post: post)
    end
  end


```

update の場合は id を取得する必要がありますが、それ以外は create とほとんど同じです。
id から取得したレコードによって changeset を作成して、update します。


edit ページより更新すると以下のように表示されているはずです。

![スクリーンショット 2017-08-29 8.31.15.png](https://qiita-image-store.s3.amazonaws.com/0/100997/19b3995f-b13d-57f2-c8f5-90d30ad8bb7d.png "スクリーンショット 2017-08-29 8.31.15.png")


# Delete

最後に Delete です。

controller はシンプルです。レコードを取得して削除するだけです。

```

  def delete(conn, %{"id" => id}) do
    post = Post |> Repo.get(id)
    Repo.delete(post)

    conn
    |> put_flash(:info, "削除に成功しました。")
    |> redirect(to: post_path(conn, :index))
  end

```

index ページにリンクを追加して終了です。

```diff
<h1>All Posts</h1>

<%= for post <- @posts do %>
<div class="row">
  <h2><%= post.title %></h2>
  <%= link 'detail', to: post_path(@conn, :show, post) %>
  <%= link 'edit', to: post_path(@conn, :edit, post) %>
+ <%= link 'delete', to: post_path(@conn, :delete, post), method: :delete %>
</div>
<% end %>

```


リンクを押すと削除されているのがわかります。


![スクリーンショット 2017-08-29 8.35.39.png](https://qiita-image-store.s3.amazonaws.com/0/100997/e44daea7-c216-c5f3-94a1-4ec470425b92.png "スクリーンショット 2017-08-29 8.35.39.png")

# 最後に

以上で Phoenix で CRUD アプリを作成を終わります！

Phoenix は日本語文献がまだまだ少ないので、
勉強するときは公式のドキュメントを読んで勉強することをおすすめします。

不明点やご指摘あればコメントいただければ幸いです。