README.md

# RecordList

A stepwise construction of a struct, from a map of parameters, to return a list of records, and meta information about the query. 

Lists of records are useful in web applications and API's. The records returned often depend on paramaters such as sorting, filtering, searching and pagination. 
The RecordList struct is built up by passing it through the steps defined, capturing the information used to define the list as well as the records. 

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `record_list` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:record_list, "~> 0.1.0"}
  ]
end
```

## Usage

Depending on the steps defined, a RecordList can be built up based on a database query, an Elixir Stream, an enumarable, etc. 
No sequence of steps is enforced. However, the steps are called in the order in which they are defined. 

```elixir
defmodule MyApp.MyRecordList do
  import Ecto.Query

  use RecordList, 
    steps: [
      base: [impl: __MODULE__],
      sort: [impl: __MODULE__, default_order: "asc", default_sort: "name"],
      retrieve: [impl: __MODULE__, repo: MyApp.Repo]
    ]

  @behaviour RecordList.StepBehaviour

  @impl true
  def execute(%RecordList{params: %{"user_id" => user_id} = _params} = record_list, :base, _opts) do
    query = from(p in Post, where: p.user_id == ^user_id)
    %{ record_list | query: query }
  end

  @impl true
  def execute(%RecordList{params: params, query: query} = record_list, :sort, opts) do
    sort = get_in(params, ["sort"]) || Keyword.fetch!(opts, :default_sort)
    order = get_in(params, ["order"]) || Keyword.fetch!(opts, :default_order)
    query = order_by(query, [^order, ^sort])

    %{ record_list | query: query }
  end

  @impl true
  def execute(%RecordList{query: query} = record_list, :retrieve, opts) do
    %{ record_list | records: MyApp.Repo.all(query), loaded: true }
  end  

end
```

```elixir
%RecordList{records: [], loaded: false, steps: [:sort, :base]} = 
  sorted_record_list = MyApp.MyRecordList.sort(params)
%RecordList{records: records, loaded: true, steps: [:retrieve, :sort, :base]} 
  = retrieved_record_list = MyApp.MyRecordList.retrieve(sorted_record_list)
# Or
%RecordList{records: records, loaded: true, steps: [:retrieve, :sort, :base]} 
  = retrieved_record_list = MyApp.MyRecordList.retrieve(params)
```

In the example above the implementation is in the module implementing `RecordList`. To allow defining multilpe steps in the same module the step `atom` is passed as the second argument. 
By passing in a different module you can share implemenation of a step between different datalists. 

If you are using RecordList with Ecto, add `RecordListEcto` to your depedencies. Then pass the steps in that library as implementations when defining your record list. 

```elixir
defmodule MyEctoApp.MyRecordList do
  use RecordList, 
    steps: [
      ...,
      sort: [impl: RecordListEcto.SortStep, ...],
      paginate: [impl: RecordListEcto.PaginateStep, ...],
      ...
    ]
end
```


```elixir  
%RecordList{records: []], loaded: false, steps: [:paginate, :base], pagination: %RecordList.Pagination{records_count: _, current_page: _}} 
  = paginated_record_list = MyEctoApp.MyRecordList.paginate(params)
%RecordList{records: records, loaded: true, steps: [:retrieve, :paginate, :base]} 
  = retrieved_record_list = MyEctoApp.MyRecordList.retrieve(paginated_record_list)
# Or
%RecordList{records: records, loaded: true, steps: [:retrieve, :paginate, :base]} 
  = retrieved_record_list = MyEctoApp.MyRecordList.retrieve(params)
```

The `RecordList.__using__` macro would have added the following functions for `sort` to your `MyEctoApp.MyRecordList` module. 

```elixir
  def sort(%RecordList{} = record_list) do
    apply(RecordListEcto.SortStep, :execute, [record_list, :sort, options_for_step_other_than_impl])
  end

  # When called with the params map, record list will run through all the prior steps before calling the version of this 
  # step that takes the record_list. 
  def sort(params) do
    step_keys
    |> Enum.reduce_while(%RecordList{params: params}, fn step, record_list -> 
        :sort, record_list ->
          {:halt, record_list}

        missing_step, record_list ->
          {:cont, step(record_list, missing_step)}
      end)
      |> step(:sort)
    end)
  end

  def step(%RecordList{} = record_list, :sort) do
    apply(__MODULE__, :sort, [record_list])
  end

  def step(params, sort) when is_map(params) do
    apply(__MODULE__, :sort, [params])
  end
  
```