Phoenix入門：10分で作るブログ

## 私

- Rails を 10 年以上使い続けています。
- Elixir 勉強中です。プロセスかわいいよ、プロセス。
- MacOS sierra 10.12.6 です。

## インストール

ここに沿う感じで。


https://hexdocs.pm/phoenix/installation.html#content

```
$ brew upgrade elixir
$ elixir --version
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.5.1
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
$ mix phoenix.new -v
Phoenix v1.3.0
```


## プロジェクト作成

まずはプロジェクトを作ります。WAFのHello Worldといえば、10分で作るブログ作成ですよね。


```
$ mix phx.new blog                                                                                                          
* creating blog/config/config.exs
* creating blog/config/dev.exs
* creating blog/config/prod.exs
* creating blog/config/prod.secret.exs
* creating blog/config/test.exs
* creating blog/lib/blog/application.ex
* creating blog/lib/blog.ex
* creating blog/lib/blog_web/channels/user_socket.ex
* creating blog/lib/blog_web/views/error_helpers.ex
* creating blog/lib/blog_web/views/error_view.ex
* creating blog/lib/blog_web/endpoint.ex
* creating blog/lib/blog_web/router.ex
* creating blog/lib/blog_web.ex
* creating blog/mix.exs
* creating blog/README.md
* creating blog/test/support/channel_case.ex
* creating blog/test/support/conn_case.ex
* creating blog/test/test_helper.exs
* creating blog/test/blog_web/views/error_view_test.exs
* creating blog/lib/blog_web/gettext.ex
* creating blog/priv/gettext/en/LC_MESSAGES/errors.po
* creating blog/priv/gettext/errors.pot
* creating blog/lib/blog/repo.ex
* creating blog/priv/repo/seeds.exs
* creating blog/test/support/data_case.ex
* creating blog/lib/blog_web/controllers/page_controller.ex
* creating blog/lib/blog_web/templates/layout/app.html.eex
* creating blog/lib/blog_web/templates/page/index.html.eex
* creating blog/lib/blog_web/views/layout_view.ex
* creating blog/lib/blog_web/views/page_view.ex
* creating blog/test/blog_web/controllers/page_controller_test.exs
* creating blog/test/blog_web/views/layout_view_test.exs
* creating blog/test/blog_web/views/page_view_test.exs
* creating blog/.gitignore
* creating blog/assets/brunch-config.js
* creating blog/.gitignore                                                                                                                          
* creating blog/assets/brunch-config.js
* creating blog/assets/css/app.css
* creating blog/assets/css/phoenix.css
* creating blog/assets/js/app.js
* creating blog/assets/js/socket.js
* creating blog/assets/package.json
* creating blog/assets/static/robots.txt
* creating blog/assets/static/images/phoenix.png
* creating blog/assets/static/favicon.ico

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile
* running cd assets && npm install && node node_modules/brunch/bin/brunch build

We are all set! Go into your application by running:

    $ cd blog

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

```

ふむふむ。問題ないですね。一応、gitに入れておきましょうか。


```
$ cd blog
$ git init
$ git add .
$ git commit -m "initial"
```

## プロジェクト周辺探訪

ちょっと寄り道。

`mix deps.tree` ってすると、依存ライブラリの一覧などが確認できるらしいです。


```
$ mix deps.tree
blog
├── gettext ~> 0.11 (Hex package)
├── phoenix_pubsub ~> 1.0 (Hex package)
├── cowboy ~> 1.0 (Hex package)
│   ├── cowlib ~> 1.0.2 (Hex package)
│   └── ranch ~> 1.3.2 (Hex package)
├── phoenix_html ~> 2.10 (Hex package)
│   └── plug ~> 1.0 (Hex package)
│       ├── cowboy ~> 1.0.1 or ~> 1.1 (Hex package)
│       └── mime ~> 1.0 (Hex package)
├── phoenix ~> 1.3.0 (Hex package)
│   ├── cowboy ~> 1.0 (Hex package)
│   ├── phoenix_pubsub ~> 1.0 (Hex package)
│   ├── plug ~> 1.3.3 or ~> 1.4 (Hex package)
│   └── poison ~> 2.2 or ~> 3.0 (Hex package)
├── phoenix_live_reload ~> 1.0 (Hex package)
│   ├── file_system ~> 0.1 (Hex package)
│   └── phoenix ~> 1.0 or ~> 1.2 or ~> 1.3 (Hex package)
├── postgrex >= 0.0.0 (Hex package)
│   ├── connection ~> 1.0 (Hex package)
│   ├── db_connection ~> 1.1 (Hex package)
│   │   ├── connection ~> 1.0.2 (Hex package)
│   │   └── poolboy ~> 1.5 (Hex package)
│   └── decimal ~> 1.0 (Hex package)
└── phoenix_ecto ~> 3.2 (Hex package)
    ├── ecto ~> 2.1 (Hex package)
    │   ├── db_connection ~> 1.1 (Hex package)
    │   ├── decimal ~> 1.2 (Hex package)
    │   ├── poison ~> 2.2 or ~> 3.0 (Hex package)
    │   ├── poolboy ~> 1.5 (Hex package)
    │   └── postgrex ~> 0.13.0 (Hex package)
    ├── phoenix_html ~> 2.9 (Hex package)
    └── plug ~> 1.0 (Hex package)
```

あと、 Rails と違って、これらの依存ライブラリは全部プロジェクト内(`blog/deps`以下)に
存在するみたいです(Railsでいうと gem を vendor 以下に入れたような状態)。

そのほか、 mix help などを見ると(Rails でいうと、 rake -T みたいなやつ)、
何ができるかだいたいわかります。

```
$ mix help
mix                    # Runs the default task (current: "mix run")
mix app.start          # Starts all registered apps
mix app.tree           # Prints the application tree
mix archive            # Lists installed archives
mix archive.build      # Archives this project into a .ez file
mix archive.install    # Installs an archive locally
mix archive.uninstall  # Uninstalls archives
mix clean              # Deletes generated application files
mix cmd                # Executes the given command
mix compile            # Compiles source files
mix deps               # Lists dependencies and their status
mix deps.clean         # Deletes the given dependencies' files
mix deps.compile       # Compiles dependencies
mix deps.get           # Gets all out of date dependencies
mix deps.tree          # Prints the dependency tree
mix deps.unlock        # Unlocks the given dependencies
mix deps.update        # Updates the given dependencies
mix do                 # Executes the tasks separated by comma
mix ecto               # Prints Ecto help information
mix ecto.create        # Creates the repository storage
mix ecto.drop          # Drops the repository storage
mix ecto.dump          # Dumps the repository database structure
mix ecto.gen.migration # Generates a new migration for the repo
mix ecto.gen.repo      # Generates a new repository
mix ecto.load          # Loads previously dumped database structure
mix ecto.migrate       # Runs the repository migrations
mix ecto.migrations    # Displays the repository migration status
mix ecto.rollback      # Rolls back the repository migrations
mix escript            # Lists installed escripts
mix escript.build      # Builds an escript for the project
mix escript.install    # Installs an escript locally
mix escript.uninstall  # Uninstalls escripts
mix gettext.extract    # Extracts translations from source code
mix gettext.merge      # Merge template files into translation files
mix help               # Prints help information for tasks
mix hex                # Prints Hex help information
mix hex.audit          # Shows retired Hex dependencies
mix hex.build          # Builds a new package version locally
mix hex.config         # Reads, updates or deletes Hex config
mix hex.docs           # Fetches or opens documentation of a package
mix hex.info           # Prints Hex information
mix hex.outdated       # Shows outdated Hex deps for the current project
mix hex.owner          # Manages Hex package ownership
mix hex.publish        # Publishes a new package version
mix hex.repo           # Manages Hex repositories
mix hex.retire         # Retires a package version
mix hex.search         # Searches for package names
mix hex.user           # Manages your Hex user account
mix loadconfig         # Loads and persists the given configuration
mix local              # Lists local tasks
mix local.hex          # Installs Hex locally
mix local.phoenix      # Updates Phoenix locally
mix local.phx          # Updates the Phoenix project generator locally
mix local.public_keys  # Manages public keys
mix local.rebar        # Installs Rebar locally
mix new                # Creates a new Elixir project
mix phoenix.gen.html   # Generates controller, model and views for an HTML based resource
mix phoenix.new        # Creates a new Phoenix v1.3.0 application
mix phoenix.server     # Starts applications and their servers
mix phx.digest         # Digests and compresses static files
mix phx.digest.clean   # Removes old versions of static assets.
mix phx.gen.channel    # Generates a Phoenix channel
mix phx.gen.context    # Generates a context with functions around an Ecto schema
mix phx.gen.embedded   # Generates an embedded Ecto schema file
mix phx.gen.html       # Generates controller, views, and context for an HTML resource
mix phx.gen.json       # Generates controller, views, and context for a JSON resource
mix phx.gen.presence   # Generates a Presence tracker
mix phx.gen.schema     # Generates an Ecto schema and migration file
mix phx.gen.secret     # Generates a secret
mix phx.new            # Creates a new Phoenix v1.3.0 application
mix phx.new.ecto       # Creates a new Ecto project within an umbrella project
mix phx.new.web        # Creates a new Phoenix web project within an umbrella project
mix phx.routes         # Prints all routes
mix phx.server         # Starts applications and their servers
mix profile.cprof      # Profiles the given file or expression with cprof
mix profile.fprof      # Profiles the given file or expression with fprof
mix run                # Runs the given file or expression
mix test               # Runs a project's tests
mix xref               # Performs cross reference checks
iex -S mix             # Starts IEx and runs the default task
```

