RuleBook

CILicenseVersionHex Docs

A lightweight, deterministic forward-chaining rules engine for Elixir.

Usage

defmodule FraudRules do
  use RuleBook.Rules

  defmodule User do
    defstruct [:id, :country]
  end

  defmodule Payment do
    defstruct [:id, :user_id, :amount, :country]
  end

  defmodule Decision do
    defstruct [:payment_id, :status, :reason]
  end

  @doc "Block payment when the user's country differs from the payment country"
  defrule :block_if_country_mismatch,
    when: [
      %Payment{id: payment_id, user_id: uid, country: pay_country},
      %User{id: uid, country: user_country} when pay_country != user_country
    ],
    then: fn ctx ->
      b = ctx.binding
      RuleBook.Action.assert(ctx, %Decision{
        payment_id: b.payment_id,
        status: :blocked,
        reason: :country_mismatch
      })
    end,
    salience: 10
end

# 1) Facts (user, payment) -> Fetch them from the db or infer from the context
user = %FraudRules.User{id: 1, country: :US}
payment = %FraudRules.Payment{id: 10, user_id: 1, amount: 12_500, country: :DE}

# 2) Run rules
rule_book = RuleBook.new(rules: FraudRules)
rule_book = rule_book |> RuleBook.assert(user) |> RuleBook.assert(payment)
{_rule_book, acts} = RuleBook.run(rb)

# 3) Decide: allow if no blocking decision, otherwise block
rb
|> RuleBook.facts()
|> Enum.any?(fn
  %FraudRules.Decision{payment_id: ^payment.id, status: :blocked} -> true
  _ -> false
end)
|> case do
  # At least one rule matched to block the payment
  true -> :block
  # No rules matched to block the payment
  false -> :allow
end

Why RuleBook?

Write rules with Elixir’s pattern matching and guards. RuleBook unifies variables across patterns, builds an ordered agenda, and executes pure actions that return effects (assert, retract, emit, log). Your app applies those effects; in particular, emitted events are your responsibility to observe.

Key ideas: rules, facts, patterns (+ guards), bindings (unified variables), agenda, activations, effects. Actions should be pure; use RuleBook.Action helpers to return effects.

Concepts

Telemetry

RuleBook (in the future) will emit telemetry when building the agenda, firing activations, and applying memory changes. Events use the [:rule_book, ...] prefix and are no-ops unless :telemetry is available.

Guards and unification

You can compare values across different facts using guards and shared variable names:

defrule :country_mismatch,
  when: [
    %Payment{user_id: uid, country: pay_country},
    %User{id: uid, country: user_country} when pay_country != user_country
  ],
  then: fn ctx -> ctx end

Install

{:rule_book, "~> 0.1.0"}