Last active
February 19, 2025 01:34
-
-
Save yuchunc/dee7324a2bd4e8360793731289bc237f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule EctoQueryMaker do | |
@moduledoc """ | |
Query builder to abstract away boilerplate queries. | |
""" | |
import Ecto.Query | |
alias Ecto.{Query, Queryable} | |
@callback apply_filter(filter :: atom | {atom, any}, query :: Query.t()) :: Query.t() | |
@callback apply_option(opt :: atom | {atom, any}, query :: Query.t()) :: Query.t() | |
@callback apply_join(join :: atom | {atom, any}, query :: Query.t()) :: Query.t() | |
@callback apply_preload(preload :: atom | {atom, any}) :: atom | {atom, any} | |
@optional_callbacks apply_filter: 2, apply_option: 2, apply_join: 2, apply_preload: 1 | |
@doc """ | |
## Example | |
iex> # simple case | |
def get_resources(filters, opts) do | |
Resource | |
|> EctoQueryMaker.appy(filters, opts) | |
|> Repo.all | |
end | |
iex> # complex query in same module | |
def get_resources(filters, opts) do | |
Resource | |
|> EctoQueryMaker.appy(__MODULE__, filters, opts) | |
|> Repo.all | |
end | |
iex> # complex query module | |
defmodule ResourceQuery do | |
# custom filters | |
def apply_filter({key, value}, query) | |
def apply_filter(:complex_filter_key, query) | |
# custom joins | |
def apply_join({key, value}, query) | |
def apply_join(:complex_join_key, query) | |
# custom preloads | |
def apply_preload({key, value}, query) | |
def apply_preload(:complex_preload_key, query) | |
# custom options, use when doe not fit neatly in to previous queries | |
def apply_option({key, value}, query) | |
def apply_option(:complex_option_key, query) | |
end | |
iex> def get_resources(filters, opts) do | |
Resource | |
|> EctoQueryMaker.appy(ResourceQuery, filters, opts) | |
|> Repo.all | |
end | |
""" | |
@spec apply(Queryable.t(), list(), list()) :: Query.t() | |
def apply(query, filters, opts) do | |
apply(query, __MODULE__, filters, opts) | |
end | |
@spec apply(Queryable.t(), module(), list(), list()) :: Query.t() | |
def apply(query, module, filters, opts) do | |
Code.ensure_loaded(module) | |
query | |
|> apply_filters(filters, {function_exported?(module, :apply_filter, 2), module}) | |
|> apply_options(opts, {function_exported?(module, :apply_option, 2), module}) | |
end | |
defp apply_filters(query, [], _module_info), do: query | |
defp apply_filters(query, [filter | tail], {true, module} = module_info) do | |
case module.apply_filter(filter, query) do | |
^query -> apply_filter(filter, query) | |
query -> query | |
end | |
|> apply_filters(tail, module_info) | |
end | |
defp apply_filters(query, [filter | tail], module_info) do | |
apply_filter(filter, query) | |
|> apply_filters(tail, module_info) | |
end | |
defp apply_filter({attr, value}, query) when is_list(value) do | |
where(query, [r], field(r, ^attr) in ^value) | |
end | |
defp apply_filter({attr, value}, query) do | |
where(query, [r], field(r, ^attr) == ^value) | |
end | |
defp apply_options(query, [], _module_info), do: query | |
defp apply_options(query, [{:select, []} | tail], module_info) do | |
apply_options(query, tail, module_info) | |
end | |
defp apply_options(query, [{:select, select_by} | tail], module_info) do | |
query | |
|> select([q], map(q, ^select_by)) | |
|> apply_options(tail, module_info) | |
end | |
defp apply_options(query, [{:preload, preloads} | tail], {_, module} = module_info) do | |
preloads = apply_preloads([], preloads, {function_exported?(module, :apply_preload, 1), module}) | |
query | |
|> preload(^preloads) | |
|> apply_options(tail, module_info) | |
end | |
defp apply_options(query, [{:join, joins} | tail], {_, module} = module_info) do | |
query | |
|> apply_joins(joins, {function_exported?(module, :apply_join, 2), module}) | |
|> apply_options(tail, module_info) | |
end | |
defp apply_options(query, [opt | tail], {true, module} = module_info) do | |
module.apply_option(opt, query) | |
|> apply_options(tail, module_info) | |
end | |
defp apply_options(query, [_ | tail], module_info) do | |
apply_options(query, tail, module_info) | |
end | |
defp apply_joins(query, [], _module_info), do: query | |
defp apply_joins(query, [join | tail], {true, module} = module_info) do | |
module.apply_join(join, query) | |
|> apply_joins(tail, module_info) | |
end | |
defp apply_joins(query, [_ | tail], module_info) do | |
apply_joins(query, tail, module_info) | |
end | |
defp apply_preloads(preloads, [], _module_info), do: preloads | |
defp apply_preloads(preloads, [preload | tail], {true, module} = module_info) do | |
[module.apply_preload(preload) | preloads] | |
|> apply_preloads(tail, module_info) | |
end | |
defp apply_preloads(preloads, [preload | tail], module_info) do | |
apply_preloads([preload | preloads], tail, module_info) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment