Top Banner
Composable Queries with Ecto Drew Olson @drewolson
41

Composable Queries with Ecto

Apr 11, 2017

Download

Technology

drewolson
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Composable Queries with Ecto

Composable Queries with Ecto Drew Olson

@drewolson

Page 2: Composable Queries with Ecto

* Brief Ecto Intro * Query Expressions * Composition * Query Pipelines

Page 3: Composable Queries with Ecto

Please ask questions

Page 4: Composable Queries with Ecto

Intro

Page 5: Composable Queries with Ecto

Patterns from real applications

Page 6: Composable Queries with Ecto

A few models for examples

Page 7: Composable Queries with Ecto

Intro - Models

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

  schema "posts" do    field :body, :string    field :published, :boolean    field :published_at, Ecto.Date    field :title, :string

    has_many :comments, MyApp.Comment  endend

Page 8: Composable Queries with Ecto

Intro - Models

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

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

    belongs_to :post, MyApp.Post  endend

Page 9: Composable Queries with Ecto

Keyword query syntax

Page 10: Composable Queries with Ecto

Intro - Baby’s First Query

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

Page 11: Composable Queries with Ecto

This is how I started writing Ecto queries

Intro

Page 12: Composable Queries with Ecto

Separate construction and execution

Intro

Page 13: Composable Queries with Ecto

Intro - First Query Deconstructed

query = from p in MyApp.Post,        where: p.published == true

MyApp.Repo.all(query)

Page 14: Composable Queries with Ecto

Intro - Fancy Query

query = from c in MyApp.Comment,        join: p in assoc(c, :post),        where: p.id == 1 and c.votes > 5        select: c

comments = MyApp.Repo.all(query)

Page 15: Composable Queries with Ecto

Query Expressions

Page 16: Composable Queries with Ecto

Query Expressions - Translation

query = from p in MyApp.Post,        where: p.published == true

query = where(MyApp.Post, [p], p.published == true)

Page 17: Composable Queries with Ecto

Query Expressions - Fancy

query = from c in MyApp.Comment,        join: p in assoc(c, :post),        where: p.id == 1 and c.votes > 5        select: c

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

Page 18: Composable Queries with Ecto

Composition

Page 19: Composable Queries with Ecto

Both query styles are composable.

Queries can be the “subject” of new queries.

Composition

Page 20: Composable Queries with Ecto

Composition

query1 = MyApp.Comment

query2 = from c in query1,         where: c.votes > 5

query3 = from c in query2,         join: p in assoc(c, :post),         where: p.id == 1,         select: c

MyApp.Repo.all(query3)

Page 21: Composable Queries with Ecto

Composition

query1 = MyApp.Comment

query2 = query1|> where([c], c.votes > 5)

query3 = query2|> join(:left, [c], p in assoc(c, :post))|> where([_, p], p.id == 1)|> select([c, _], c)

MyApp.Repo.all(query3)

Page 22: Composable Queries with Ecto

We can now extract reusable components, name them and compose them.

Composition

Page 23: Composable Queries with Ecto

Compositiondefmodule MyApp.Comment do  ...

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

  def popular(query) do    from c in query,    where: c.votes > 5  endend

alias MyApp.Comment

Comment|> Comment.popular|> Comment.for_post(1)|> MyApp.Repo.all

Page 24: Composable Queries with Ecto

Composition - Reads so nice :)

Comment|> Comment.popular|> Comment.for_post(1)|> MyApp.Repo.all

Page 25: Composable Queries with Ecto

Query Pipelines

Page 26: Composable Queries with Ecto

Query Pipelines

Comment|> Comment.popular|> Comment.for_post(1)|> MyApp.Repo.all

Page 27: Composable Queries with Ecto

Query Pipelines

Comment|> Comment.popular|> Comment.for_post(1)|> MyApp.Repo.all

<- source<- transformation

<- transformation<- sink

Page 28: Composable Queries with Ecto

Query Pipelines - A note

Welcome to my typespecs, where the types are all made up and the points don’t matter.

Page 29: Composable Queries with Ecto

Query Pipelines - Source

A source is the starting point for a query.

@spec source() :: Query.t

Page 30: Composable Queries with Ecto

Query Pipelines - Source Examples

MyApp.Post

MyApp.Post.owned_by(user)

Page 31: Composable Queries with Ecto

Query Pipelines - Transformation

A transformation expands or constrains an existing query.

@spec transformation(Query.t) :: Query.t

Page 32: Composable Queries with Ecto

Query Pipelines - Transformation Examples

MyApp.Post.published(query)

MyApp.Comment.for_post(query, post)

Page 33: Composable Queries with Ecto

Query Pipelines - Sink

A sink executes a query and returns a result.

@spec sink(Query.t) :: Result.t

Page 34: Composable Queries with Ecto

Query Pipelines - Sink Examples

MyApp.Repo.all(query)

MyApp.Repo.one(query)

MyApp.Repo.paginate(query)

Page 35: Composable Queries with Ecto

Query Pipelines

Comment|> Comment.popular|> Comment.for_post(1)|> MyApp.Repo.all

Page 36: Composable Queries with Ecto

Query Pipelines

Pipelines are fractal

Page 37: Composable Queries with Ecto

Query Pipelines - Fractal

MyApp.Repo.paginate(query)

Page 38: Composable Queries with Ecto

Query Pipelines

Pagination acts as a sink, but is really several smaller pipelines

Page 39: Composable Queries with Ecto

defmodule MyApp.Repo do  def paginate(query, page_number \\ 1) do    {entries(query, page_number), total_entries(query)}  end

  defp entries(query, page_number) do    page_size = 10    offset = page_size * (page_number - 1)

    query    |> limit([_], ^page_size)    |> offset([_], ^offset)    |> all  end

  defp total_entries(query) do    query    |> exclude(:order_by)    |> exclude(:preload)    |> exclude(:select)    |> select([_], count("*"))    |> one  endend

Page 40: Composable Queries with Ecto

See Also

* blog.drewolson.org * hex.pm/packages/scrivener

Page 41: Composable Queries with Ecto

Fin

Thanks. Questions?

@drewolson