## DBの作成

上記で ecto.create しろって出てたので、やります。


```
$ mix ecto.create
```

DB を見ると、`blog_dev` という名前の空のDBができてますね。

## Scaffold 的なやつ

phx.gen.html というのでやるらしいです。


```
$ mix phx.gen.html Articles Article articles title body:text
* creating lib/blog_web/controllers/article_controller.ex
* creating lib/blog_web/templates/article/edit.html.eex
* creating lib/blog_web/templates/article/form.html.eex
* creating lib/blog_web/templates/article/index.html.eex
* creating lib/blog_web/templates/article/new.html.eex
* creating lib/blog_web/templates/article/show.html.eex
* creating lib/blog_web/views/article_view.ex
* creating test/blog_web/controllers/article_controller_test.exs
* creating lib/blog/articles/article.ex
* creating priv/repo/migrations/20170816110139_create_articles.exs
* creating lib/blog/articles/articles.ex
* injecting lib/blog/articles/articles.ex
* creating test/blog/articles/articles_test.exs
* injecting test/blog/articles/articles_test.exs

Add the resource to your browser scope in lib/blog_web/router.ex:

    resources "/articles", ArticleController


Remember to update your repository by running migrations:

    $ mix ecto.migrate


```

git commit しておきましょう。私は spacemacs + magit でやるので、
コマンドラインでどうやるのが適切なのかは知らず、ここには書けませんが、
みなさんいい感じに git add して git commit してください。

Rails 同様、型名を省略すると string 型として扱ってくれるようです。

Rails との違いとしては

- コンテキスト名
- リソース名単数
- リソース名複数(テーブル名？)

というように同じような名前を3つ書かないといけない点ですね。

また、Rails だと routes への追加も自動でやってくれますが、
phoenix はそうではないみたいです。

ちなみに、この状態で http://localhost:4000/ にアクセスすると、
ターミナルにこんなエラーが出てます。


```
== Compilation error in file lib/blog_web/views/article_view.ex ==
** (CompileError) lib/blog_web/templates/article/edit.html.eex:3: undefined function article_path/3
    (stdlib) lists.erl:1338: :lists.foreach/2
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:121: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
```

なるほど、 article_path が定義されてない、と。完全に route のせいですね。


## route の追加

``` elixir
defmodule BlogWeb.Router do
  use BlogWeb, :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 "/", BlogWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index

    resources "/articles", ArticleController # ここでいいのかな？
  end

  # Other scopes may use custom stacks.
  # scope "/api", BlogWeb do
  #   pipe_through :api
  # end
end
```

これで再び http://localhost:4000/ へアクセスすると、問題なく表示されます。

ターミナルの方にはもうエラーは出なくなり、

```
[info] GET /
[debug] Processing with BlogWeb.PageController.index/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 271µs
```

って出てます。

ここでも git commit しておきましょう。

## migrate

もう一つ、 migrate しろって出てましたね。

```
$ mix ecto.migrate

20:25:30.733 [info]  == Running Blog.Repo.Migrations.CreateArticles.change/0 forward

20:25:30.733 [info]  create table articles

20:25:30.746 [info]  == Migrated in 0.0s
```

ふむふむ。 DB を見ると...(Postico.appのスクリーンショットです)

