# EctoDripper
[![Hex.pm](https://img.shields.io/hexpm/v/ecto_dripper.svg)](https://hex.pm/packages/ecto_dripper) [![Build Status](https://travis-ci.com/Ninigi/ecto_dripper.svg?branch=master)](https://travis-ci.com/Ninigi/ecto_dripper)
Provides an easy way to create composable ecto queries following a convention of `query_x(queryable, %{x: "asdf"})`, or `query_all(queryable, %{x: "asdf"})`.
## Installation
The package can be installed by adding `ecto_dripper` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ecto_dripper, "~> 0.1.0"}
]
end
```
## Why not just write my queries in the already extremely readable ecto DSL??
Excellent question and I am glad you are not a mindless zombie :ok_hand:
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 :fire:
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 Queries
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.
```elixir
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)
```
## Basic Usage
```elixir
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's going on here? I'd like to know how this works before `__using__`
```elixir
# 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
```
## Slightly more advanced usage - aka "I am lazy, give me more convenience"
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:
```elixir
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:
```elixir
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]`
```elixir
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.
```elixir
def name_or_identifier(query, %{name_or_identifier: _} = args) do
MyModule.my_handler(query, args)
end
```
## Read more
[A quick tutorial for usage in phoenix](https://medium.com/@fabian.zitter/phoenix-with-ectodripper-a-tutorial-i-guess-1578e4152f62)
## Repo.insert(%Contribution{code: "def awesome"})
**Contributions are highly welcome**
1. Fork it
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create new Pull Request