[翻訳] Ectoを使った組み立て可能なクエリ

Drew Olsonさんの2015年1月23日付のブログ記事[**Composable Queries with Ecto**](http://blog.drewolson.org/composable-queries-ecto/)の翻訳です。
**Ecto**はRuby and Railsだと**Active Record**に相当するのでしょうか、データベースの抽象化機構です。
**Phoenix**にも欠かせない機能なんですがあまり紹介記事がなさそうなので訳してみました。
そういえば*ecto*っていうのは「外の…」という意味の接頭語で(ex-なんかと同じ)～魂？が口から「外に出る」エクトプラズム(ectoplasm)なんてのがありましたね(笑)～データベースに直接タッチせず外から触るからついた名前かもしれません。

(どうでもいい追記) ゴーストバスターズの「作業車」の名前が**Ecto-1**でした。
![41iqdof9XHL.jpg](https://qiita-image-store.s3.amazonaws.com/0/46294/957353e0-d7e4-5c40-cb26-db41d0c65eb1.jpeg)


------

[以前の投稿](http://blog.drewolson.org/blog/2015/01/19/building-an-elixir-web-app/)で(ある種ホンモノの)Webアプリを**Elixir**で作成して[^1]今までに私が習ってきたレッスンのいくつかについて簡潔に述べました。今日は[**Ecto**](https://github.com/elixir-lang/ecto)を使った組み立て可能なクエリについて詳細を見てみようと思います。まずはEctoの簡単な紹介から。

[^1]: 前回、作ったものは簡単なブログサイトを実現するアプリのひな形でした。

## **Ecto**とは何か ##

**Ecto**[^2] は軽量ORMと考えられます。**Ecto**は**Elixir**のstructを使ってデータベースのテーブルを表現し、データベースのクエリの構築と実行のためのDSLを提供します。つまらない著者なもんで申し訳ないんですが、使い古された「たくさんのコメントを持つ投稿」という例を**Ecto**の能力を示すために使うことにしましょう。次のようなモデルを仮定します。

[^2]: デフォルトの状態でEctoを使うとデータベースとしてはPostgresqlが使われます。現時点で設定すればMySQL(もうMariaDBというべきか)とSqlite3も使えます。


```ex
defmodule MyApp.Post do  
  use Ecto.Model
  import Ecto.Query

  schema "posts" do
    field :body, :string
    field :published, :boolean
    field :published_at, :datetime
    field :title, :string

    has_many :comments, MyApp.Comment
  end
end  
```


```ex
defmodule MyApp.Comment do  
  use Ecto.Model
  import Ecto.Query

  schema "comments" do
    field :commenter, :string
    field :title, :string
    field :votes, :integer

    belongs_to :post, MyApp.Post
  end
end  
```

## **Ecto**でクエリを ##

**Ecto**は２通りのクエリシンタックスを提供します：キーワードクエリシンタックスとクエリ式です。どちらのクエリスタイルでもクエリの構成はクエリの実行とは分離されたプロセスであることに注意してください。それぞれのスタイルで構成されたクエリはアプリケーションの`Repo`に渡されて実行されます。

### キーワードクエリシンタックス ###

キーワードクエリシンタックスはSQLを密接に反映した、**LINQ**を彷彿させるスタイルです。いくつかの例題クエリで説明しましょう。

#### すべての投稿を選択 ####

```ex
MyApp.Repo.all(  
  from p in MyApp.Post,
  select: p
)
```

#### 全ての公開済みの投稿を選択 ####

```ex
MyApp.Repo.all(  
  from p in MyApp.Post,
   where: p.published == true,
  select: p
)
```

#### 1番めの投稿の全てのコメントを選択 ####

```ex
MyApp.Repo.all(  
  from c in MyApp.Comment,
    join: p in assoc(c, :post),
   where: p.id == 1,
  select: c
)
```

### クエリ式 ###

クエリ式は**Elixir**のAPIでよく見られるパイプラインのコンセプトに従っています。これらのクエリ全てはモデルモジュール(例えば`MyApp.Post`)から始まっているという点は重要です。モデルそのものがクエリになれるオブジェクトであり与えられたテーブルの全アイテムを表現しています。

#### すべての投稿を選択 ####

```ex
MyApp.Post |> MyApp.Repo.all 
```

#### 全ての公開済みの投稿を選択 ####

```ex
MyApp.Post  
|> where([p], p.published == true)
|> MyApp.Repo.all
```

#### 1番めの投稿の全てのコメントを選択 ####

```ex
MyApp.Comment  
|> join(:left, [c], p in assoc(c, :post))
|> where([_, p], p.id == 1)
|> select([c, _], c)
|> MyApp.Repo.all
```

### クエリの組み立て ###

クエリ式スタイルでどのようにクエリを組み立てるかについては見ればわかりますね…新しい制約をパイプラインに追加するだけです。キーワードクエリシンタックスで作られたクエリや異なるスタイルのクエリからクエリを組み立てるやり方はそれほど明白ではありません。

まず、キーワードクエリシンタックスの重要な仕様について理解する必要があります。`from`節で`in`以降のトークンにはクエリ可能オブジェクトなら何でも置けて、かつ、他のクエリも「クエリ可能オブジェクト」なのです！ 例を示します。

```ex
query  = from p in MyApp.Post,  
         select: p

query2 = from p in query,  
         where: p.published == true

MyApp.Repo.all(query2)  
```

これを踏まえると、２つの文法スタイルを混ぜてくっつけることができます。

```ex
query  = from p in MyApp.Post,  
         select: p

query |> where([p], p.published == true) |> MyApp.Repo.all  
```

## ぜんぶまとめると ##

では我々の**Ecto**モデルにいい感じの、中身がわかるような名前の関数をいくつか追加しましょう。

```ex
defmodule MyApp.Post do  
  use Ecto.Model
  import Ecto.Query

  schema "posts" do
    field :body, :string
    field :published, :boolean
    field :published_at, :datetime
    field :title, :string

    has_many :comments, MyApp.Comment
  end

  def published(query) do
    from p in query,
    where: p.published == true
  end

  def sorted(query) do
    from p in query,
    order_by: [desc: p.published_at]
  end
end  
```

```ex
defmodule MyApp.Comment do  
  use Ecto.Model
  import Ecto.Query

  schema "comments" do
    field :commenter, :string
    field :title, :string
    field :votes, :integer

    belongs_to :post, MyApp.Post
  end

  def for_post(query, post) do
    from c in query,
     join: p in assoc(c, :post)
    where: p.id == ^post.id
  end

  def popular(query) do
    query |> where([c], c.votes > 10)
  end
end  
```

クエリの組み立てにおいてクエリスタイルがお互いに交換可能であることを示すために両方のクエリスタイルを使っています。では追加した関数でいくつかクエリを作ってみましょう。通常、私はこのタイプのクエリの組み立てを[**Phoenix**](http://phoenixframework.org/)で使います。

```ex
alias MyApp.Post  # 訳注： alias で引数なしだと以下PostがMyApp.Postと同じ意味になります。次も同様。
alias MyApp.Comment

published_posts = Post  # (訳注: 公開された投稿)
|> Post.published
|> MyApp.Repo.all

last_post = Post  # (訳注：最後の投稿)
|> Post.published
|> Post.sorted
|> MyApp.Repo.one

recent_popular_comments = Comment  # (訳注：最新の人気のあるコメント)
|> Comment.for_post(last_post)
|> Comment.popular
|> MyApp.Repo.all
```

## まとめ ##

この記事を通じてソースから取り出して再利用可能なクエリコンポーネントとそれを組み立ててもっと複雑なクエリを作れる**Ecto**のパワーと柔軟性について何らかの見識を持っていただければ幸いです。これらのテクニックを使うとテストを支援する際にも重複や複雑さを減らすことができますよ。