PhoenixでREST-APIを実装する

# やること
[これまで作成したデモアプリ](http://qiita.com/ak-ymst/items/b000461f4deeffc96f9f)に、記事情報を取得するためのREST-APIを追加する

# generatorを使う場合
## コントローラの作成
mixコマンドを使ってAPI用のコントローラを作成する。

```
$ mix phoenix.gen.json ApiPost posts --no-model
```

記事を表すPostモデルはすでに存在するので、--no-modelオプションを付ける。
しかし、このままでは生成されたコントローラは存在しない`ApiPost`というモデルを扱おうとしてしまうので、web/controllers/api_post_controller.exを開き、モデル名をApiPost->Postに置換する。

## ルートの追加
web/routes.exを開き、以下の記述を追加

```ex:web/routes.ex
  scope "/api", PhoenixSample do
    pipe_through :api

    resources "/posts", ApiPostController
  end
```

実際にhttp://localhost:4000/api/postsにアクセスしてみると...

```
$ http -b http://localhost:4000/api/posts
{
    "data": [
        {
            "id": 1
        }, 
        {
            "id": 2
        }
    ]
}
```

ルートは有効になっているし、データも取れているようだが、IDしか表示されないのでは話にならない。

## ビューの修正
ということで、ビューを修正する。
web/views/api_post_view.exを開き、内容を確認すると

```ex:web/views/api_post_view.ex
  def render("api_post.json", %{api_post: api_post}) do
    %{id: api_post.id}
  end
```

という記述に気がつく。
どうやら、generator使用時にmodelのプロパティを省略したため、IDしか表示されてなかったようだ。
そこで、以下のように表示項目を追加

```ex:web/views/api_post_view.ex
  def render("api_post.json", %{api_post: api_post}) do
    %{id: api_post.id,
      title: api_post.title, 
      content: api_post.content,
      inserted_at: api_post.inserted_at
     }
  end
```

改めてアクセスしてみると...

```
$ http -b http://localhost:4000/api/posts
{
    "data": [
        {
            "content": "希望の朝だ！", 
            "id": 1, 
            "inserted_at": "2016-02-08T03:26:39Z", 
            "title": "新しい朝が来た"
        }, 
        {
            "content": "輝く緑", 
            "id": 2, 
            "inserted_at": "2016-02-18T21:40:06Z", 
            "title": "新しい朝のもと"
        }
    ]
}

```

今度はID以外の情報もちゃんと取得できるようになりました。

しかし、今のままでは一覧情報と詳細情報とで同じ情報を取得するようになっている。
ブラウザ向けの画面にしろ、それ以外のクライアント向けのAPIにしろ、リストで取得する情報と
詳細情報で取得する情報は異なるものだと思うので、それに合わせて更にビューを修正することにする。
具体的には、一覧/詳細APIでは以下の様な情報を返すようにする。
- 一覧APIではIDとタイトルと投稿日時、それと詳細APIへのリンクを返す
- 詳細APIではIDとタイトルと内容、それとコメント一覧を返す
- ついでに、一覧APIで取得されるリストにつけられるキー名を`data`から`posts`にかえ、詳細APIで取得する情報については、ラップされていないオブジェクトとして返す

viewを変更した結果が以下のとおり

```ex:web/views/api_post_view.ex
defmodule PhoenixSample.ApiPostView do
  use PhoenixSample.Web, :view

  def render("index.json", %{posts: posts}) do
   %{posts: render_many(posts, PhoenixSample.ApiPostView, "api_post_header.json")}
  end

  def render("show.json", %{api_post: api_post}) do
    render_one(api_post, PhoenixSample.ApiPostView, "api_post_body.json")
  end

  def render("api_post_header.json", %{api_post: api_post}) do
    %{id: api_post.id,
      title: api_post.title, 
      inserted_at: api_post.inserted_at,
      link: api_post_path(PhoenixSample.Endpoint, :show, api_post)
     }
  end

  def render("api_post_body.json", %{api_post: api_post}) do
    %{id: api_post.id,
      title: api_post.title, 
      content: api_post.content,
      inserted_at: api_post.inserted_at,
      comments: render_many(api_post.comments, PhoenixSample.ApiPostView, "comment.json")
     }
  end

  def render("comment.json", %{api_post: comment}) do
    %{id: comment.id,
      name: comment.name,
      content: comment.content,
      inserted_at: comment.inserted_at
     }
  end
end

```

実際にアクセスしてみると...

```
$ http -b http://localhost:4000/api/posts
{
    "posts": [
        {
            "id": 1, 
            "inserted_at": "2016-02-08T03:26:39Z", 
            "link": "/api/posts/1", 
            "title": "新しい朝が来た"
        }, 
	{
            "id": 2, 
            "inserted_at": "2016-02-18T21:40:06Z", 
            "link": "/api/posts/2", 
            "title": "新しい朝のもと"
        }
    ]
}

$ http -b http://localhost:4000/api/posts/1
{
    "comments": [
        {
            "content": "喜びに胸を開け", 
            "id": 2, 
            "inserted_at": "2016-02-09T17:29:18Z", 
            "name": "名無し"
        }, 
        {
            "content": "大空あおげ", 
            "id": 3, 
            "inserted_at": "2016-02-09T00:00:00Z", 
            "name": "通りがかり"
        }, 
        {
            "content": "ラジオの声に", 
            "id": 6, 
            "inserted_at": "2016-02-09T10:33:14Z", 
            "name": "名無し2"
        }, 
         {
            "content": "健やかな胸を", 
            "id": 7, 
            "inserted_at": "2016-02-13T08:52:45Z", 
            "name": "3"
        }
    ], 
    "content": "希望の朝だ！", 
    "id": 1, 
    "inserted_at": "2016-02-08T03:26:39Z", 
    "title": "新しい朝が来た"
} 
```

これで、だいたい欲しかったものは出来上がりました。

ちなみに、詳細APIのURIを生成するために、Viewの中で呼び出している

```
      link: api_post_path(PhoenixSample.Endpoint, :show, api_post)
```

この関数ですが、ControllerやTemplateのなかでは

```
      api_post_path(conn, :show, api_post)
```

あるいは

```
      api_post_path(@conn, :show, api_post)
```

という形で使用されているので、同じようにconnを使った形で書きたかったのですが、controllerから渡されているはずの、この`conn`へのアクセスの仕方がわからなかったのでこのように書いています。
[こちらの記事](http://qiita.com/uasi/items/a49d9c84d113dd54d0be)によると、引数のmapの中に含まれているようにも見えるのですが、もう少し詳しく調査してみないとわかりません。

## （追記）Viewの中で、connにアクセスする方法について
コメントで @uasiさんに教えていただきました。情報ありがとうございます。
render関数の第二引数のキーとしてconnを指定すればアクセス可能とのことなので、

```
  def render("index.json", %{posts: posts}) do
   %{posts: render_many(posts, PhoenixSample.ApiPostView, "api_post_header.json")}
  end
```
を

```
  def render("index.json", %{posts: posts, conn: conn}) do
   %{posts: render_many(posts, PhoenixSample.ApiPostView, "api_post_header.json")}
  end
```
と変えてみたところ、connにアクセスすることができました。
しかし、このrender_manyを経由して呼び出される

```
  def render("api_post_header.json", %{api_post: api_post}) do
    %{id: api_post.id,
      title: api_post.title, 
      inserted_at: api_post.inserted_at,
      link: api_post_path(PhoenixSample.Endpoint, :show, api_post)
     }
  end
```
を

```
  def render("api_post_header.json", %{api_post: api_post, conn: conn}) do
    %{id: api_post.id,
      title: api_post.title, 
      inserted_at: api_post.inserted_at,
      link: api_post_path(conn, :show, api_post)
     }
  end
```

と変えてみると...
![スクリーンショット 2016-02-21 13.27.06.png](https://qiita-image-store.s3.amazonaws.com/0/111295/edd522f5-6e41-b2e5-2df9-a56ef7061900.png)
とマッチする関数が見つからないというエラー。
なので、少し戻って、render_manyに渡している引数を、モデルのリストから
link先URIを含んだmapのリストに変えてみる。

```
  def render("index.json", %{posts: posts, conn: conn}) do
    posts = posts |> Enum.map(fn(post) -> Map.merge( Map.from_struct(post), %{link: api_post_path(conn, :show, post)}) end)

    %{posts: render_many(posts, PhoenixSample.ApiPostView, "api_post_header.json")}
  end

  def render("api_post_header.json", %{api_post: api_post}) do
    %{id: api_post.id,
      title: api_post.title, 
      inserted_at: api_post.inserted_at,
      link: api_post.link
     }
  end
```

このやり方なら、想定通りの出力が得られましたが、モデルを一度別のマップにいちいち変換しているのが、スマートじゃないような気がするんですよね…

## 追記2
@uasiさんから、さらなる情報。
たびたびありがとうございます。

> render_many/4 の第4引数に conn を入れた Map をセットすると def  render("api_post_header.json", %{api_post: api_post, conn: conn}) に渡されますよ：

実際に, render_manyの呼び出しを

```
    render_many(posts, PhoenixSample.ApiPostView, "api_post_header.json", %{conn: conn})
```
に変えてみたところ、

```
def render("api_post_header.json", %{api_post: api_post, conn: conn})
```
にマッチしました。
また、試しにrender_oneの第4引数に同じマップを渡してみたところ、こちらも同様の挙動が確認されました。
render_manyやrender_oneは第1引数（の各要素）を含むマップと第4引数のマップをマージしたものが、最終的にrender関数の第2引数として渡されるようです。

# 別のやり方
ブラウザ向けの出力とAPI向けの出力を同じコントローラで行う方法が、[こちらの記事](http://qiita.com/FL4TLiN3/items/41ca80cdbfca1956ed78)で紹介されておりました。
こちらも、非常にスマートなやり方だと思います。

ただ、個人的には、statelessに作りたいAPI機能と、セッション情報を使うであろうstatefulなブラウザ向け機能を一つのコントローラで処理しようとすると、お互いの事情の間に色々と齟齬が出て
面倒なことになるんじゃないかとも思います。

# 参考サイト
[Elixir] PhoenixでJSONを返すWeb APIを作る: http://qiita.com/FL4TLiN3/items/41ca80cdbfca1956ed78

Phoenix のコントローラで render/3 を呼んでからのレンダリング処理の流れを追う: http://qiita.com/uasi/items/a49d9c84d113dd54d0be