![migrateでできたテーブル群](https://qiita-image-store.s3.amazonaws.com/0/30009/7a287df9-4d28-765e-548d-a9a3694bcf45.png)

schema_migrations て、完全に Rails と同じですねw

![articlesテーブルの構造](https://qiita-image-store.s3.amazonaws.com/0/30009/8c9abf85-1068-3ede-2a38-7a2d2c09f322.png)

ふむふむ。 id が primary キーになってて。何一つ驚くところはありませんね。良い意味で「普通」。

![schema_migrations内のデータ](https://qiita-image-store.s3.amazonaws.com/0/30009/140879bb-24a4-8756-d717-8524308745ae.png)

schema_migrations の中身も Rails と同じ。

## 動作確認

### Article 一覧

![Article一覧](https://qiita-image-store.s3.amazonaws.com/0/30009/44a57c7c-a8df-4a09-56cb-e406bd8a7797.png)

おー！出ましたねぇ！データの登録をしてみましょう。


### Article 登録

![Article登録](https://qiita-image-store.s3.amazonaws.com/0/30009/a5dc0f4b-d4a2-3793-3a98-de515222cb8b.png)


```
[info] POST /articles
[debug] Processing with BlogWeb.ArticleController.create/2
  Parameters: %{"_csrf_token" => "RjolInAZHU0VGxZHPlY7MQI/YFQqEAAA0kRq4aO8PZX1OburoUY9KQ==", "_utf8" => "✓", "article" => %{"body" => "こんにちは。ブログ始め
ました。\r\n\r\nではでは。", "title" => "ブログはじめました"}}
  Pipelines: [:browser]
[debug] QUERY OK db=9.2ms
INSERT INTO "articles" ("body","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["こんにちは。ブログ始めました。\r\n\r\nではでは。", "
ブログはじめました", {{2017, 8, 16}, {11, 32, 14, 597018}}, {{2017, 8, 16}, {11, 32, 14, 660880}}]
[info] Sent 302 in 113ms
```

Rails に比べると、若干 SQL が見にくいかな。

### Article 詳細

![Article詳細](https://qiita-image-store.s3.amazonaws.com/0/30009/5fdd9add-1964-3362-e216-983c0200b721.png)

```
[info] GET /articles/1
[debug] Processing with BlogWeb.ArticleController.show/2
  Parameters: %{"id" => "1"}
  Pipelines: [:browser]
[debug] QUERY OK source="articles" db=8.5ms queue=0.4ms
SELECT a0."id", a0."body", a0."title", a0."inserted_at", a0."updated_at" FROM "articles" AS a0 WHERE (a0."id" = $1) [1]
[info] Sent 200 in 10ms
```

再度、Aritlce一覧に戻って見ましょう。

### データが入った状態の Article 一覧

![再度、Aritlce一覧](https://qiita-image-store.s3.amazonaws.com/0/30009/f77beb42-d019-7a59-130a-c2da501f41bb.png)

```
[info] GET /articles
[debug] Processing with BlogWeb.ArticleController.index/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="articles" db=1.7ms
SELECT a0."id", a0."body", a0."title", a0."inserted_at", a0."updated_at" FROM "articles" AS a0 []
[info] Sent 200 in 2ms
```

ふむふむ。デザインはスッキリしてますが、付いている機能は概ね Rails と同じですね。

### 開発者コンソール

ちょっと開発者コンソール見てみましょう。

![開発者コンソールの Network](https://qiita-image-store.s3.amazonaws.com/0/30009/1a9501cf-d719-7563-7d15-3d290a40b554.png)

概ね想定内ですが、 websocket というのが繋がっているのと、frame という名前で live-reload 関連の（？）
Javascript を読み込んでいるところが目を引きますね。

### エラー画面

![エラー画面](https://qiita-image-store.s3.amazonaws.com/0/30009/8337a479-83e8-b96e-cc21-159a9f5bd7d0.png)

スタックトレースと、それぞれのフレームのソースコードが見られます。


## 軽くソースコード探訪

route はさっき見たので...

### migration

``` elixir:/blog/priv/repo/migrations/20170816110139_create_articles.exs
defmodule Blog.Repo.Migrations.CreateArticles do
  use Ecto.Migration

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

      timestamps()
    end

  end
end

```

ふむ。ここも驚くところは全くありませんね。
`priv` ってパスに入れてるところに、やや工夫が見られます。
`repo` ってのは DB アクセスモジュールの名前(ActiveRecordみたいなやつ)
なんじゃないかと思います。

### モデル

``` elixir:/blog/lib/blog/articles/article.ex
defmodule Blog.Articles.Article do
  use Ecto.Schema
  import Ecto.Changeset
  alias Blog.Articles.Article


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

    timestamps()
  end

  @doc false
  def changeset(%Article{} = article, attrs) do
    article
    |> cast(attrs, [:title, :body])
    |> validate_required([:title, :body])
  end
end

```

ここにも DB の定義があるのがやや驚きなのと、
changeset というのがよくわからないですね。

``` elixir:/blog/lib/blog/articles/articles.ex
defmodule Blog.Articles do
  @moduledoc """
  The Articles context.
  """

  import Ecto.Query, warn: false
  alias Blog.Repo

  alias Blog.Articles.Article

  @doc """
  Returns the list of articles.

  ## Examples

      iex> list_articles()
      [%Article{}, ...]

  """
  def list_articles do
    Repo.all(Article)
  end

  @doc """
  Gets a single article.

  Raises `Ecto.NoResultsError` if the Article does not exist.

  ## Examples

      iex> get_article!(123)
      %Article{}

      iex> get_article!(456)
      ** (Ecto.NoResultsError)

  """
  def get_article!(id), do: Repo.get!(Article, id)

  @doc """
  Creates a article.

  ## Examples

      iex> create_article(%{field: value})
      {:ok, %Article{}}

      iex> create_article(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_article(attrs \\ %{}) do
    %Article{}
    |> Article.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a article.

  ## Examples

      iex> update_article(article, %{field: new_value})
      {:ok, %Article{}}

      iex> update_article(article, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_article(%Article{} = article, attrs) do
    article
    |> Article.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a Article.

  ## Examples

      iex> delete_article(article)
      {:ok, %Article{}}

      iex> delete_article(article)
      {:error, %Ecto.Changeset{}}

  """
  def delete_article(%Article{} = article) do
    Repo.delete(article)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking article changes.

  ## Examples

      iex> change_article(article)
      %Ecto.Changeset{source: %Article{}}

  """
  def change_article(%Article{} = article) do
    Article.changeset(article, %{})
  end
end

```

ほほーぅ。これは Rails だと ActiveRecord がメタプログラミングで動的に生成してくれる
処理になりますかねぇ？

### コントローラー

``` elixir:/blog/lib/blog_web/controllers/article_controller.ex
defmodule BlogWeb.ArticleController do
  use BlogWeb, :controller

  alias Blog.Articles
  alias Blog.Articles.Article

  def index(conn, _params) do
    articles = Articles.list_articles()
    render(conn, "index.html", articles: articles)
  end

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

  def create(conn, %{"article" => article_params}) do
    case Articles.create_article(article_params) do
      {:ok, article} ->
        conn
        |> put_flash(:info, "Article created successfully.")
        |> redirect(to: article_path(conn, :show, article))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    article = Articles.get_article!(id)
    render(conn, "show.html", article: article)
  end

  def edit(conn, %{"id" => id}) do
    article = Articles.get_article!(id)
    changeset = Articles.change_article(article)
    render(conn, "edit.html", article: article, changeset: changeset)
  end

  def update(conn, %{"id" => id, "article" => article_params}) do
    article = Articles.get_article!(id)

    case Articles.update_article(article, article_params) do
      {:ok, article} ->
        conn
        |> put_flash(:info, "Article updated successfully.")
        |> redirect(to: article_path(conn, :show, article))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", article: article, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    article = Articles.get_article!(id)
    {:ok, _article} = Articles.delete_article(article)

    conn
    |> put_flash(:info, "Article deleted successfully.")
    |> redirect(to: article_path(conn, :index))
  end
end

```

だいたい想定内のコードですが、 相変わらず changeset というのがよくわからないのと、
conn というのもわからないですね。

それから、モデルは `lib/blog` というパスの下にあるのに対し、
コントローラーとビューは `lib/blog_web`というパスにあるのが目を引きます。
ビジネスロジックに Web インターフェースをくっつけているのだ、というポリシーを感じます。

### ビュー

```:/blog/lib/blog_web/templates/article/form.html.eex
<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

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

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

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>

```

すごく...Railsです...

error_tag ってのが Rails とは違うのと、ここにも出てきた changeset。よくわかりません。

## まとめ

- Phoenix で Scaffold がほとんどつまずくことなくできて、出来上がったアプリも全く問題なく動作しました。
- コードをほぼ書きませんでした。routerを手で追記した1行ぐらい？コードとは呼べない。
- 非常に Rails に似ている部分が多いことがわかりました。
- それと同時に、 Rails と違う部分も垣間見え、よくわかりませんでした（今後調べます）。