CSVとDBアクセス(Ecto、SQL)をMapListでサクっと

（この記事は [Elixir (その2)とPhoenix Advent Calendar 2016](http://qiita.com/advent-calendar/2016/elixir2_and_phoenix) 17日目の記事です）

[前回](http://qiita.com/piacere/items/b8b6155cfd209904530e)、再帰処理を書かずにJSON項目を抜くためのミニ汎用関数「MapList.select()」を作りましたが、今回はこれを使って、以下3つを試してみましょう :shaved_ice:

① CSVファイルの特定列をリストアップする
② Ecto経由で取得したDBデータの特定列をリストアップする
③ SQLで取得したDBデータの特定列をリストアップする

※そうそう、Enum.map()をスラスラ書ける人には、MapList.select()自体が不要です:relaxed:

# 事前準備：MapListモジュールを作る

Elixirプロジェクトを作成します

```
# mix new csv_sample
# cd sample
# mkdir lib/tiny
```

libフォルダ配下に、MapListモジュールを作ります（[前回](http://qiita.com/piacere/items/b8b6155cfd209904530e)と全く同じ内容）

```elixir:lib/tiny/map_list.ex
defmodule MapList do
	def select( list, key1, key2 \\ "", key3 \\ "", key4 \\ "" ), do: _select( list, [], key1, key2, key3, key4 )
	defp _select( [], values, _key1, _key2, _key3, _key4 ), do: Enum.reverse( values )
	defp _select( map_list, values,  key1,  key2,  key3,  key4 ) do
		[ head | tail ] = map_list
		value = cond do
			key2 == "" -> 
				%{ ^key1 => value1 } = head
				value1
			key3 == "" ->
				%{ ^key1 => value1, ^key2 => value2 } = head
				{ value1, value2 }
			key4 == "" ->
				%{ ^key1 => value1, ^key2 => value2, ^key3 => value3 } = head
				{ value1, value2, value3 }
			true	   ->
				%{ ^key1 => value1, ^key2 => value2, ^key3 => value3, ^key4 => value4 } = head
				{ value1, value2, value3, value4 }
		end
		_select( tail, [ value | values ], key1, key2, key3, key4 )
	end
	def dig( map_list, key ) do
		%{ ^key => %{ value } } = map_list
		value
	end
end
```

# ① CSVファイルの特定列をリストアップする

CSVはこんな感じ

sample.csv

```
id, title, body
1,タイトル1,ボディ1
2,タイトル2,ボディ2
3,タイトル3,ボディ3
```

便利なCSVモジュールがあったので使ってみる

```elixir:mix.exs
defmodule CsvSample.Mixfile do
　…
	defp deps do
　…
		{ :csv, "~> 2.0" },
　…
```

モジュール取得します（要ネット接続）

```
# mix deps.get
```

CSVファイルを、1行1マップとして読み込むためのヘルパー関数を追加します

```elixir:lib/tiny/map_list.ex
defmodule MapList do
　…
	def csv_with_header( file_path ) do
		file_path
		|> File.stream!
		|> CSV.decode( headers: true )
		|> Enum.map( &( &1 |> elem( 1 ) ) )
	end
　…
```

これでマップのリストとして扱えるので、MapList.select()して、title列をリストアップしてみる

```elixir:lib/csv.ex
defmodule CsvSample do
	def select() do
		"sample.csv"
		|> MapList.csv_with_header
		|> MapList.select( "title" )
	end
end
```

試してみると．．．

```
# iex -S mix
iex> CsvSample.select()
["タイトル1", "タイトル2", "タイトル3"]
```

うむ、かなりシンプルに仕上がった :v:

# ② Ecto経由で取得したDBデータの特定列をリストアップする

次は、DBデータの特定列をリストアップ :fist:

DBアクセス用のEctoを単体でインストールすることも可能だけど、Phoenixをインストールすれば、「Ecto付き」＋「deps.get自動」＋「Modelクラス作成がmix一発」、と非常にカンタンなので、Phoenixプロジェクトにて構築することにしてみる

※DBを扱うので、事前にPostgreSQLをインストールしておいてください

※この後出てくるPhoenix構築や、JSON API利用について、詳しく知りたい場合は、下記スライドをどうぞ

[Elixir入門「Phoenixで高速Webアプリ ＆ REST APIをサクッと書いてみる」](https://www.slideshare.net/piacere_ex/elixir3phoenixweb-rest-api-75571536)

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/c4a230c7-ecb2-8cb6-5ccc-c8eb65f9a7b5.png)](https://www.slideshare.net/piacere_ex/elixir3phoenixweb-rest-api-75571536)
https://www.slideshare.net/piacere_ex/elixir3phoenixweb-rest-api-75571536
（ここから本編続き）Phoenixプロジェクトを作り、DB構築します

```
# mix phoenix.new db_sample --no-brunch
# cd db_sample
# mix ecto.create
# mix phoenix.gen.html Posts posts title:string body:string
```

マイグレーション（≒MVC追加＋DBテーブル構築）を行うために、ルーティング追加します

```elixir:web/router.ex
defmodule DbSample.Router do
　…
	scope "/", ElmPhpenix do
　…
		resources "/posts", PostController
	end
　…
```

マイグレーションを行った後、ビルドします

```
# mix ecto.migrate
# iex -S mix phoenix.server
```

ブラウザで、以下URLにアクセスし、「New post」リンクをクリックすると、データ追加できます

```
POST http://localhost:4000/posts/
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/c17c5921-31e5-69e7-440b-0a25d16c7030.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/46b80872-7bca-aab5-5ab7-e59bd1458bf9.png)

以下のような2件のデータを追加しておく

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/176d323e-df47-7530-ab13-9733bdaad71a.png)

さて、Ectoでテーブルのデータ全件を取得してみると、SQLのデバッグ表示の後に、1レコード毎に1マップ（構造体）のリストが取得できます

```elixir
iex> DbSample.Posts |> DbSample.Repo.all()
[debug] QUERY OK source="posts" db=0.0ms decode=16.0ms
SELECT p0."id", p0."title", p0."body", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 []
[%A.Posts{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "ボディ1",
  id: 1, inserted_at: ~N[2017-06-10 11:43:12.560000], title: "タイトル1",
  updated_at: ~N[2017-06-10 11:43:12.572000]},
 %A.Posts{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "ボディ2",
  id: 2, inserted_at: ~N[2017-06-10 11:43:22.931000], title: "タイトル2",
  updated_at: ~N[2017-06-10 11:43:22.931000]}]
```

それでは、Ectoで取得したデータ全件を、MapList.select()して、title列をリストアップしてみよう

```elixir:lib/db_sample.ex
defmodule DbSample do
　…
	def ecto_select() do
		DbSample.Posts
		|> DbSample.Repo.all()
		|> MapList.select( :title )
	end
　…
```

実行すると、title全件がリストアップされます :muscle_tone1:

```elixir
iex> recompile()
iex> DbSample.ecto_select()
[debug] QUERY OK source="posts" db=32.0ms
SELECT p0."id", p0."title", p0."body", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 []
["タイトル1", "タイトル2"]
```

ちなみに、Ectoによるall()以外の操作は、[Ecto.RepoのHex](https://hexdocs.pm/ecto/Ecto.Repo.html)か、[こちらのQiita記事](http://qiita.com/techno-tanoC/items/c703a5a90e4133fbea82)や[また別のQiita記事](http://qiita.com/su_mi/items/10b1c40e9c1f97f72a7f#_reference-f20b66d07eca0bc0ec27)が参考になります

# ③ SQLで取得したDBデータの特定列をリストアップする

最後に、SQLで取得したDBデータの特定列をリストアップしてみましょう

「・・・ん？ それって、select時の列指定で良いのでは．．．」というツッコミは一旦無で :sweat_smile:

まず、SQLでデータ取得できることを確認する

```elixir
iex> Ecto.Adapters.SQL.query( DbSample.Repo, "select * from posts", [] )
[debug] QUERY OK db=0.0ms
select * from posts []
{:ok,
 %Postgrex.Result{columns: ["id", "title", "body", "inserted_at", "updated_at"],
  command: :select, connection_id: 2796, num_rows: 2,
  rows: [[1, "タイトル1", "ボディ1",
    {{2017, 6, 10}, {11, 43, 12, 560000}},
    {{2017, 6, 10}, {11, 43, 12, 572000}}],
   [2, "タイトル2", "ボディ2", {{2017, 6, 10}, {11, 43, 22, 931000}},
    {{2017, 6, 10}, {11, 43, 22, 931000}}]]}}
```

SQLのデバッグ表示の後に、:okと取得結果のタプルがあり、取得結果内は、「columns」に列名リスト、「rows」にデータリストのリスト、という構成なので、「columns」と「rows」をバインドしてマップ化するためのヘルパー関数を作ります

```elixir:lib/tiny/map_list.ex
defmodule MapList do
　…
	def map_column( columns, rows ) do
		rows
		|> Enum.map( fn( row ) -> Enum.into( List.zip( [ columns, row ] ), %{} ) end )
	end
　…
```

Enum.zip()による、2つのリストのバインディング、ホント便利 :fish_cake:

これでマップのリストとして扱えるので、MapList.select()して、title列をリストアップしてみる

```elixir:lib/db_sample.ex
defmodule DbSample do
　…
	def sql_select() do
		case Ecto.Adapters.SQL.query( DbSample.Repo, "select * from posts", [] ) do
			{ :ok, result } -> MapList.map_column( result.columns, result.rows )
		end
		|> MapList.select( "title" )
	end
　…
```

実行すると、これまたtitle全件がリストアップされます :sunrise:

```elixir
iex> recompile()
iex> DbSample.sql_select()
[debug] QUERY OK db=31.0ms
select * from posts []
["タイトル1", "タイトル2"]
```

こんな感じで、JSONだけで無く、CSVでもDBでも、MapList.select()で、お手軽にデータ抽出できました :tada:

# Enum.map()をスラスラ書ける人が育ってきたら

以下のような感じで、MapList.select()も、再帰処理も、不要になります

```elixir
iex> DbSample.Posts |> DbSample.Repo.all() |> Enum.map( fn( %{ title: title } ) -> title end )
[debug] QUERY OK source="posts" db=0.0ms
SELECT p0."id", p0."title", p0."body", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 []
["タイトル1", "タイトル2"]
```

```elixir
iex> DbSample.Posts |> DbSample.Repo.all() |> Enum.map( fn( %{ title: title, body: body } ) -> { title, body } end )
[debug] QUERY OK source="posts" db=0.0ms
SELECT p0."id", p0."title", p0."body", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 []
["タイトル1,ボディ1", "タイトル2,ボディ2"]
```

Enum.map()とパターンマッチの組み合わせ、強力過ぎ :race_car:

---
# p.s.

福岡でElixirをワイワイ盛り上げるコミュニティの発足記念MeetUp、[fukuoka.ex#1](https://techjin.connpass.com/event/56497/)、おかげさまで、かなり盛り上がりました :angel:

Exlixir／Phoenixのプロダクション採用に興味強い方が多く、また関数型言語に抵抗ある方も「Elixirは、なんかはじめられそう」とのことなので、本当にこれからが楽しみ！ :gift:

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/c3969ff0-bf75-1ce9-5e74-12acae8dab17.png)](https://techjin.connpass.com/event/56497/)

---