Skip to content

Commit

Permalink
docs: add basic description and code examples
Browse files Browse the repository at this point in the history
  • Loading branch information
fuelen committed Dec 17, 2020
1 parent e6748e0 commit 1cd3f44
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
91 changes: 91 additions & 0 deletions lib/composite.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule Composite do
@moduledoc """
A utility for writing composable queries.
"""
import Kernel, except: [apply: 3]
defstruct param_definitions: [], dep_definitions: %{}, params: nil, input_query: nil
@type dependency_name :: atom()
Expand All @@ -23,14 +26,74 @@ defmodule Composite do
input_query: query
}

@doc """
Initializes a `Composite` struct with delayed application of `query` and `params`.
Must be used with `apply/3`.
composite =
Composite.new()
|> Composite.param(:organization_id, &where(&1, organization_id: ^&2))
|> Composite.param(:age_more_than, &where(&1, [users], users.age > ^&2))
params = %{organization_id: 1}
User
|> where(active: true)
|> Composite.apply(composite, params)
|> Repo.all()
"""
@spec new :: t(any())
def new, do: %__MODULE__{}

@doc """
Initializes a `Composite` struct with `query` and `params`.
Must be used with `apply/1`.
This strategy is useful when working with `Ecto.Query` in pipe-based queries.
params = %{organization_id: 1}
User
|> where(active: true)
|> Composite.new(params)
|> Composite.param(:organization_id, &where(&1, organization_id: ^&2))
|> Composite.param(:age_more_than, &where(&1, [users], users.age > ^&2))
|> Repo.all()
Please note, that there is no explicit `Composite.apply/1` call before `Repo.all/1`, because `Composite`
implements `Ecto.Queryable` protocol.
"""
@spec new(query, params()) :: t(query) when query: any()
def new(input_query, params) do
%__MODULE__{params: params, input_query: input_query}
end

@doc """
Defines a parameter handler.
Handler is applied to a query when `apply/1` or `apply/3` is invoked.
All handlers are invoke in the same order as they are defined.
If the parameter requires dependencies, then they will be loaded before the parameters' handler and only if
parameter wasn't ignored. Examples with dependencies usage can be found in doc for `dependency/4`
User
|> Composite.new(%{location: "Arctic", order: :age_desc})
|> Composite.param(:location, &where(&1, location: ^&2),
ignore?: &(&1 in [nil, "WORLDWIDE", ""])
)
|> Composite.param(
:order,
fn
query, :name_asc -> query |> order_by(name: :asc)
query, :age_desc -> query |> order_by(age: :desc)
end,
on_ignore: &order_by(&1, inserted_at: :desc)
)
"""
@spec param(t(query), param_path_item() | [param_path_item()], apply_param(query), [
param_option(query)
]) :: t(query)
Expand All @@ -45,6 +108,24 @@ defmodule Composite do
%{composite | param_definitions: [{List.wrap(path), func, opts} | param_definitions]}
end

@doc """
Defines a dependency loader.
Dependency is an instruction which is being applied lazily to a query.
The same dependency can be required by many parameters, but it will be invoked only once.
Dependency can depend on other dependency.
Useful for joining tables via SQL.
User
|> Composite.new(%{org_type: :nonprofit, is_org_closed: false})
|> Composite.param(:is_org_closed, &where(&1, [orgs: orgs], orgs.closed == ^&2), requires: :orgs)
|> Composite.param(:org_type, &where(&1, [orgs: orgs], orgs.type == ^&2), requires: :orgs)
|> Composite.dependency(
:orgs,
&join(&1, :inner, [users], orgs in assoc(users, :org), as: :orgs)
)
"""
@spec dependency(t(query), dependency_name(), load_dependency(query), [dependency_option]) ::
t(query)
when query: any()
Expand All @@ -58,11 +139,21 @@ defmodule Composite do
%{composite | dep_definitions: Map.put(dep_definitions, dependency, {func, opts})}
end

@doc """
Applies handlers to query.
Used when composite is defined with `new/2`
"""
@spec apply(t(query)) :: query when query: any()
def apply(%__MODULE__{} = composite) do
apply(nil, composite, nil)
end

@doc """
Applies handlers to query.
Used when composite is defined with `new/0`
"""
@spec apply(query, t(query), params()) :: query when query: any()
def apply(
input_query,
Expand Down
14 changes: 12 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
defmodule Composite.MixProject do
use Mix.Project

@version "0.2.1"
def project do
[
app: :composite,
version: "0.2.0",
version: @version,
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps(),
dialyzer: [plt_add_apps: [:ecto]],
package: package()
package: package(),
docs: docs()
]
end

Expand All @@ -36,4 +38,12 @@ defmodule Composite.MixProject do
{:ex_doc, "~> 0.22", only: :dev, runtime: false}
]
end

defp docs do
[
deps: [ecto: "https://hexdocs.pm/ecto/"],
source_url: "https://github.com/fuelen/composite",
source_ref: "v#{@version}"
]
end
end

0 comments on commit 1cd3f44

Please sign in to comment.