cowboy2とectoでRESTサーバ

[cowboy](https://github.com/ninenines/cowboy)と[ecto](https://github.com/elixir-lang/ecto)でシンプルなRESTサーバを実装してみます。
(素直に[Phoenix](http://www.phoenixframework.org/)を使えとの神の声が聞こえてきそうですが、勉学目的で地道に行ってみます)

以下の通り動作するサーバを実装します:

```bash
$ curl localhost:3000/hello-school-live/characters
["丈槍 由紀","佐倉 慈","若狭 悠里","恵飛須沢 胡桃","直樹 美紀"]
```

このサンプルでは最新のcowboy 2系を利用しています(@ColdFreak さん、ご指摘ありがとうございます)。cowboy 2系ではErlang 18以上が必要となります。また、1系のサンプルについてはソースのみですが[こちら](https://github.com/p-baleine/hello_school_live/tree/cowboy1)にあります。

# 事始め

まずmixでアプリケーションを作成します:

```bash
$ mix new --sup hello_school_live
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/hello_school_live.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_school_live_test.exs

Your mix project was created successfully.
You can use mix to compile it, test it, and more:

    cd hello_school_live
    mix test

Run `mix help` for more commands.
```

できたディレクトリに移動しておきます:

```bash
$ cd hello_school_live
```

# Hello, cowboy!

[cowboy](https://github.com/ninenines/cowboy)は言わずと知れたErlang向けHTTPサーバです、Phoenixも内部ではcowboyを使っています。まず動作確認用に「Hello, School Live!」という文字列を返却するページを作ってみます。

`mix.exs`にcowboyへのdependencyを追記します。

mix.exs

```elixir
def application do
  [applications: [:logger, :cowboy], # ← `:cowboy`を追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"}, # ← 追記
  ]
end
```

`lib/hello_school_live.ex`にルーティングの設定とcowboyの起動処理を追記します。

lib/hello_school_live.ex

```elixir
def start(_type, _args) do
  import Supervisor.Spec, warn: false

  dispatch = :cowboy_router.compile routes # ← 追記
  {:ok, _} = :cowboy.start_http :http, 100, [{:port, 3000}], [{:env, [{:dispatch, dispatch}]}] # ← 追記

  children = [
  ...省略...
end

# ↓の関数を追記
defp routes do
  [
    {:_, [
          {"/hello-school-live", HelloSchoolLive.Handlers.HelloSchoolLive, []}
        ]
    }
  ]
end
```

ハンドラを格納するディレクトリを作って:

```bash
$ mkdir -p lib/hello_school_live/handlers
```

`lib/hello_school_live/handlers/hello_school_live.ex`を以下内容で作成します:

lib/hello_school_live/handlers/hello_school_live.ex

```elixir
defmodule HelloSchoolLive.Handlers.HelloSchoolLive do
  def init(req, opts) do
    req2 = :cowboy_req.reply 200, [{"content-type", "text/plain"}], "Hello, School Live!", req
    {:ok, req2, opts}
  end
end
```

dependencyを取得した後、起動してみます、

```bash
$ mix deps.get
...省略...
$ iex -S mix
...省略...
```

ブラウザで http://localhost:3000/hello-school-live にアクセスして「Hello, School Live!」が表示されることを確認します。

# Hello, ecto!

ectoは「Elixir向けのデータベースラッパーと統合クエリ言語」と[本家](https://github.com/elixir-lang/ecto)では紹介されています、個人的には綺麗にRailsから分離されたActiveRecordのElixir板と理解するのがわかり易いかと。ここではデータベースとしてPostgreSQLを利用します、その他のデータベースを利用する場合、アダプターを適宜置き換えることで動くはず…。
まず動作確認用にiexでcharactersテーブルから一覧取得できるようにしてみます。

mix.exsにectoへのdependencyを追記します:

```elixir
def application do
  [applications: [:logger, :cowboy, :postgrex, :ecto], # ← 追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"},
    {:postgrex, ">= 0.0.0"}, # ← 追記
    {:ecto, "~> 0.14.3"} # ← 追記
  ]
end
```

依存関係を解決してコンパイルしておきます:

```bash
$ mix do deps.get, compile
...省略...
```

`mix -h`を実行するとecto関連のタスクが追加されているのがわかります

```bash
$ mix -h
mix                    # Run the default task (current: mix run)
...
mix do                 # Executes the tasks separated by comma
mix ecto.create        # Create the storage for the repo
mix ecto.drop          # Drop the storage for the repo
mix ecto.gen.migration # Generate a new migration for the repo
mix ecto.gen.repo      # Generate a new repository
mix ecto.migrate       # Run migrations up on a repo
mix ecto.rollback      # Rollback migrations from a repo
mix escript.build      # Builds an escript for the project
...
```

以下コマンドでリポジトリを作成します:

```bash
$ mix ecto.gen.repo
* creating lib/hello_school_live
* creating lib/hello_school_live/repo.ex
* updating config/config.exs
Don't forget to add your new repo to your supervision tree
(typically in lib/hello_school_live.ex):

    worker(HelloSchoolLive.Repo, [])
```

上記出力にあるとおり、supervision treeにリポジトリを追加する必要があります。`lib/hello_school_live.ex`に以下を追記します:

lib/hello_school_live.ex

```elixir
def start(_type, _args) do
  ...省略...

  children = [
    worker(HelloSchoolLive.Repo, [])
  ]

  ...省略...
end
```

また作成された`config/config.exs`のリポジトリ設定を環境に合わせて設定します、以下は私の環境の例です

config/config.exs

```elixir
config :hello_school_live, HelloSchoolLive.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "hello_school_live_repo",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"
```

以下コマンドでデータベースを作成します:

```bash
$ mix ecto.create
Compiled lib/hello_school_live/handlers/hello_school_live.ex
Compiled lib/hello_school_live.ex
Compiled lib/hello_school_live/repo.ex
Generated hello_school_live app
The database for HelloSchoolLive.Repo has been created.
```

以下コマンドでマイグレーションファイルを作成します:

```bash
$ mix ecto.gen.migration characters
* creating priv/repo/migrations
* creating priv/repo/migrations/20150815055420_characters.exs
```

`priv/repo/migrations/<TIMESTAMP>_characters.exs`を以下の通り編集します:

priv/repo/migrations/TIMESTAMP_characters.exs

```elixir
def change do
  create table(:characters) do # ← 追記
    add :name, :string
  end
end
```

以下コマンドでマイグレーションを適用します:

```bash
$ mix ecto.migrate

14:56:18.082 [info]  == Running HelloSchoolLive.Repo.Migrations.Characters.change/0 forward

14:56:18.082 [info]  create table characters

14:56:18.087 [info]  == Migrated in 0.0s
```

次にモデルを作成します。まずモデルを格納するディレクトリを作成して、

```bash
$ mkdir lib/hello_school_live/models
```

`lib/hello_school_live/models/character.ex`を以下内容で作成します。

lib/hello_school_live/models/character.ex

```elixir
defmodule HelloSchoolLive.Models.Character do
  use Ecto.Model
  schema "characters" do
    field :name, :string
  end
end
```

iexで動作確認してみます:

```bash
$ iex -S mix
iex(1)> character = %HelloSchoolLive.Models.Character{name: "丈槍 由紀"}
iex(2)> HelloSchoolLive.Repo.insert! character
iex(3)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "佐倉 慈"}
iex(4)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "若狭 悠里"}
iex(5)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "恵飛須沢 胡桃"}
iex(6)> HelloSchoolLive.Repo.insert! %HelloSchoolLive.Models.Character{name: "直樹 美紀"}

iex(7)> import Ecto.Query
iex(8)> query = from c in HelloSchoolLive.Models.Character, select: c
iex(9)> HelloSchoolLive.Repo.all(query) |> Enum.each(&(IO.puts &1.name))

15:05:41.468 [debug] SELECT c0."id", c0."name" FROM "characters" AS c0 [] OK query=2.7ms
丈槍 由紀
佐倉 慈
若狭 悠里
恵飛須沢 胡桃
直樹 美紀
:ok
```

# Hello, School Live!

まとめです、冒頭で提示したAPIを実装します。

まず`lib/hello_school_live.ex`にルーティング`/hello-school-live/characters`を追記します。

lib/hello_school_live.ex

```elixir
defp routes do
  [
    {:_, [
          {"/hello-school-live", HelloSchoolLive.Handlers.HelloSchoolLive, []},
          {"/hello-school-live/characters", HelloSchoolLive.Handlers.Characters, []} # ← 追記
        ]
    }
  ]
end
```

このAPIではJSONで応答したいので、応答時にはElxiirのtermをJSONエンコードした文字列を返却する必要があります。ここではElixir向けJSONライブラリの[poison](https://github.com/devinus/poison)を利用します。`mix.exs`にpoisonのdependencyを追記します

mix.exs

```elixir
def application do
  [applications: [:logger, :cowboy, :postgrex, :ecto, :poison], # ← 追記
   mod: {HelloSchoolLive, []}]
end

...省略...

defp deps do
  [
    {:cowboy, github: "ninenines/cowboy"},
    {:postgrex, ">= 0.0.0"},
    {:ecto, "~> 0.14.3"},
    {:poison, "~> 1.4.0"} # ← 追記
  ]
end
```

依存関係を解決しておきます:

```bash
$ mix deps.get
```

次にCharactersハンドラを実装します、`lib/hello_school_live/handlers/characters.ex`を以下内容で作成します:

lib/hello_school_live/handlers/characters.ex

```elixir
defmodule HelloSchoolLive.Handlers.Characters do
  import Ecto.Query

  def init(req, opts) do
    query = from c in HelloSchoolLive.Models.Character, select: c
    characters = HelloSchoolLive.Repo.all(query) |> Enum.map(&(&1.name))
    req2 = :cowboy_req.reply 200, [{"content-type", "application/json"}],
      Poison.Encoder.encode(characters, []), req
    {:ok, req2, opts}
  end
end
```

最後に確認です:

```bash
$ iex -S mix
```

別のshellを開いて以下コマンドを実行します

```bash
$ curl localhost:3000/hello-school-live/characters
["丈槍 由紀","佐倉 慈","若狭 悠里","恵飛須沢 胡桃","直樹 美紀"]
```

上記の通り出力されることを確認します。

以上のソースは[ここ](https://github.com/p-baleine/hello_school_live)においてあります。