Welcome! This is an extension for the Ash framework which protects actions from abuse by enforcing rate limits.
Uses the excellent hammer to provide rate limiting functionality.
The easiest way to install ash_rate_limiter is using Igniter:
mix igniter.install ash_rate_limiterThis will:
- Add the dependency to your
mix.exs - Configure the formatter to handle AshRateLimiter DSL
- Set up proper Spark DSL section ordering
Add ash_rate_limiter to your list of dependencies in mix.exs:
def deps do
[
{:ash_rate_limiter, "~> 1.0.0"}
]
endAnd add :ash_rate_limiter to your .formatter.exs:
[
import_deps: [:ash, :ash_rate_limiter],
# ... other formatter config
]- Add a Hammer module: You need to create a Hammer module:
# lib/my_app/hammer.ex
defmodule MyApp.Hammer do
use Hammer, backend: :ets
end- Add the rate limiter to your application's supervision tree: (more information about
:clean_periodin Hammer):
# lib/my_app/application.ex
@impl true
def start(_type, _args) do
children = [
# ...
# Add the line below:
{MyApp.Hammer, clean_period: :timer.minutes(1)},
# ...
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end- Configure the Hammer backend Add configuration to point to your Hammer module:
# config/config.exs
config :my_app, :ash_rate_limiter,
hammer: MyApp.Hammer- Add to your resource: Use the
rate_limitDSL section in your Ash resource:
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp,
extensions: [AshRateLimiter]
rate_limit do
# Configure hammer backend
backend MyApp.Hammer
# Limit create action to 10 requests per 5 minutes
action :create, limit: 10, per: :timer.minutes(5)
# Limit read action to 100 requests per minute
action :read, limit: 100, per: :timer.minutes(1)
end
# ... rest of your resource definition
end- That's it! Your actions are now rate limited. When the limit is exceeded, an
AshRateLimiter.LimitExceedederror will be raised.
rate_limit do
backend MyApp.Hammer
action :create, limit: 5, per: :timer.minutes(1)
endrate_limit do
backend MyApp.Hammer
action :create,
limit: 10,
per: :timer.minutes(5),
key: fn changeset, context ->
"user:#{context.actor.id}:create"
end
endrate_limit do
backend MyApp.Hammer
action :create, limit: 10, per: :timer.minutes(5)
action :update, limit: 20, per: :timer.minutes(5)
action :read, limit: 100, per: :timer.minutes(1)
endFor more control, you can add rate limiting directly to specific actions:
defmodule MyApp.Post do
use Ash.Resource, domain: MyApp
actions do
create :create do
change {AshRateLimiter.Change, limit: 10, per: :timer.minutes(5)}
end
read :read do
prepare {AshRateLimiter.Preparation, limit: 100, per: :timer.minutes(1)}
end
end
endThe key function determines how requests are grouped for rate limiting:
# Rate limit per IP address
key: fn _changeset, context ->
"ip:#{context[:ip_address]}"
end
# Rate limit per user and action
key: fn changeset, context ->
"user:#{context.actor.id}:action:#{changeset.action.name}"
end
# Use the built-in key function with options
key: {&AshRateLimiter.key_for_action/2, include_actor_attributes: [:role]}When rate limits are exceeded, an AshRateLimiter.LimitExceeded exception is raised:
case MyApp.create_post(attrs) do
{:ok, post} ->
# Success
{:ok, post}
{:error, %AshRateLimiter.LimitExceeded{} = error} ->
# Rate limit exceeded
{:error, "Too many requests, please try again later"}
{:error, other_error} ->
# Handle other errors
{:error, other_error}
endIn web applications, the exception includes Plug.Exception behaviour for automatic HTTP 429 responses.

