Skip to content

Commit 065bf91

Browse files
committed
Add global config via repo
1 parent 6f75cdd commit 065bf91

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

lib/dx.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ defmodule Dx do
1212
The corresponding `get!/3`, `load!/3` and `put!/3` functions return `result`
1313
directly, or otherwise raise an exception.
1414
15+
If you want to apply default options to all functions, `use` `Dx.Repo`.
16+
1517
Arguments:
1618
- **subjects** can either be an individual subject (with the given predicates defined on it), or a list of subjects.
1719
Passing an individual subject will return the predicates for the subject, passing a list will return a list of them.

lib/dx/repo.ex

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
defmodule Dx.Repo do
2+
@moduledoc """
3+
Defines a repository with default options, similar to `Ecto.Repo`.
4+
5+
When used, the repository expects the `:otp_app` as option.
6+
The `:otp_app` should point to an OTP application that has
7+
the repository configuration. For example, the repository:
8+
9+
defmodule Repo do
10+
use Dx.Repo,
11+
otp_app: :my_app,
12+
loader: Dx.Loaders.Dataloader,
13+
loader_options: [telemetry_options: [dx: true]]
14+
end
15+
16+
Could be configured with:
17+
18+
config :my_app, Repo,
19+
loader_options: [timeout: 20_000]
20+
21+
See `Dx` for further options. The options are deep-merged,
22+
with the following order of precedence:
23+
24+
1. Options passed to boundary functions, such as `Dx.get/3`
25+
2. Options returned by `Dx.Repo.default_options/1` callback
26+
3. Options from config
27+
4. Options from `use Dx.Repo, ...`.
28+
"""
29+
30+
@doc false
31+
defmacro __using__(opts) do
32+
quote bind_quoted: [opts: opts] do
33+
@behaviour Dx.Repo
34+
@otp_app Keyword.fetch!(opts, :otp_app)
35+
@opts opts
36+
37+
def default_options(_operation), do: []
38+
defoverridable default_options: 1
39+
40+
@compile {:inline, prepare_opts: 2}
41+
defp prepare_opts(operation_name, opts) do
42+
config = Application.get_env(otp_app, __MODULE__, [])
43+
44+
@opts
45+
|> Dx.Util.Keyword.deep_merge(config)
46+
|> Dx.Util.Keyword.deep_merge(default_options(operation_name))
47+
|> Dx.Util.Keyword.deep_merge(opts)
48+
end
49+
50+
def get(records, predicates, opts \\ []) do
51+
Dx.get(records, predicates, prepare_opts(opts, :get))
52+
end
53+
54+
def get!(records, predicates, opts \\ []) do
55+
Dx.get!(records, predicates, prepare_opts(opts, :get))
56+
end
57+
58+
def load(records, predicates, opts \\ []) do
59+
Dx.load(records, predicates, prepare_opts(opts, :load))
60+
end
61+
62+
def load!(records, predicates, opts \\ []) do
63+
Dx.load!(records, predicates, prepare_opts(opts, :load))
64+
end
65+
66+
def put(records, predicates, opts \\ []) do
67+
Dx.put(records, predicates, prepare_opts(opts, :put))
68+
end
69+
70+
def put!(records, predicates, opts \\ []) do
71+
Dx.put!(records, predicates, prepare_opts(opts, :put))
72+
end
73+
74+
def filter(records, condition, opts \\ []) when is_list(records) do
75+
Dx.filter(records, condition, prepare_opts(opts, :filter))
76+
end
77+
78+
def reject(records, condition, opts \\ []) when is_list(records) do
79+
Dx.reject(records, condition, prepare_opts(opts, :reject))
80+
end
81+
82+
def query_all(queryable, condition, opts \\ []) do
83+
Dx.query_all(queryable, condition, prepare_opts(opts, :query_all))
84+
end
85+
86+
def query_one(queryable, condition, opts \\ []) do
87+
Dx.query_one(queryable, condition, prepare_opts(opts, :query_one))
88+
end
89+
end
90+
end
91+
92+
## User callbacks
93+
94+
@doc """
95+
A user customizable callback invoked to retrieve default options
96+
for operations.
97+
This can be used to provide default values per operation that
98+
have higher precedence than the values given on configuration.
99+
"""
100+
@doc group: "User callbacks"
101+
@callback default_options(operation) :: Keyword.t()
102+
when operation: :get | :load | :put | :filter | :reject | :query_one | :query_all
103+
104+
## Query API
105+
106+
@type record :: any()
107+
@type predicate :: any()
108+
@type condition :: any()
109+
@type queryable :: any()
110+
@type opts :: Keyword.t()
111+
112+
@doc group: "Query API"
113+
@callback get([record], [predicate], opts) :: any()
114+
115+
@doc group: "Query API"
116+
@callback get!([record], [predicate], opts) :: any()
117+
118+
@doc group: "Query API"
119+
@callback load([record], [predicate], opts) :: any()
120+
121+
@doc group: "Query API"
122+
@callback load!([record], [predicate], opts) :: any()
123+
124+
@doc group: "Query API"
125+
@callback put([record], [predicate], opts) :: any()
126+
127+
@doc group: "Query API"
128+
@callback put!([record], [predicate], opts) :: any()
129+
130+
@doc group: "Query API"
131+
@callback filter([record], condition, opts) :: any()
132+
133+
@doc group: "Query API"
134+
@callback reject([record], condition, opts) :: any()
135+
136+
@doc group: "Query API"
137+
@callback query_all(queryable, condition, opts) :: any()
138+
139+
@doc group: "Query API"
140+
@callback query_one(queryable, condition, opts) :: any()
141+
end

lib/dx/util/keyword.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Dx.Util.Keyword do
2+
def deep_merge(left, right) do
3+
Keyword.merge(left, right, &deep_resolve/3)
4+
end
5+
6+
# Key exists in both lists, and both values are lists as well.
7+
# These can be merged recursively.
8+
defp deep_resolve(_key, left = [{_, _} | _], right = [{_, _} | _]) do
9+
deep_merge(left, right)
10+
end
11+
12+
# Key exists in both maps, but at least one of the values is
13+
# NOT a list. We fall back to standard merge behavior, preferring
14+
# the value on the right.
15+
defp deep_resolve(_key, _left, right) do
16+
right
17+
end
18+
end

0 commit comments

Comments
 (0)