README.md

# Ralitobu

**The Rate Limiter with Token Bucket algorithm**

This is a fork of [ExRated](https://github.com/grempe/ex_rated), but with some changes:

- the checkout and inspection always return the same result tuple
- asynchronous counterparts for checkout, inspect and delete
- configuration can be given on initialization and not only via application environment
  (useful when starting multiple rate limiters with different settings)

## Installation

In `mix.exs`:

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

```elixir
  def application do
    [applications: [:ralitobu]]
  end
```

## Usage

### Checkouts

- `Ralitobu.checkout/3` (`Ralitobu.checkout(id, limit, lifetime)` using default server)
- `Ralitobu.checkout/4` (`Ralitobu.checkout(server, id, limit, lifetime)`)

The result tuple format:

```elixir
{success, counter, remaining_limit, total_limit, countdown, created_at, updated_at}
```

```elixir
# 1st call:
Ralitobu.checkout("my-bucket", 3, 10_000)
#=> {:ok, 1, 2, 3, 7806, 1461432862194, 1461432862194}

# 3rd call:
Ralitobu.checkout("my-bucket", 3, 10_000)
#=> {:ok, 3, 0, 3, 7799, 1461432862194, 1461432862201}

# 4th call fails (over rate limit):
Ralitobu.checkout("my-bucket", 3, 10_000)
#=> {:error, 3, 0, 3, 7795, 1461432862194, 1461432862200}
```

### Inspection

- `Ralitobu.inspect/3` (`Ralitobu.inspect(id, limit, lifetime)` using default server)
- `Ralitobu.inspect/4` (`Ralitobu.inspect(server, id, limit, lifetime)`)

The result tuple format:

```elixir
{success, counter, remaining_limit, total_limit, countdown, created_at, updated_at}
```

```elixir
# multiple calls do not increment the counter:
Ralitobu.inspect("my-bucket", 3, 10_000)
#=> {:ok, 2, 3, 3, 4132, 1461432862194, 1461432862200}
Ralitobu.inspect("my-bucket", 3, 10_000)
#=> {:ok, 2, 3, 3, 4130, 1461432862194, 1461432862200}

# bucket does not exists:
Ralitobu.inspect("my-other-bucket", 3, 10_000)
#=> {:ok, 0, 3, 3, 9142, nil, nil}
```

### Deletion

- `Ralitobu.delete/1` (`Ralitobu.delete(id)` using default server)
- `Ralitobu.delete/2` (`Ralitobu.delete(server, id)`)

Result is either `:ok` or `:error`, depending if the bucket existed or not.

```elixir
# bucket (still) exists:
Ralitobu.delete("my-bucket")
#=> :ok

# bucket is not present (anymore):
Ralitobu.delete("my-bucket")
#=> :error
```

### New server instance

The application always starts a default server,
but you can run your own instance(s) as well.

`Ralitobu.start_server/2` (`Ralitobu.start_server(configuration, gen_server_opts)`)

```elixir
{:ok, server} = Ralitobu.start_server(
  [table_name: :another_ralitobu_table, timeout: 30_000_000, cleanup_rate: 15_000],
  [name: :another_ralitobu_server]
)
```

You must provide the `name` parameter for the GenServer options,
otherwise it will take the default name and therefore not starting a new server.
Also the `table_name` must be different, too.

### Server configuration

- `table_name` (atom):
  The name of the ETS table
- `timeout` (integer):
  milliseconds; buckets older than _(now - timeout)_ will be cleaned up (based on last update timestamp)
- `cleanup_rate` (integer):
  milliseconds; the interval for the clean up task