EctoIRS is an Elixir library that provides easy auditing capabilities for Ecto schemas and migrations. The library allows developers to automatically track who created or modified database records.
- 🔍 Automatic Audit Fields: Automatically add
inserted_byandupdated_byfields to your schemas - 🗃️ Migration Helpers: Easy-to-use migration functions for adding audit columns
- ⚙️ Flexible Configuration: Customize field names, references, and behavior
- 🚀 Auto-population: Support for automatic field population via MFA tuples
- 📦 Zero Dependencies: Built on top of Ecto with minimal external dependencies
Add ecto_irs to your list of dependencies in mix.exs:
def deps do
[
{:ecto_irs, "~> 0.1.0"}
]
endIn your migration:
defmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
import EctoIRS.Migration
def change do
create table(:posts) do
add :title, :string
add :content, :text
# Adds :inserted_by_id and :updated_by_id columns
audits :users
timestamps()
end
end
endIn your schema:
defmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
field :content, :text
# Generates :inserted_by and :updated_by associations
audits MyApp.User
timestamps()
end
end# Preload audit associations
post = MyApp.Repo.get(MyApp.Post, 1) |> MyApp.Repo.preload([:inserted_by, :updated_by])
# Access audit information
IO.puts "Created by: #{post.inserted_by.name}"
IO.puts "Last updated by: #{post.updated_by.name}"defmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
audits MyApp.User
timestamps()
end
enddefmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
audits MyApp.User,
inserted_by: :created_by,
updated_by: :modified_by
end
enddefmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
# Only track who created, not who updated
audits MyApp.User, updated_by: false
end
enddefmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
audits MyApp.User, references: :user_id
end
enddefmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
schema "posts" do
field :title, :string
audits MyApp.User, autogenerate: {MyApp.Context, :current_user_id, []}
end
enddefmodule MyApp.Post do
use Ecto.Schema
use EctoIRS.Schema
@audits_opts [autogenerate: {MyApp.Context, :current_user_id, []}]
schema "posts" do
field :title, :string
audits MyApp.User
end
enddefmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
import EctoIRS.Migration
def change do
create table(:posts) do
add :title, :string
audits :users
timestamps()
end
end
enddefmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
import EctoIRS.Migration
def change do
create table(:posts) do
add :title, :string
audits :users,
inserted_by: :created_by,
updated_by: :modified_by
timestamps()
end
end
enddefmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
import EctoIRS.Migration
def change do
create table(:posts) do
add :title, :string
audits :users, null: true
timestamps()
end
end
enddefmodule MyApp.Repo.Migrations.AddAuditsToExistingTable do
use Ecto.Migration
import EctoIRS.Migration
def change do
alter table(:existing_posts) do
audits :users
end
end
enddefmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
import EctoIRS.Migration
def change do
create table(:posts) do
add :title, :string
audits :users,
column: :user_id,
on_delete: :nilify_all,
on_update: :update_all
timestamps()
end
end
endConfigure default audit options at the repository level:
config :my_app, MyApp.Repo,
migration_audits: [
inserted_by: :created_by,
updated_by: :modified_by,
null: false
]inserted_by: The field name for insertion audit (default::inserted_by)updated_by: The field name for update audit (default::updated_by)references: The field on the referenced table (default::id)autogenerate: MFA tuple for automatic field population
inserted_by: Column name prefix for insertion audit (default::inserted_by)updated_by: Column name prefix for update audit (default::updated_by)null: Whether columns accept null values (default:false)
create index(:posts, [:inserted_by_id])
create index(:posts, [:updated_by_id])Stick to either the default names or establish a consistent naming convention across your application.
Decide whether audit fields should be required based on your application's security requirements.
Always preload audit associations when you need to access the audit information:
posts = MyApp.Repo.all(MyApp.Post) |> MyApp.Repo.preload([:inserted_by, :updated_by])mix testmix credo # Run static code analysis
mix dialyzer # Run type checkingmix docs # Generate documentation- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.