https://resolve.digital/blog/posts/creating-a-todo-application-using-the-phoenix-framework-and-ember-js/
を翻訳しました。
翻訳の誤りなどあればご指摘お待ちしております。
我々は常にクライアントに関連する技術に目を光らせています。本日は Phoenix と呼ばれるWebアプリケーション・フレームワークを見ていきます。 Phoenix は Elixir (あなたが Ruby を書いていたならば、いくつかの点でお馴染みと感じる、燃えるような早い関数型プログラミング言語)の上に構築されています。
このチュートリアルでは、サーバ上で Phoenix と Elixir を使用して、単純なクライアント-サーバ Web アプリケーションを作成していきます。データストアとして PostgreSQL を使用します。クライアントサイドでは、Ember Javascript フレームワークがユーザーインターフェースを提供し、サーバへ変更を伝えます。このコンポーネントの組み合わせは、PEEP(Postgres, Elixir, Ember, Phoenix)スタックとして知られるようになってきました。
構築する Todo アプリは次のようなものです:
##Phoenix アプリの作成
始める前に、Phoenix インストール・ガイドに従って、必要なすべての依存関係がインストールされていることを確認してください。
####1) スケルトン・アプリの作成
最初に、新しい Phoenix アプリを作成するために、以下を実行しましょう:
$ mix phoenix.new phoenix_todos_api --no-brunch
$ cd phoenix_todos_api
API のみのアプリはアセット管理を必要としませんので、Brunch.io を除いています。「Fetch and install dependencies?」(依存関係をフェッチしてインストール)のプロンプトには、 Y
と答えます。
####2) データベースの作成
次に、データベースを作成します。 config/dev.exs
と config/test.exs
を確認して PostgreSQL サーバの正しい資格情報を指定してください。デフォルトの資格情報は、次のとおりです:
# ...
username: "postgres",
password: "postgres",
# ...
代わりに、正しい資格情報を使用して、以下を実行します:
$ mix ecto.create
「rebar」をインストールするように指示される場合があります、継続してこれを受け入れます。アプリのコンパイルが完了した後、次のように表示されます:
$ The database for PhoenixTodosApi.Repo has been created.
####3) サーバの起動
次に、Phoenix の Web サーバを起動しましょう:
$ mix phoenix.server
これで、アプリに http://localhost:4000 でアクセス可能なはずで、Phoenix のデフォルトのランディング・ページが表示されます。
Ctrl + C
を2回押すことでサーバが終了します。
####4) Todoリソースの生成
私たちの API の単一のリソースは、Todo 項目です。各 Todo 項目は title
文字列と is_completed
ブール値を格納します。Phoenix は、JSON リソースのために必要なすべてのファイルを作成するためのジェネレータを提供し、それは以下で呼び出すことができます:
$ mix phoenix.gen.json Todo todos title:string is_completed:boolean
ジェネレータからの出力は、作成したファイルをリストします:
* creating web/controllers/todo_controller.ex
* creating web/views/todo_view.ex
* creating test/controllers/todo_controller_test.exs
* creating web/views/changeset_view.ex
* creating priv/repo/migrations/20150929044516_create_todo.exs
* creating web/models/todo.ex
* creating test/models/todo_test.exs
ご覧のとおり、コントローラ、モデル、それらに対応するテスト・ファイルが作成されます。また、データベースを更新するためのマイグレーションを取得し、最終的に、2つのビューも作成されます:1つは新しいコントローラ用の TodoView
であり、もう1つはモデルのバリデーション・エラーをレンダリングするためにすべての JSON コントローラによって使用される、より一般的な ChangesetView
です。
####5) ルートの追加
次に、入ってくる要求を新しく生成された TodoController
に接続する方法を Phoenix に伝えるために、ルートを設定する必要があります。
web/router.ex
を開きます。一番下に新しいスコープを追加します:
defmodule PhoenixTodosApi.Router do
# ...
scope "/", PhoenixTodosApi do
pipe_through :api
resources "/todos", TodoController, except: [:new, :edit]
end
end
####6) データベースのマイグレート
次に、Todos
を格納するテーブルを作成するために、データベースをマイグレートする必要があります。
$ mix ecto.migrate
…
17:55:14.586 [info] == Running PhoenixTodosApi.Repo.Migrations.CreateTodo.change/0 forward
17:55:14.586 [info] create table todos
17:55:14.617 [info] == Migrated in 0.2s
####7) データベースに初期データ導入
再びサーバを起動します:
$ mix phoenix.server
http://localhost:4000/todos で Todo リストにアクセスすることができますが、まだ何も持っていないことに気づくでしょう。
それでは、いくつかの初期データをデータベースに導入することによって、この問題を修正しましょう。priv/repo/seeds.exs
に次の行を追加します:
alias PhoenixTodosApi.Repo
alias PhoenixTodosApi.Todo
Repo.insert!(%Todo{title: "Create the Phoenix App", is_completed: true})
Repo.insert!(%Todo{title: "Prepare the Ember App", is_completed: false})
Repo.insert!(%Todo{title: "Ensure the Apps Work Together", is_completed: false})
データを導入するために以下を実行します:
$ mix run priv/repo/seeds.exs
これで、http://localhost:4000/todos から、いくつかの Todo が見られます。
{
"data": [{
"title": "Create the Phoenix App",
"is_completed": true,
"id": 1
}, {
"title": "Prepare the Ember App",
"is_completed": false,
"id": 2
}, {
"title": "Ensure the Apps Work Together",
"is_completed": false,
"id": 3
}]
}
以上により、基本的な API が稼働可能になりました。次はアプリケーションのクライアントサイドに取りかかります。
##Emberアプリの準備
クライアントサイドのために、TodoMVC.com の Ember CLI 実装を使用します。
以下の手順を進める前に、Ember CLI の Getting Started のインストールの部分に従うようにしてください。
####1) クローンと依存関係のインストール
Ember アプリのリポジトリのクローンを作成し、プロジェクトのディレクトリに移動します。
$ git clone https://github.com/ember-cli/ember-cli-todos.git
$ cd ember-cli-todos
npm 依存関係をインストールします:
$ npm install
そして、bower 依存関係もインストールします:
$ bower install
####2) Ember サーバの起動
必要なものがすべてインストールされたことを確認するために、アプリを起動します:
$ ember server
ブラウザで http://localhost:4200 を開きます。1この時点で Todo を追加することができますが、メモリ内のストレージを使用しており、ページをリフレッシュした場合 Todo は永続化されません。
####Ember を Phoenix へ接続
ここまでで、Phoenix で書かれたサーバサイドの API アプリと、Ember で書かれたクライアントサイドのアプリができました。しかし、2つのアプリは互いに会話していません。次に、2つのアプリを互いに接続し、Todo を永続化します。
####Ember アプリへの変更
#####1) Phoenix を使用するように Ember Data を設定
メモリ内のストレージを使用する代わりに、Ember Data が Phoenix API をバックエンドとして使用するように設定します。そのためには、 app/adapters/application.js
の内容を以下のように置き換えます:
import config from '../config/environment';
import FixtureAdapter from 'ember-data-fixture-adapter';
import DS from 'ember-data';
var adapter;
if (config.environment === 'test') {
adapter = FixtureAdapter.extend({});
} else {
adapter = DS.RESTAdapter.extend({
host: 'http://localhost:4000'
});
}
export default adapter;
アプリをテスト環境で起動する場合、元の FixtureAdapter
を使用し続けますが、他のすべての環境では RESTAdapter
を使用して http://localhost:4000 で Phoenix API を参照するように指示していることに注意してください。
#####2) シリアライザの設定
デフォルトでは、Ember Data は、JSON のキー名にキャメル・ケースを使用することを期待しますが、私たちの Phoenix API ではスネーク・ケースを使用しています。この問題を回避するために、また、Ember が使用するシリアライザをカスタマイズする必要があります。
まず serializers
ディレクトリを作成します:
$ mkdir -p app/serializers/
そしてその後、app/serializers/application.js
ファイルを作成し、以下のコードをコピーします:
import config from '../config/environment';
import DS from 'ember-data';
import Ember from 'ember';
var serializer;
if (config.environment === 'test') {
serializer = DS.JSONSerializer.extend({});
} else {
serializer = DS.RESTSerializer.extend({
keyForAttribute(attr) {
return Ember.String.decamelize(attr);
}
});
}
export default serializer;
ここでもテスト環境では異なるシリアライザを使用していますが、それ以外の場合、Ember Data モデルの属性をスネーク・ケースに変換するために、Ember のビルトイン decamelize
アダプターを使用した、カスタム RESTSerializer
を提供します。これはかなり単純なアプローチですが、私たちの目的のためには機能します。
#####3) CORSの設定
クライアントとサーバを異なるポートからホストしようとしているので、それらが通信できるように、セキュリティポリシーを適切に配置する必要があります。まず、Ember サーバが提供している CSP ヘッダ を更新します。開発環境のために、config/environment.js
の ENV.contentSecurityPolicy
の内容を変更します:
ENV.contentSecurityPolicy = {
// ...
'connect-src': "'self' http://localhost:*",
// ...
}
ここで行った変更は、connect-src
ルールに、ローカルホスト上の任意のポートへの接続を許可する http://localhost:*
を追加したことです。
また、Phoenix アプリに CORS ヘッダを追加する必要があり、そのためにはEmber アプリが実行されるポートを知る必要があります。それでは、プロジェクトの .ember-cli
設定ファイル(Ember アプリのルートディレクトリにあります)でポートを9000に設定しましょう:
{
// ...
"port": 9000
}
####Phoenix アプリへの変更
アプリに CORS ヘッダを追加して、Ember がアプリに対してリクエストすることをブラウザが許可するようにする必要があります。
#####1) cors_plug の設定
cors_plug と呼ばれるオープンソースの plug を使用することでこれを行うことができます。これを使用するためには、mix.exs
で依存関係に追加する必要があります:
def deps do
# ...
{:cors_plug, "~> 0.1.3"},
# ...
end
そして、以下でインストールします:
$ mix deps.get
一旦インストールされれば、アプリのエンドポイント(lib/phoenix_todos_api/endpoint.ex
にあります)に追加することができます:
defmodule PhoenixTodosApi.Endpoint do
# ...
plug CORSPlug, [origin: "http://localhost:9000"] # add this line
plug PhoenixTodosApi.Router
end
パイプラインで PhoenixTodosApi.Router
の上に置き、Ember アプリが動くように構成したポート使用して、origin として http://localhost:9000
を渡しています。
#####2) JSON ルート・キーの設定
Ember で RESTAdapter
は、JSONレスポンスをそれがデシリアライズされるべき Ember Data モデルにマッピングするためのルート・キーを必要とします。Todo モデルでは、Todo のコレクションは todos
、単一の Todo は todo
です。しかし、mix phoenix.gen.json Todo
を実行したとき、常に data
をルート・キーとして応答する TodoView
が設定されています。
では、Todo のコレクションのルート・キーとして todos
を、単一の Todo のルート・キーとして todo
を使用するように、JSON 出力を変更しましょう。まず、この変更を必要とするコントローラのテストを更新します。test/controllers/todo_controller_test.exs
を開き、["data"]
のすべてのインスタンスを ["todo"]
で置き換えます(4つの置き換えがあるはずです)。そして、 :index
アクションのためのテストを見つけ、期待されるルートを ["todo"]
から [todos]
に変更します:
test "lists all entries on index", %{conn: conn} do
conn = get conn, todo_path(conn, :index)
assert json_response(conn, 200)["todos"] == []
end
コントローラのテストを実行すると、4つの失敗が表示されます:
$ mix test test/controllers/todo_controller_test.exs
...
Finished in 0.4 seconds (0.3s on load, 0.1s on tests)
8 tests, 4 failures
テストをパスするには、index.json
と show.json
ビューで指定されているルート・キーを変更します。これらは web/views/todo_view.ex
にあります:
def render("index.json", %{todos: todos}) do
# '%{data:' replaced with '%{todos:':
%{todos: render_many(todos, PhoenixTodosApi.TodoView, "todo.json")}
end
def render("show.json", %{todo: todo}) do
# '%{data:' replaced with '%{todo:':
%{todo: render_one(todo, PhoenixTodosApi.TodoView, "todo.json")}
end
再度テストを実行し、すべてパスすることを確認しましょう:
$ mix test test/controllers/todo_controller_test.exs
...
Finished in 0.4 seconds (0.3s on load, 0.1s on tests)
8 tests, 0 failures
これで Phoenix アプリは Ember アプリからのリクエストを受け容れる準備ができました。
一緒にアプリをテスト
各々を起動することでアプリが一緒に動作することを確認できます。Phoenix を起動します:
$ mix phoenix.server
つづけて Ember を起動します:
$ ember server
そして、ブラウザで http://localhost:9000 を開きます。
Ember アプリに、先ほど Phoenix アプリで追加したデータがあるはずです。Ember アプリで加えた変更は Phoenix アプリに永続化されます。mix phoenix.server
からのログ出力を見ることでこの動作を確認できます。ページをリフレッシュしても、変更は永続化されています。
-
私(翻訳者)の環境ではここで Ember のビルドエラーが発生しました。 bower で正しくインストールされなかった moment を手動でインストールすることで、エラーが解消しました。
$ bower install moment
↩