Provides an easy way to create composable ecto queries following a convention of query_x(queryable, %{x: "asdf"})
, or query_all(queryable, %{x: "asdf"})
.
The package can be installed by adding ecto_dripper
to your list of dependencies in mix.exs
:
def deps do
[
{:ecto_dripper, "~> 0.1.0"}
]
end
Excellent question and I am glad you are not a mindless zombie 👌
If you don't know how to write queries in ecto, then this lib doesn't do anything for you but hide a few basic queries away. Don't use EctoDripper
if you are not at least a little familiar with ecto queries yet, at some point you will have to use it anyways 🔥
I wrote this lib as part of another project when I realized I am following the same pattern over and over again, and that's all this lib does for you: Streamline some repetetive tasks and unclutter your query modules.
Composable in this case means "pipable", as in create a bunch of queries you can pipe together, or use the convenience function query_all/2
to create queries from arguments.
Because of elixirs awesome pattern matching, we can just blindly pipe query functions together and only apply them if a certain key is in the arguments.
args = %{this_arg: "asdf"}
args2 = %{that_arg: "asdf"}
# Does only query for :this_arg
MyApp.MySchema
|> MyApp.MyQuery.query_all(args)
# Does only query for :that_arg
MyApp.MySchema
|> MyApp.MyQuery.query_all(args2)
# Or use a specific query
MyApp.MySchema
|> MyApp.MyQuery.query_this_arg(args)
defmodule MyApp.SomeQuery do
use EctoDripper,
composable_queries: [
[:status, :==, :status],
[:max_height, :>, :height],
[:status_down, :status_down]
],
standalone_queries: [
[:small_with_status_up]
]
def status_down(query, args)
def status_down(query, %{status_down: true}) do
from(
i in query,
where: i.status == ^"down"
)
end
def status_down(query, %{status_down: _}) do
from(
i in query,
where: i.status != ^"down"
)
end
def small_with_status_up(query, _args) do
from(
i in query,
where: i.status == ^"up", i.height <= 10
)
end
end
MyThing
|> MyApp.SomeQuery.query_all(%{status: "somewhere", max_height: 30})
# #Ecto.Query<from i in MyThing, where: i.status == ^"somewhere", i.height > ^30>
# and use it with your Repo
MyThing
|> MyApp.SomeQuery.query_all(%{status: "up", max_height: 30})
|> Repo.all()
# [%MyThing{}, ..]
# What my modules usually looks like without EctoDripper
defmodule MyApp.MyQuery do
def query_all(query, args) do
query
|> query_status(args)
|> query_name_or_identifier(args)
end
def query_status(query, args)
def query_status(query, %{status: status}) do
# Create query when status key is in args
from(
thing in query,
where: thing.status == ^status
)
end
def query_status(query, _args) do
# Let the query "fall through" if no status key in args
query
end
def query_name_or_identifier(query, args)
def query_name_or_identifier(query, %{query_name_or_identifier: query_name_or_identifier}) do
# Create query when query_name_or_identifier key is in args
from(
thing in query,
where: thing.name == ^query_name_or_identifier or thing.identifier == ^query_name_or_identifier
)
end
def query_name_or_identifier(query, _args) do
# Let the query "fall through" if no query_name_or_identifier key in args
query
end
end
# When using EctoDripper
defmodule MyApp.MyQuery do
use EctoDripper,
composable_queries:[
[:status, :==],
[:name_or_identifier, :do_query_name_or_identifier]
]
def do_query_name_or_identifier(query, args) do
from(
thing in query,
where: thing.name == ^query_name_or_identifier or thing.identifier == ^query_name_or_identifier
)
end
end
There are 2 different options, composable_queries
and standalone_queries
, both take a list of lists to create some
basic functions for you.
A function option can consist of 2 or 3 keywords. When read from left to right, they describe the query function. For example with:
use EctoDripper,
composable_queries: [
[:status, :==]
]
[:status, :==]
will create a query, comparing the status field of your queryable with the value for the status key in the args:
def query_status(query, %{status: _} = args) do
for(
thing in query,
where: thing.status == ^args.status
)
end
If you want your query argument different from the field name, you can do so by adding a third field to the option:
[:my_status_thing, :==, :status]
def query_status(query, %{my_status_thing: _} = args) do
for(
thing in query,
where: thing.status == ^args.my_status_thing
)
end
[:name_or_identifier, :my_handler]
calls your custom handler function when passed name_or_identifier
in args.
def name_or_identifier(query, %{name_or_identifier: _} = args) do
MyModule.my_handler(query, args)
end
A quick tutorial for usage in phoenix
Contributions are highly welcome
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request