tutorial
Elixir
Phoenix

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

More than 1 year has passed since last update.


概要

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

Elixir / Phoenix の魅力については以下の記事を参考にしてください

[翻訳] Elixir - 次に来る大物Web言語

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

ソースコードは以下よりご確認ください。

phoenix-crud | GitHub


環境構築

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

Elixir / Phoenix の環境構築!HelloWorld!


プロジェクトの作成

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

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

$ mix phoenix.new blog_app

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

config/dev.exs にて以下のように記述されていると思います。

以下の通りに postgres のユーザを作成するか、既存のユーザの username, password を入力して設定します。


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


Post モデルの作成

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

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


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 します。

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 で行います。


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 に値を渡します。


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 | 公式ドキュメント


View の設定

次に View を設定します。

Rails でいうところの Helper のようなものです。

$ touch web/views/post_view.ex

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


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


Create アクションの実装

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


Validation の追加

model の changeset メソッドにおいて validation を追加します。

changeset は受け取った params を Struct に変換して、validation を行う役割があります。

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


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 アクションの追加

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


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

失敗した場合は次のようになります。

ちゃんと失敗した理由まで表示されていますね。

スクリーンショット 2017-08-29 7.13.49.png


index アクション

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

controller に以下を追加します。

Phoenix も Rails 同様に Ecto と呼ばれる OR マッパーがあります。

詳しくはこちらの記事を読むとよいでしょう。

ActiveRecord vs Ecto | Rails使いがElixirのEctoを勉強した時のまとめ


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

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


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 を追加します。


web/templates/post/show.html.eex


<h1>Show</h1>

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

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


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


<%= 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


Edit

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

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 に渡す


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 ページへのリンクを追加します。

<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


部分テンプレートの使用

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

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

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


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 において部分テンプレートを使用します。


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 のみが異なります。


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


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 ページにリンクを追加して終了です。

<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


最後に

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

Phoenix は日本語文献がまだまだ少ないので、

勉強するときは公式のドキュメントを読んで勉強することをおすすめします。

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