Skip to content

Commit

Permalink
feat: add force_require/2 and support loaders with arity 2
Browse files Browse the repository at this point in the history
  • Loading branch information
fuelen committed Nov 19, 2021
1 parent 2c37bb4 commit d36c3c3
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 16 deletions.
75 changes: 61 additions & 14 deletions lib/composite.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Composite do
It allows getting rid of some boilerplate when building a query based on input parameters.
params = %{query_string: "John Doe"}
params = %{search_query: "John Doe"}
User
|> where(active: true)
Expand All @@ -21,7 +21,12 @@ defmodule Composite do
You're able to use Composite with any Elixir term, as it is just an advanced wrapper around `Enum.reduce/3`.
"""
import Kernel, except: [apply: 3]
defstruct param_definitions: [], dep_definitions: %{}, params: nil, input_query: nil

defstruct param_definitions: [],
dep_definitions: %{},
params: nil,
input_query: nil,
required_deps: []

@type dependency_name :: atom()
@type dependencies ::
Expand All @@ -36,14 +41,15 @@ defmodule Composite do
@type dependency_option :: {:requires, dependencies()}
@type param_path_item :: any()
@type apply_fun(query) :: (query, value :: any() -> query) | (query -> query)
@type load_dependency(query) :: (query -> query)
@type load_dependency(query) :: (query -> query) | (query, params() -> query)
@type params :: Access.t()
@type t(query) :: %__MODULE__{
param_definitions: [{[param_path_item()], apply_fun(query), [param_option(query)]}],
dep_definitions: %{
optional(dependency_name()) => [{load_dependency(query), [dependency_option()]}]
},
params: params(),
required_deps: [dependency_name()],
params: params() | nil,
input_query: query
}

Expand Down Expand Up @@ -176,7 +182,7 @@ defmodule Composite do
|> Composite.param(
:search,
fn
query, "+" <> _ = phone_number -> where(query, [phones: phones] phones.number == ^phone_number)
query, "+" <> _ = phone_number -> where(query, [phones: phones], phones.number == ^phone_number)
query, query_string -> where(query, [records], ilike(records.text, ^query_string))
end,
requires: fn
Expand All @@ -186,6 +192,8 @@ defmodule Composite do
)
|> Composite.dependency(:phone, &join(&1, :inner, [records], phones in assoc(records, :phone), as: :phones))
When `loader` function has arity 2, then all parameters are passed in the second argument.
### Options
* `:requires` - allows to set dependencies for current dependency.
Expand All @@ -199,7 +207,7 @@ defmodule Composite do
func,
opts \\ []
)
when is_function(func, 1) do
when is_function(func, 1) or is_function(func, 2) do
ensure_unknown_opts_absent!(opts, [:requires])
%{composite | dep_definitions: Map.put(dep_definitions, dependency, {func, opts})}
end
Expand Down Expand Up @@ -233,11 +241,19 @@ defmodule Composite do
|> set_once!(:input_query, input_query)
|> set_once!(:params, params)

{query, loaded_deps} =
load_dependencies(
composite.input_query,
composite.params,
composite.dep_definitions,
MapSet.new(),
composite.required_deps
)

{query, _loaded_deps} =
composite.param_definitions
|> Enum.reverse()
|> Enum.reduce({composite.input_query, MapSet.new()}, fn {path, func, opts},
{query, loaded_deps} ->
|> Enum.reduce({query, loaded_deps}, fn {path, func, opts}, {query, loaded_deps} ->
value = get_in(composite.params, path)

ignore? = Keyword.get(opts, :ignore?, &empty_value?/1)
Expand All @@ -252,7 +268,13 @@ defmodule Composite do
required_deps = opts |> Keyword.get(:ignore_requires) |> List.wrap()

{query, loaded_deps} =
load_dependencies(query, composite.dep_definitions, loaded_deps, required_deps)
load_dependencies(
query,
composite.params,
composite.dep_definitions,
loaded_deps,
required_deps
)

{on_ignore.(query), loaded_deps}
else
Expand All @@ -266,7 +288,13 @@ defmodule Composite do
|> List.wrap()

{query, loaded_deps} =
load_dependencies(query, composite.dep_definitions, loaded_deps, required_deps)
load_dependencies(
query,
composite.params,
composite.dep_definitions,
loaded_deps,
required_deps
)

case func do
func when is_function(func, 1) -> {func.(query), loaded_deps}
Expand All @@ -278,6 +306,19 @@ defmodule Composite do
query
end

@doc """
Forces loading dependency even if it is not required by `params`.
"""
@spec force_require(t(query), dependency_name() | [dependency_name()]) :: t(query)
when query: any()
def force_require(
%__MODULE__{required_deps: required_deps} = composite,
dependency_or_dependencies
) do
dependencies = List.wrap(dependency_or_dependencies)
%__MODULE__{composite | required_deps: dependencies ++ required_deps}
end

defp empty_value?(value) do
value in [nil, "", [], %{}]
end
Expand Down Expand Up @@ -306,11 +347,11 @@ defmodule Composite do
end
end

defp load_dependencies(query, _deps_definitions, loaded_deps, [] = _required_deps) do
defp load_dependencies(query, _params, _deps_definitions, loaded_deps, [] = _required_deps) do
{query, loaded_deps}
end

defp load_dependencies(query, deps_definitions, loaded_deps, required_deps) do
defp load_dependencies(query, params, deps_definitions, loaded_deps, required_deps) do
deps_to_load = required_deps |> MapSet.new() |> MapSet.difference(loaded_deps)

{query, loaded_deps} =
Expand All @@ -327,9 +368,15 @@ defmodule Composite do
required_deps = opts |> Keyword.get(:requires) |> List.wrap()

{query, loaded_deps} =
query |> load_dependencies(deps_definitions, loaded_deps, required_deps)
load_dependencies(query, params, deps_definitions, loaded_deps, required_deps)

query =
case loader do
loader when is_function(loader, 1) -> loader.(query)
loader when is_function(loader, 2) -> loader.(query, params)
end

{loader.(query), loaded_deps}
{query, loaded_deps}
end)

{query, MapSet.union(loaded_deps, deps_to_load)}
Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Composite.MixProject do
use Mix.Project

@version "0.3.0"
@version "0.4.0"
def project do
[
app: :composite,
Expand All @@ -24,7 +24,7 @@ defmodule Composite.MixProject do
def package do
[
description: "A utility for writing dynamic queries.",
licenses: ["Apache 2"],
licenses: ["Apache-2.0"],
links: %{
GitHub: "https://github.com/fuelen/composite"
}
Expand Down

0 comments on commit d36c3c3

Please sign in to comment.