waldit 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8367ecd692940c28ead4bc255173137f7d9f34b9db3df262c15081ab64334ce0
4
- data.tar.gz: 84a7c6eeee1b62ff8a3f507e5ae6128759292a80d6e5bd1f1a936ca33c431326
3
+ metadata.gz: fb7ae260f6d1af42f4e6169084c7ba79de48cea93f44ff47be032f5764df23a6
4
+ data.tar.gz: 7cf9d0c022ffbb457bcac4380ed635cd65d2df1db24faae5fbd169b50b5aa07c
5
5
  SHA512:
6
- metadata.gz: 161fafcaba15339898929328f103ff1b659aa17aa89179e7e9124ecb7f7b0487da39d55402711f02f724326203a28c2f7e322ba97a28b3672007663114ac33fd
7
- data.tar.gz: cd4cc774d28e9f14732bc807478716c190fa52f6b2f4ec243512655db7fbd3d5aae6c712e7a9a301192702ea5b5a3753e315921ca62c64ea8c7c0119ffa27950
6
+ metadata.gz: b54045c2265401d420a276efddb20525b7b72e39d27c25e10da6a7a2c357805ea1956aed900d3c265f5cc88096dc402cd53ea52d0989eff8e3dd965d47c2ec75
7
+ data.tar.gz: 8d86ba10a3c94e7ca87bb90a39957663b305f19b6b5a8bfb7062fdae89879b57c82d230989bfd49b5480738f63ca81bf9db7b1093406b730578498bfc001a862
data/README.md CHANGED
@@ -1 +1,117 @@
1
1
  # Waldit
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/waldit.svg)](https://badge.fury.io/rb/waldit)
4
+
5
+ Waldit is a Ruby gem that provides a simple and extensible way to audit changes to your ActiveRecord models. It leverages PostgreSQL's logical replication capabilities to capture changes directly from your database with 100% consistency.
6
+
7
+ ## Features
8
+
9
+ - **Automatic Auditing:** Automatically track `create`, `update`, and `delete` operations on your models.
10
+ - **Contextual Auditing:** Add custom context to your audit records to understand who made the change and why.
11
+ - **Flexible Configuration:** Configure which tables and columns to watch, and how to store audit information.
12
+ - **High Performance:** Built on top of [`wal`](https://github.com/reu/wal), which uses PostgreSQL's logical replication for minimal overhead.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem "waldit"
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install waldit
29
+
30
+ ## Usage
31
+
32
+ 1. **Configure your database adapter:**
33
+
34
+ First step is to configure in your `config/database.yml` and change your adapter to `postgresqlwaldit`, which is a special adapter that allows injecting `waldit` contextual information on your transactions:
35
+
36
+ ```yaml
37
+ default: &default
38
+ adapter: postgresqlwaldit
39
+ # ...
40
+ ```
41
+
42
+ 2. **Create an audit table:**
43
+
44
+ Generate a migration to create the `waldit` table:
45
+
46
+ ```bash
47
+ rails generate migration create_waldit
48
+ ```
49
+
50
+ And then add the following to your migration file:
51
+
52
+ ```ruby
53
+ class CreateWalditTable < ActiveRecord::Migration[7.0]
54
+ def change
55
+ create_table :waldit do |t|
56
+ t.bigint :transaction_id, null: false
57
+ t.bigint :lsn, null: false
58
+ t.string :action, null: false
59
+ t.jsonb :context, default: {}
60
+ t.string :table_name, null: false
61
+ t.string :primary_key, null: false
62
+ t.jsonb :old, default: {}
63
+ t.jsonb :new, default: {}
64
+ t.timestamp :commited_at
65
+
66
+ t.index [:table_name, :primary_key, :transaction_id], unique: true
67
+ end
68
+ end
69
+ end
70
+ ```
71
+
72
+ 3. **Configure Waldit:**
73
+
74
+ Create an initializer file at `config/initializers/waldit.rb`:
75
+
76
+ ```ruby
77
+ Waldit.configure do |config|
78
+ # A callback that returns true if a table should be watched.
79
+ config.watched_tables = ->(table) { table != "waldit" }
80
+
81
+ # A callback that returns an array of columns to ignore for a given table.
82
+ config.ignored_columns = ->(table) { %w[created_at updated_at] }
83
+ end
84
+ ```
85
+
86
+ 4. **Add context to your changes:**
87
+
88
+ Use the `with_context` method to add context to your database operations:
89
+
90
+ ```ruby
91
+ Waldit.with_context(user_id: 1, reason: "User updated their profile") do
92
+ user.update(name: "New Name")
93
+ end
94
+ ```
95
+
96
+ 5. **Start the watcher:**
97
+
98
+ To process the events, you need to start a WAL watcher. The recommended way is to have a config/waldit.yml
99
+
100
+ ```yml
101
+ slots:
102
+ audit:
103
+ publications: [waldit_publication]
104
+ watcher: Waldit::Watcher
105
+ ```
106
+
107
+ And then run:
108
+
109
+ ```bash
110
+ bundle exec wal start config/waldit.yml
111
+ ```
112
+
113
+ ## How it Works
114
+
115
+ Waldit uses a custom PostgreSQL adapter to set the `waldit_context` session variable before each transaction. This context is then captured by the logical replication slot and stored in the `waldit` table by the `Waldit::Watcher`.
116
+
117
+ The `Waldit::Watcher` is a streaming watcher that listens for changes in the logical replication slot and creates audit records in the `waldit` table. It processes events in batches to minimize the number of database transactions.
@@ -27,6 +27,46 @@ module Waldit
27
27
  end
28
28
  end
29
29
 
30
+ def exec_no_cache(sql, ...)
31
+ return super if READ_QUERY_REGEXP.match? sql
32
+ return super if @current_waldit_context == Waldit.context.hash
33
+
34
+ if transaction_open?
35
+ set_waldit_context!
36
+ super
37
+
38
+ elsif Waldit.context
39
+ # We are trying to execute a query with waldit context while not in a transaction, so we start one
40
+ transaction do
41
+ set_waldit_context!
42
+ super
43
+ end
44
+
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def exec_cache(sql, ...)
51
+ return super if READ_QUERY_REGEXP.match? sql
52
+ return super if @current_waldit_context == Waldit.context.hash
53
+
54
+ if transaction_open?
55
+ set_waldit_context!
56
+ super
57
+
58
+ elsif Waldit.context
59
+ # We are trying to execute a query with waldit context while not in a transaction, so we start one
60
+ transaction do
61
+ set_waldit_context!
62
+ super
63
+ end
64
+
65
+ else
66
+ super
67
+ end
68
+ end
69
+
30
70
  def begin_db_transaction(...)
31
71
  @current_waldit_context = nil.hash
32
72
  super
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Waldit
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3"
6
6
  end
@@ -60,7 +60,7 @@ module Waldit
60
60
  else
61
61
  # Finally the most common case: just deleting a record not created or updated on this transaction
62
62
  record.upsert(
63
- audit.merge(action: "delete", old:),
63
+ audit.merge(action: "delete", old: event.old),
64
64
  unique_by:,
65
65
  on_duplicate: :update,
66
66
  )
data/rbi/waldit.rbi CHANGED
@@ -2,7 +2,7 @@
2
2
  module Waldit
3
3
  extend T::Sig
4
4
  extend Waldit::Context
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3"
6
6
 
7
7
  class << self
8
8
  sig { returns(String) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waldit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Navarro