Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

(どうでもいい追記) ゴーストバスターズの「作業車」の名前がEcto-1でした。
41iqdof9XHL.jpg


以前の投稿で(ある種ホンモノの)WebアプリをElixirで作成して1今までに私が習ってきたレッスンのいくつかについて簡潔に述べました。今日はEctoを使った組み立て可能なクエリについて詳細を見てみようと思います。まずはEctoの簡単な紹介から。

Ectoとは何か

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

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

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

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

すべての投稿を選択

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

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

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

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

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

クエリ式

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

すべての投稿を選択

MyApp.Post |> MyApp.Repo.all 

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

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

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

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

クエリの組み立て

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

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

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

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

MyApp.Repo.all(query2)  

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

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

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

ぜんぶまとめると

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

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  
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で使います。

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のパワーと柔軟性について何らかの見識を持っていただければ幸いです。これらのテクニックを使うとテストを支援する際にも重複や複雑さを減らすことができますよ。


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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした