json_api_query_builder v1.0.1 JsonApiQueryBuilder behaviour View Source

Behaviour and mixin for building an Ecto query from a JSON-API request.

Example

defmodule Article do
  use Ecto.Schema

  schema "articles" do
    field :body, :string
    field :description, :string
    field :slug, :string
    field :tag_list, {:array, :string}
    field :title, :string
    belongs_to :author, User, foreign_key: :user_id
    has_many :comments, Comment
    timestamps()
  end

  defmodule Query do
    use JsonApiQueryBuilder,
      schema: Article,
      type: "article",
      relationships: ["author", "comments"]

    @impl JsonApiQueryBuilder
    def filter(query, "tag", value), do: from(a in query, where: ^value in a.tag_list)
    def filter(query, "comments", params) do
      comment_query = from(Comment, select: [:article_id], distinct: true) |> Comment.Query.filter(params)
      from a in query, join: c in ^subquery(comment_query), on: a.id == c.article_id
    end
    def filter(query, "author", params) do
      user_query = from(User, select: [:id]) |> User.Query.filter(params)
      from a in query, join: u in ^subquery(user_query), on: a.user_id == u.id
    end

    @impl JsonApiQueryBuilder
    def include(query, "comments", comment_params) do
      from query, preload: [comments: ^Comment.Query.build(comment_params)]
    end
    def include(query, "author", author_params) do
      from query, select_merge: [:author_id], preload: [author: ^User.Query.build(author_params)]
    end
  end
end

Link to this section Summary

Types

A JSON-API request after parsing by Plug into a string keyed map

Callbacks

Builds an Ecto.Queryable.t from parsed JSON-API request parameters

Optional callback responsible for mapping a JSON-API field string to an Ecto schema field

Applies sparse fieldset selection from a parsed JSON-API request to an Ecto.Queryable.t

Applies filter conditions from a parsed JSON-API request to an Ecto.Queryable.t

Callback responsible for adding a filter criteria to a query

Applies related resource inclusion from a parsed JSON-API request to an Ecto.Queryable.t as preloads

Callback responsible for adding an included resource via preload

Applies sorting from a parsed JSON-API request to an Ecto.Queryable.t

Link to this section Types

Link to this type request() View Source
request() :: %{optional(String.t()) => any()}

A JSON-API request after parsing by Plug into a string keyed map.

May contain "filter", "sort", "fields", "include", "page" keys.

Link to this section Callbacks

Builds an Ecto.Queryable.t from parsed JSON-API request parameters.

An overridable default implementation is generated by the mixin.

Example:

User.Query.build(%{
  "filter" => %{
    "articles.tag" => "animals",
    "comments" => %{
      "body" => "Boo"
    }
  },
  "include" => "articles.comments",
  "fields" => %{"user" => "id,bio"}
})

#Ecto.Query<
  from u in Blog.User,
  join: a in ^#Ecto.Query<
    from a in subquery(
      from a in Blog.Article,
      where: ^"animals" in a.tag_list,
      distinct: true,
      select: [:user_id]
    )
  >,
  on: u.id == a.user_id,
  select: [:id, :bio, :id],
  preload: [
    articles: #Ecto.Query<
      from a in Blog.Article,
      select: [:id, :body, :description, :slug, :tag_list, :title, :user_id, :inserted_at, :updated_at],
      preload: [
        comments: #Ecto.Query<
          from c in Blog.Comment,
          select: [:id, :body, :user_id, :article_id, :inserted_at, :updated_at]
        >
      ]
    >
  ]
>
Link to this callback field(api_field) View Source
field(api_field :: String.t()) :: atom()

Optional callback responsible for mapping a JSON-API field string to an Ecto schema field.

An overridable default implementation using String.to_existing_atom/1 is generated by the mixin.

Example

@impl JsonApiQueryBuilder
def field("username"), do: :name
def field("price"), do: :unit_price
def field(other), do: String.to_existing_atom(other)
Link to this callback fields(query, request) View Source
fields(query :: Ecto.Queryable.t(), request()) :: Ecto.Queryable.t()

Applies sparse fieldset selection from a parsed JSON-API request to an Ecto.Queryable.t

An overridable default implementation is generated by the mixin. By default all fields are selected unless specified in the "fields" key of the request.

Link to this callback filter(query, request) View Source
filter(query :: Ecto.Queryable.t(), request()) :: Ecto.Queryable.t()

Applies filter conditions from a parsed JSON-API request to an Ecto.Queryable.t

An overridable default implementation is generated by the mixin.

Link to this callback filter(query, field, value) View Source
filter(query :: Ecto.Queryable.t(), field :: String.t(), value :: any()) :: Ecto.Queryable.t()

Callback responsible for adding a filter criteria to a query.

Attribute filters will generally add a where: condition to the query.

Relationship filters will generally add a join: based on a subquery.

When applying a filter to a has-many relationship, take care to select: the foreign key with distinct: true to avoid duplicated results. For filtering a belongs-to relationships, selecting the primary key is all that is needed.

Example

@impl JsonApiQueryBuilder
def filter(query, "tag", value), do: from(article in query, where: ^value in article.tag_list)
def filter(query, "comments", params) do
  comment_query = from(Comment, select: [:article_id], distinct: true) |> Comment.Query.filter(params)
  from article in query, join: comment in ^subquery(comment_query), on: article.id == comment.article_id
end
def filter(query, "author", params) do
  user_query = from(User, select: [:id]) |> User.Query.filter(params)
  from article in query, join: user in ^subquery(user_query), on: article.user_id == user.id
end
Link to this callback include(query, request) View Source
include(query :: Ecto.Queryable.t(), request()) :: Ecto.Queryable.t()

Applies related resource inclusion from a parsed JSON-API request to an Ecto.Queryable.t as preloads.

An overridable default implementation is generated by the mixin.

Link to this callback include(query, relationship, related_request) View Source
include(query :: Ecto.Queryable.t(), relationship :: String.t(), related_request :: request()) :: Ecto.Queryable.t()

Callback responsible for adding an included resource via preload.

Any required foreign keys should be added to the query using select_merge: as required by the preload.

Example

@impl JsonApiQueryBuilder
def include(query, "comments", comment_params) do
  from query, preload: [comments: ^Comment.Query.build(comment_params)]
end
def include(query, "author", author_params) do
  from query, select_merge: [:user_id], preload: [author: ^User.Query.build(author_params)]
end

Applies sorting from a parsed JSON-API request to an Ecto.Queryable.t

An overridable default implementation is generated by the mixin.