(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の13日目、自然言語処理 Advent Calendar 2017 - Qiita の16日目です)
昨日は@zacky1972さんの「ZEAM開発ログv0.1.6 Elixir から Rustler で GPU を駆動しよう〜ElixirでAI/MLを高速化」でした!
本題
PhoenixはElixirで書かれたWebフレームワークです。今回はこのPhoenixをAPIサーバーとして利用し、さらにそのAPIから取得したレコードをVue.jsを利用して画面表示するところまでを解説したいと思います。
環境
Phoenix環境はDockerで用意するのがオススメです。
$ docker -v
Docker version 18.03.1-ce, build 9ee9f40
※ Phoenixのタスク名について
v1.3から、Phoenixのタスク名が mix phoenix.*
から mix phx.*
に変更されています。
最新のmaster→phoenix/lib/mix/tasks at master · phoenixframework/phoenix · GitHub
v1.2のブランチ→phoenix/lib/mix/tasks at v1.2 · phoenixframework/phoenix · GitHub
v1.3以降を利用する際はmix phoenix.*
ではなくmix phx.*
を利用するようにしましょう。
STEP1: PhoenixでJSON APIを作成する
json APIの構築はElixir入門「第3回:Phoenix 1.3で高速webアプリ & REST APIアプリをサクッと書いてみる」のスライドでばっちりなので、こちらのスライドに則ればOKです。
今回はphoenixが入っているこちらのdockerイメージを利用してみます。
# 現在開いているディレクトリをマウントしてコンテナを起動
# Phoenix serverで利用するポート4000をbindした状態で起動
$ docker run -it -v `pwd`:/code -p 4000:4000 marcelocg/phoenix
以下、コンテナ内での作業です。
先にpostgresqlを入れておきましょう。
# postgresqlのインストール
$ apt-get update
$ apt-get install postgresql
# 起動
$ service postgresql start
# 初期パスワードをPhoenixの初期設定に合わせて"postgres"に変更しておく
$ sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres'"
ALTER ROLE
# 再起動
$ service postgresql restart
続いて、本題のPhoenixプロジェクトを作成します。PhoenixフレームワークはデフォルトでBrunchというビルドツールを採用しており、これを利用しない場合には--no-brunchをつけてプロジェクトを作成します。
# プロジェクトを作成
# --no-brunch: Brunch.ioを利用しない
$ mix phx.new phoenix_vue_example --no-brunch
# インストールが成功したら、フォルダへ移動
$ cd phoenix_vue_example
# DBの作成
$ mix ecto.create
ectoはElixirで書かれたデータベースのラッパーとクエリ操作を提供するモジュールです。DB操作の記述やバリデーションの記述が楽になる便利なもの
ぐらいにとらえておきましょう。ectoはPhoenixに依存したものではないので、Phoenix環境下以外でも利用することができます。Elixirで値のバリデーションをEctoで行うの記事はその一例です。
ecto.createで
** (Mix) The database for PhoenixVueExample.Repo couldn't be created: ERROR 22023 (invalid_parameter_value): new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
のように怒られたら、config/dev.exs
にtemplateの項目を追加してみてください。
# Configure your database
config :phoenix_vue_example, PhoenixVueExample.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "postgres",
database: "phoenix_vue_example_dev",
hostname: "localhost",
template: "template0", # 追加
pool_size: 10
データベースが作成できました!
$ sudo -u postgres psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-------------------------+----------+-----------+---------+-------+-----------------------
phoenix_vue_example_dev | postgres | UTF8 | C | C |
postgres | postgres | SQL_ASCII | C | C |
template0 | postgres | SQL_ASCII | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | SQL_ASCII | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
続けてAPIで提供するデータのモデルを作成します。今回は「タイトル」と「本文」をデータとして持つArticleモデルを用意しましょう。
# モデルを生成
# mix phx.gen.json <コンテキスト名> <スキーマのモジュール名(A)> <(A)の複数形> <フィールドと型>
$ mix phx.gen.json Blog Article articles title:string body:text
ファイルが生成されたら、ルーティングの追加をし、マイグレーションを実行しましょう。
# lib/phoenix_vue_example_web/router.exを編集
defmodule PhoenixVueExampleWeb.Router do
use PhoenixVueExampleWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
# plug :protect_from_forgery ← コメントアウト
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", PhoenixVueExampleWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/articles", ArticleController, except: [:new, :edit] ← 追加
end
# Other scopes may use custom stacks.
# scope "/api", PhoenixVueExampleWeb do
# pipe_through :api
# end
end
# migrateを実行
$ mix ecto.migrate
# テーブルが生成されたのを確認する
$ sudo -u postgres psql -d phoenix_vue_example_dev -c "\d articles"
phoenix_vue_example_dev=# \d articles
Table "public.articles"
Column | Type | Modifiers
-------------+-----------------------------+-------------------------------------------------------
id | bigint | not null default nextval('articles_id_seq'::regclass)
title | character varying(255) |
body | text |
inserted_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
Indexes:
"articles_pkey" PRIMARY KEY, btree (id)
ココまでくれば、あとはサーバーを起動して所定のURLにアクセスするだけです!
# ルーティングを確認
$ mix phx.routes
page_path GET / PhoenixVueExampleWeb.PageController :index
article_path GET /articles PhoenixVueExampleWeb.ArticleController :index
article_path GET /articles/:id PhoenixVueExampleWeb.ArticleController :show
article_path POST /articles PhoenixVueExampleWeb.ArticleController :create
article_path PATCH /articles/:id PhoenixVueExampleWeb.ArticleController :update
PUT /articles/:id PhoenixVueExampleWeb.ArticleController :update
article_path DELETE /articles/:id PhoenixVueExampleWeb.ArticleController :delete
# サーバーを起動
$ iex -S mix phx.server
APIの準備ができたので、試しにlocalhost:4000/articles
に対してPostmanからgetとpostを試してみましょう。
これでAPIの準備は終わりです!
STEP2: Vue.jsを導入しAPIのレスポンスを画面に表示する
準備
localhost:4000 にアクセスしたときに表示される↓↓の画面をいじりましょう。
事前準備としてvue.jsと、HTTPクライアントのaxiosをCDNで持ってきちゃいましょう。
app.html.eexのheadタグ内に以下を追記します。
<!-- axios, vueをCDNから取得する -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
これでVue.jsが利用できる状態になりました。検証中にCDNでサクッと持ってこれるのはいいですね。
Vue.jsで何を書くか
まず方針を整理します。ここからVue.jsで書いていく内容は以下の2つです。
(1) DOMがどんなデータを持つか(Model)
(2) DOMでどうデータを表示するか(View)
(1)は.jsにVueインスタンスを作成して定義します。(2)はhtmlにデータが入ってくる箇所を{{ }}
で記述して定義します。データ(Model)と見た目(View)をきれいに分離して記述するのがVue.jsのポイントです。
(1) VueインスタンスでDOMが持つデータを定義する
まず(1)を書いていきましょう。body直前で呼ばれているapp.jsでvueインスタンスを作成します。
var app = new Vue({
el: '[role="main"]',
data: {
articles: []
},
created: function() {
axios.get('/articles').then(function(response){
app.articles = response.data.data;
});
}
});
app.html.eex内のmainタグをvueインスタンスのROOT要素として登録しています。Vueインスタンスが作成された後に実行されるフック関数created()
を利用して、インスタンスが作成されたタイミングでAPIを叩き、自身のdataにapiの返り値を格納しています。
似たフック関数にmounted()
があり、こちらはDOMの描画が完了したタイミングで呼ばれます。APIを叩いてVueインスタンスにデータを格納する処理はDOMの構築を待たずに実行してOKなので、created()
で書きましょう。
参考: Vuejs APIアクセスはcreatedとmountedのどちらで行う?
(2) Vueインスタンスが持つデータを表示する
最後に(2)です。Vueインスタンスが持つデータをどう表示するかをhtmlに書いてあげれば完成です。
<div v-for="article in articles">
<h2>{{ article.title }}</h2>
<pre>{{ article.body }}</pre>
<hr>
</div>
v-for
は配列データをループさせるときに書くvueの記法です。ここでは割愛しますが、あるデータが真のときだけDOMを表示するv-if
など、他にもたくさんの記法があります。
localhost:4000にアクセスすると、v-forでループしてDBのすべてのレコードが表示されているはずです!
まとめ
以下の流れで、Phoenix+Vueのシンプルなアプリケーションを作成しました。
① mix phx.gen.json
でAPIを構築
② ブラウザからaxios
を利用してAPIを叩く
③ Vueインスタンスのdataに格納し、画面表示
Phoenixで「いかにデータを作成するか」、Vueで「いかにデータを表示するか」という関心の分離が出来るので、この構成は良いですね。さらに複雑な構成でどう書いていくか、今後学習して発信していけたらなと思います!
次回は、@kobatako さんの「定期的にSlack botで記事を通知する with Qiita API」です!お楽しみに!
満員御礼!Elixir MeetUpを6月末に開催します
※応募多数により、増枠しました
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します。特別ゲストも迎え、非常に濃い2時間になること間違いなしです!ご興味のある方はぜひご参加ください!