README.md

# SimpleRepo

[![Hex Version](https://img.shields.io/hexpm/v/simple_repo.svg?style=flat-square)](https://hex.pm/packages/simple_repo) [![Docs](https://img.shields.io/badge/api-docs-orange.svg?style=flat-square)](https://hexdocs.pm/simple_repo) [![Hex downloads](https://img.shields.io/hexpm/dt/simple_repo.svg?style=flat-square)](https://hex.pm/packages/simple_repo) [![GitHub](https://img.shields.io/badge/vcs-GitHub-blue.svg?style=flat-square)](https://github.com/ertgl/simple_repo) [![MIT License](https://img.shields.io/hexpm/l/simple_repo.svg?style=flat-square)](LICENSE.txt)

This is a library enabling you to create a simple way to query for data using simple data structures like keyword lists.

## Add to dependencies

```elixir
defp deps do
  [
    {:simple_repo, "~> 1.1"}
  ]
end
```

## Usage

You can either use the SimpleRepo.Query module to create queries to use them with Ecto.Repo or add the SimpleRepo.Scoped macro to your own Repo module (see below).

```elixir
# To integrate into your Repo module you can use the Scoped macro:
defmodule MyApp.Repo do
    use Ecto.Repo, otp_app: :my_app
    use SimpleRepo.Scoped, repo: __MODULE__
end

# Create a schema and have a def changeset/2 function in place. This is a required convention to make this library work.
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :org,  :string
    field :email :string
    field :first_name, :string
    field :last_name, :integer

    timestamps()
  end

  def changeset(model, params) do
    fields = ~w(org email first_name last_name)

    model
    |> Ecto.Changeset.cast(params, fields)
    |> Ecto.Changeset.validate_required(:org)
    |> Ecto.Changeset.validate_required(:email)
    |> Ecto.Changeset.validate_required(:first_name)
    |> Ecto.Changeset.validate_required(:last_name)
  end
end

```

With this setup you can use it as follows.

```elixir
# UPDATE_SCOPED (for updates by primary key)
MyApp.Repository.update_scoped(MyApp.User, 42, [org: "Baz Ltd"])
# => {:ok, %MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Baz Ltd"}}
# When invalid:
# => {:error, changeset}
# When id does not exist:
# => {:error, :not_found}

# UPDATE_ALL_SCOPED
# For update all items in a scope. The specification of the return value can be found in the Ecto.Repo documentation &update_all/3:
MyApp.Repository.update_all_scoped(MyApp.User, [set: [org: "Baz Ltd"]], [org: "Foobar Ltd"])

# BY_ID_SCOPED
# Get item by primary key ensuring a scope is satisfied
MyApp.Repository.by_id_scoped(MyApp.User, 42, [org: "Baz Ltd"])
# => {:ok, %MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Foobar Ltd"}}
# When id does not exist:
# => {:error, :not_found}

# ONE_SCOPED (to get a single element)
MyApp.Repository.one_scoped(MyApp.User, [email: "foo@bar.com])
# => {:ok, %MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Foobar Ltd"}}
# When id does not exist:
# => {:error, :not_found}
# When multiple items would match an exception is raised (See Ecto.Repo &one/2)

# ALL_SCOPED
MyApp.Repository.all_scoped(MyApp.User, [org: "Foobar Ltd"])
# => [%MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Baz Ltd"}, ...]
# .all_scoped also allows ordering via options:
MyApp.Repository.all_scoped(MyApp.User, [org: "Foobar Ltd"], [order_by: [first_name: :asc]])
# => [%MyApp.User{id: 42, email: "foo@bar.com", first_name: "Aaron", last_name: "Baron", org: "Baz Ltd"}, ...]

# DELETE_SCOPED
MyApp.Repo.delete_scoped(MyApp, 42, [org: Foobar Ldt])
# => {:ok, %MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Foobar Ltd"}}
# When no such item exists:
# => {:error, :not_found}

# DELETE_ALL_SCOPED
MyApp.Repo.delete_all_scoped(MyApp, [org: Foobar Ldt])
# => [%MyApp.User{id: 42, email: "foo@bar.com", first_name: "John", last_name: "Doe", org: "Foobar Ltd"}, ...]

# AGGREGATE
MyApp.Repo.aggregate(MyApp, :count, :id, [org: Foobar Ldt])
# => 3
# Also here scoping is possible
MyApp.Repository.aggregate(MyApp, :count, :id, [org: "Foobar Ltd"])
# possible aggregations: [:avg, :count, :max, :min, :sum]
```

There are different ways to add scopes. As an alternative you can also use the Query module. The opportunities are the same also syntaxwise.
Here are some examples:
```elixir
SimpleRepo.Query.scoped(MyApp.User, [last_name: "Smith"])
# Scope to all users with last_name 'Smith'

SimpleRepo.Query.scoped(MyApp.User, [last_name: {:not, "Smith"}])
# Scope to all users not having last_name 'Smith'

SimpleRepo.Query.scoped(MyApp.User, [org: nil])
# Scope to all users not belonging to an org (The library handles the NULL case for you)

SimpleRepo.Query.scoped(MyApp.User, [org: {:not, nil}])
# Scope to all users belonging to an org.

SimpleRepo.Query.scoped(MyApp.User, [first_name: ["Kevin", "Hugo", "James"]])
# Scope to all users either having 'Kevin', 'Hugo' or 'James' as first_name). This is equivalent to the SQL 'WHERE IN'

SimpleRepo.Query.scoped(MyApp.User, [first_name: {:not, ["Kevin", "Hugo", "James"]}])
# Scope to all users NOT having 'Kevin', 'Hugo' or 'James' as first_name)

SimpleRepo.Query.scoped(MyApp.User, [email: {:like, "@gmail."}])
# Scope to all email addresses containing '@gmail.'

SimpleRepo.Query.scoped(MyApp.User, [email: {:not_like, "@gmail."}])
# Scope to all email addresses NOT containing '@gmail.'

SimpleRepo.Query.scoped(MyApp.User, [inserted_at: {:<=, Ecto.DateTime.from_erl({{2017, 1, 1}, {0, 0, 0}}})])
# Scope to all users either inserted after or at 2017-01-01T00:00:00Z. Analogue you can use :<, :> and :>=.

# Again ordering is possible:
SimpleRepo.Query.ordered(MyApp.User, {:last_name, :asc})
# or
SimpleRepo.Query.ordered(MyApp.User, last_name: :asc, first_name: :asc)
# The order direction is either :asc of :desc

```

**TODOs:**
 - Bulk save made simple
 - Extend possibilities to query and scope