README.md

<!-- MDOC !-->
LibEcto is a simple wrapper for [ecto](https://hexdocs.pm/ecto/Ecto.html), make it much easier for daily use.

## Why LibEcto

Ecto is a great library, but it's a little bit verbose for daily use.

For example, imaging you have a schema like this:

```Elixir
defmodule Sample.Schema do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, LibEcto.KsuidType, autogenerate: true}
  schema "test" do
    field :name, :string
    field :value, :string

    timestamps()
  end

  def changeset(m, params) do
    m
    |> cast(params, [:name, :value])
  end
end
```

For most common use case, you need to write a lot of boilerplate code to do simple CRUD operation:

```Elixir
defmodule Sample.DB do

  alias Sample.Schema
  alias Sample.Repo
  import Ecto.Changeset

  def insert_one(params) do
    Schema.changeset(%Schema{}, params)
    |> Repo.insert()
  end

  def update_one(m, params) do
    m
    |> change(params)
    |> Repo.update()
  end

  def delete_one(m) do
    Repo.delete(m)
  end


  def get_by_id(id) do
    Repo.get(Schema, id, select: [:id, :name, :value])
  end

  def get_by_id_array(id_array) do
    Repo.all(from m in Schema, where: m.id in ^id_array, select: [:id, :name, :value])
  end

  def get_by_name(name) do
    Repo.get_by(Schema, name: name, select: [:id, :name, :value])
  end

  #... more boilerplate code
  # - get by name array
  # - get by page
  # - get by prefix

end
```

But!!!!!! With LibEcto, you can code like this:

```Elixir
defmodule Sample.DB do
    use LibEcto,
      repo: Sample.Repo,
      schema: Sample.Schema,
      columns: [
        :id,
        :name,
        :value
      ],
      filters: [
        :id,
        :name
      ]

    def filter(:id, dynamic, %{"id" => value}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and m.id == ^value)}

    def filter(:id, dynamic, %{"id" => value}) when is_list(value),
      do: {:ok, dynamic([m], ^dynamic and m.id in ^value)}

    def filter(:name, dynamic, %{"name" => value}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and m.name == ^value)}

    def filter(:name, dynamic, %{"name" => {"like", value}}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and like(m.name, ^value))}

    def filter(:name, dynamic, %{"name" => value}) when is_list(value),
      do: {:ok, dynamic([m], ^dynamic and m.name in ^value)}

    def filter(_, dynamic, _), do: {:ok, dynamic}


    def init_filter, do: dynamic([m], true)


    # you can use ecto's ability to build complicate query or update or transaction if GenericDB can't satisfy your need
    def other_complicated_query_or_update() do
      # do something
    end
end
```

LibEcto will generate all the boilerplate code for you, and you can focus on your business logic:


```Elixir
iex> Sample.DB.create_one(%{name: "test", value: "testv"})
{:ok, %Simple.Schema{id: "2JIebKci1ZgKenvhllJa3PMbydB", name: "test", value: "testv"}}

iex> Sample.DB.get_one(%{"name" => "test"})
{:ok, %Simple.Schema{id: "2JIebKci1ZgKenvhllJa3PMbydB", name: "test", value: "testv"}}

iex> Sample.DB.get_one(%{"name" => "not-exists"})
{:ok, nil}

iex> Sample.DB.get_one!(%{"name" => "not-exists"})
{:error, 404}

iex> {:ok, m} = Sample.DB.get_one(%{"name" => "test"})
iex> Sample.DB.update_one(m, name: "test2")
{:ok, %Simple.Schema{id: "2JIebKci1ZgKenvhllJa3PMbydB", name: "test2", value: "testv"}}
```
All supported functions:

- create_one/1
- get_one/1
- get_one!/1
- get_all/2
- get_limit/4
- count/1
- exists?/1
- get_by_page/5
- update_one/2
- delete_one/1
- delete_all/1

For more usage details, please check the test cases, which covers all the supported functions.


## V2
V2 is a complete rewrite of LibEcto, it's much more powerful and flexible. It simply breaks down the original complex macros into more reasonable ones. The only difference with LibEcto is the DB layer, Here's how to use it:

```Elixir
  defmodule Test.DB do
    @moduledoc false
    use LibEctoV2

    @repo Repo
    @schema Test.Schema
    @columns [:id, :name, :value]
    @filters [:id, :name]

    def filter(:id, dynamic, %{"id" => value}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and m.id == ^value)}

    def filter(:id, dynamic, %{"id" => value}) when is_list(value), do: {:ok, dynamic([m], ^dynamic and m.id in ^value)}

    def filter(:name, dynamic, %{"name" => value}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and m.name == ^value)}

    def filter(:name, dynamic, %{"name" => {"like", value}}) when is_bitstring(value),
      do: {:ok, dynamic([m], ^dynamic and like(m.name, ^value))}

    def filter(:name, dynamic, %{"name" => value}) when is_list(value),
      do: {:ok, dynamic([m], ^dynamic and m.name in ^value)}

    def filter(_, dynamic, _), do: {:ok, dynamic}

    def init_filter, do: dynamic([m], m.removed_at == 0)
  end
```

## Installation

The package can be installed by adding `lib_ecto` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:lib_ecto, "~> 0.3"}
  ]
end
```

## Test

Test cases use [ecto_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3) as database.


```bash
Finished in 1.5 seconds (0.00s async, 1.5s sync)
17 tests, 0 failures

Randomized with seed 336538

Generating cover results ...

Percentage | Module
-----------|--------------------------
    40.00% | LibEcto.KsuidType
    78.95% | LibEcto.Ksuid
   100.00% | LibEcto
   100.00% | LibEcto.Base62
-----------|--------------------------
    76.19% | Total

Coverage test failed, threshold not met:

    Coverage:   76.19%
    Threshold:  90.00%

Generated HTML coverage results in "cover" directory
```