Skip to content

Commit d36c3c3

Browse files
committed
feat: add force_require/2 and support loaders with arity 2
1 parent 2c37bb4 commit d36c3c3

2 files changed

Lines changed: 63 additions & 16 deletions

File tree

lib/composite.ex

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Composite do
44
55
It allows getting rid of some boilerplate when building a query based on input parameters.
66
7-
params = %{query_string: "John Doe"}
7+
params = %{search_query: "John Doe"}
88
99
User
1010
|> where(active: true)
@@ -21,7 +21,12 @@ defmodule Composite do
2121
You're able to use Composite with any Elixir term, as it is just an advanced wrapper around `Enum.reduce/3`.
2222
"""
2323
import Kernel, except: [apply: 3]
24-
defstruct param_definitions: [], dep_definitions: %{}, params: nil, input_query: nil
24+
25+
defstruct param_definitions: [],
26+
dep_definitions: %{},
27+
params: nil,
28+
input_query: nil,
29+
required_deps: []
2530

2631
@type dependency_name :: atom()
2732
@type dependencies ::
@@ -36,14 +41,15 @@ defmodule Composite do
3641
@type dependency_option :: {:requires, dependencies()}
3742
@type param_path_item :: any()
3843
@type apply_fun(query) :: (query, value :: any() -> query) | (query -> query)
39-
@type load_dependency(query) :: (query -> query)
44+
@type load_dependency(query) :: (query -> query) | (query, params() -> query)
4045
@type params :: Access.t()
4146
@type t(query) :: %__MODULE__{
4247
param_definitions: [{[param_path_item()], apply_fun(query), [param_option(query)]}],
4348
dep_definitions: %{
4449
optional(dependency_name()) => [{load_dependency(query), [dependency_option()]}]
4550
},
46-
params: params(),
51+
required_deps: [dependency_name()],
52+
params: params() | nil,
4753
input_query: query
4854
}
4955

@@ -176,7 +182,7 @@ defmodule Composite do
176182
|> Composite.param(
177183
:search,
178184
fn
179-
query, "+" <> _ = phone_number -> where(query, [phones: phones] phones.number == ^phone_number)
185+
query, "+" <> _ = phone_number -> where(query, [phones: phones], phones.number == ^phone_number)
180186
query, query_string -> where(query, [records], ilike(records.text, ^query_string))
181187
end,
182188
requires: fn
@@ -186,6 +192,8 @@ defmodule Composite do
186192
)
187193
|> Composite.dependency(:phone, &join(&1, :inner, [records], phones in assoc(records, :phone), as: :phones))
188194
195+
When `loader` function has arity 2, then all parameters are passed in the second argument.
196+
189197
### Options
190198
191199
* `:requires` - allows to set dependencies for current dependency.
@@ -199,7 +207,7 @@ defmodule Composite do
199207
func,
200208
opts \\ []
201209
)
202-
when is_function(func, 1) do
210+
when is_function(func, 1) or is_function(func, 2) do
203211
ensure_unknown_opts_absent!(opts, [:requires])
204212
%{composite | dep_definitions: Map.put(dep_definitions, dependency, {func, opts})}
205213
end
@@ -233,11 +241,19 @@ defmodule Composite do
233241
|> set_once!(:input_query, input_query)
234242
|> set_once!(:params, params)
235243

244+
{query, loaded_deps} =
245+
load_dependencies(
246+
composite.input_query,
247+
composite.params,
248+
composite.dep_definitions,
249+
MapSet.new(),
250+
composite.required_deps
251+
)
252+
236253
{query, _loaded_deps} =
237254
composite.param_definitions
238255
|> Enum.reverse()
239-
|> Enum.reduce({composite.input_query, MapSet.new()}, fn {path, func, opts},
240-
{query, loaded_deps} ->
256+
|> Enum.reduce({query, loaded_deps}, fn {path, func, opts}, {query, loaded_deps} ->
241257
value = get_in(composite.params, path)
242258

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

254270
{query, loaded_deps} =
255-
load_dependencies(query, composite.dep_definitions, loaded_deps, required_deps)
271+
load_dependencies(
272+
query,
273+
composite.params,
274+
composite.dep_definitions,
275+
loaded_deps,
276+
required_deps
277+
)
256278

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

268290
{query, loaded_deps} =
269-
load_dependencies(query, composite.dep_definitions, loaded_deps, required_deps)
291+
load_dependencies(
292+
query,
293+
composite.params,
294+
composite.dep_definitions,
295+
loaded_deps,
296+
required_deps
297+
)
270298

271299
case func do
272300
func when is_function(func, 1) -> {func.(query), loaded_deps}
@@ -278,6 +306,19 @@ defmodule Composite do
278306
query
279307
end
280308

309+
@doc """
310+
Forces loading dependency even if it is not required by `params`.
311+
"""
312+
@spec force_require(t(query), dependency_name() | [dependency_name()]) :: t(query)
313+
when query: any()
314+
def force_require(
315+
%__MODULE__{required_deps: required_deps} = composite,
316+
dependency_or_dependencies
317+
) do
318+
dependencies = List.wrap(dependency_or_dependencies)
319+
%__MODULE__{composite | required_deps: dependencies ++ required_deps}
320+
end
321+
281322
defp empty_value?(value) do
282323
value in [nil, "", [], %{}]
283324
end
@@ -306,11 +347,11 @@ defmodule Composite do
306347
end
307348
end
308349

309-
defp load_dependencies(query, _deps_definitions, loaded_deps, [] = _required_deps) do
350+
defp load_dependencies(query, _params, _deps_definitions, loaded_deps, [] = _required_deps) do
310351
{query, loaded_deps}
311352
end
312353

313-
defp load_dependencies(query, deps_definitions, loaded_deps, required_deps) do
354+
defp load_dependencies(query, params, deps_definitions, loaded_deps, required_deps) do
314355
deps_to_load = required_deps |> MapSet.new() |> MapSet.difference(loaded_deps)
315356

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

329370
{query, loaded_deps} =
330-
query |> load_dependencies(deps_definitions, loaded_deps, required_deps)
371+
load_dependencies(query, params, deps_definitions, loaded_deps, required_deps)
372+
373+
query =
374+
case loader do
375+
loader when is_function(loader, 1) -> loader.(query)
376+
loader when is_function(loader, 2) -> loader.(query, params)
377+
end
331378

332-
{loader.(query), loaded_deps}
379+
{query, loaded_deps}
333380
end)
334381

335382
{query, MapSet.union(loaded_deps, deps_to_load)}

mix.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Composite.MixProject do
22
use Mix.Project
33

4-
@version "0.3.0"
4+
@version "0.4.0"
55
def project do
66
[
77
app: :composite,
@@ -24,7 +24,7 @@ defmodule Composite.MixProject do
2424
def package do
2525
[
2626
description: "A utility for writing dynamic queries.",
27-
licenses: ["Apache 2"],
27+
licenses: ["Apache-2.0"],
2828
links: %{
2929
GitHub: "https://github.com/fuelen/composite"
3030
}

0 commit comments

Comments
 (0)