auditron 1.0.0 → 1.1.0
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 +4 -4
- data/CHANGELOG.md +154 -4
- data/README.md +3 -0
- data/lib/auditron/audit_log.rb +1 -0
- data/lib/auditron/version.rb +1 -1
- data/lib/generators/auditron/install/install_generator.rb +242 -84
- data/lib/generators/auditron/install/templates/create_audit_logs.rb.erb +56 -7
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d81d9849d620fd0965ee9e42495554abb5c9cb3d03db2768dbde6cc6cf84933
|
|
4
|
+
data.tar.gz: ab9d9baceb9c1942f9f52e9c8863e29843378bb481d299e6f84d1dffed47409e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bfe04d8854fc308a4552d19497531eed265b052bc9da3b609a60ad72ca86ba34b6450bdcb74efdb4fd291bbba7ccca320de3fbc32f0c276315c2445f668ac119
|
|
7
|
+
data.tar.gz: 2171c1878c75b9097896348329017adb349026dec846827d463ea2ae78a5cc1aae6f4236ef0761f306dae169748f1e845501166030cba00cfacd55ab7173b34f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,155 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.1.0] - 2026-04-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Interactive initializer — zero-config setup in one command
|
|
15
|
+
|
|
16
|
+
`rails generate auditron:install` now walks the developer through a short
|
|
17
|
+
wizard and **automatically creates `config/initializers/auditron.rb`** — no
|
|
18
|
+
manual file creation, no copy-pasting from the README.
|
|
19
|
+
|
|
20
|
+
**Before (1.0.x):**
|
|
21
|
+
```
|
|
22
|
+
rails generate auditron:install
|
|
23
|
+
→ creates db/migrate/create_audit_logs.rb
|
|
24
|
+
→ prints "Next steps: create config/initializers/auditron.rb manually"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**After (1.1.0):**
|
|
28
|
+
```
|
|
29
|
+
rails generate auditron:install
|
|
30
|
+
→ Step 1: creates db/migrate/create_audit_logs.rb
|
|
31
|
+
→ Step 2: asks 4 quick questions, writes config/initializers/auditron.rb
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The wizard asks:
|
|
35
|
+
|
|
36
|
+
| Prompt | Config written | Default |
|
|
37
|
+
|--------|---------------|---------|
|
|
38
|
+
| Ignored fields | `config.ignored_fields` | `[:updated_at, :created_at]` |
|
|
39
|
+
| Store IP address? | `config.store_ip` | `false` |
|
|
40
|
+
| Retention period (days) | `config.retention_days` | `nil` (keep forever) |
|
|
41
|
+
| Current actor method | Comment example in initializer | `current_user` |
|
|
42
|
+
|
|
43
|
+
Every prompt has a default — pressing Enter on all four produces a working
|
|
44
|
+
initializer instantly. The generated file looks like:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# frozen_string_literal: true
|
|
48
|
+
|
|
49
|
+
# Auditron configuration
|
|
50
|
+
# Generated by: rails generate auditron:install
|
|
51
|
+
# Auditron version: 1.1.0
|
|
52
|
+
|
|
53
|
+
Auditron.configure do |config|
|
|
54
|
+
|
|
55
|
+
# ── Ignored Fields ───────────────────────────────────────────────────
|
|
56
|
+
# Fields excluded from change tracking across all auditable models.
|
|
57
|
+
config.ignored_fields = [:updated_at, :created_at]
|
|
58
|
+
|
|
59
|
+
# ── IP Tracking ──────────────────────────────────────────────────────
|
|
60
|
+
# When true, the actor's remote IP is stored on every audit log.
|
|
61
|
+
config.store_ip = false
|
|
62
|
+
|
|
63
|
+
# ── Retention ────────────────────────────────────────────────────────
|
|
64
|
+
# Auto-purge logs older than this many days. nil = keep forever.
|
|
65
|
+
config.retention_days = nil
|
|
66
|
+
|
|
67
|
+
# ── Actor Resolution ─────────────────────────────────────────────────
|
|
68
|
+
# Set Auditron.current_actor in a before_action in your controller.
|
|
69
|
+
# Example (ApplicationController):
|
|
70
|
+
# before_action :set_audit_actor
|
|
71
|
+
# def set_audit_actor
|
|
72
|
+
# Auditron.current_actor = current_user
|
|
73
|
+
# end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Re-running the generator overwrites the existing initializer — useful when
|
|
79
|
+
onboarding new environments or resetting config to a clean state.
|
|
80
|
+
|
|
81
|
+
#### ASCII logo in generator output
|
|
82
|
+
|
|
83
|
+
The generator now displays an AUDITRON block-character logo on startup:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
█████╗ ██╗ ██╗██████╗ ██╗████████╗██████╗ ██████╗ ███╗ ██╗
|
|
87
|
+
██╔══██╗██║ ██║██╔══██╗██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║
|
|
88
|
+
███████║██║ ██║██║ ██║██║ ██║ ██████╔╝██║ ██║██╔██╗ ██║
|
|
89
|
+
██╔══██║██║ ██║██║ ██║██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║
|
|
90
|
+
██║ ██║╚██████╔╝██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║
|
|
91
|
+
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
92
|
+
|
|
93
|
+
Audit Logging for Rails ─── v1.1.0
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
All generator output goes through `$stdout` directly — Thor's buffering is
|
|
97
|
+
bypassed entirely to prevent the duplicate-output issue seen in some
|
|
98
|
+
Thor / Rails version combinations.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- Generator install flow split into two clearly labelled steps:
|
|
103
|
+
**Step 1: Migration** and **Step 2: Initializer**
|
|
104
|
+
- Post-install "Next steps" section updated — manual initializer creation
|
|
105
|
+
instruction removed since the file is now written automatically
|
|
106
|
+
- Generator intro updated to list initializer creation as part of what
|
|
107
|
+
the installer does
|
|
108
|
+
|
|
109
|
+
### Removed
|
|
110
|
+
|
|
111
|
+
- Manual `config/initializers/auditron.rb` instructions removed from
|
|
112
|
+
post-install output — no longer needed since the generator creates it
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## [1.0.1] - 2026-04-08
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- **Migration version hardcoded to `[7.0]`** — the generated `create_audit_logs`
|
|
121
|
+
migration previously always inherited from `ActiveRecord::Migration[7.0]`
|
|
122
|
+
regardless of the host application's Rails version. Running
|
|
123
|
+
`rails generate auditron:install` on a Rails 7.1, 7.2, or 8.0 app would
|
|
124
|
+
generate an incorrect migration class header.
|
|
125
|
+
|
|
126
|
+
The generator now reads `ActiveRecord::VERSION::MAJOR` and
|
|
127
|
+
`ActiveRecord::VERSION::MINOR` at generation time and injects the correct
|
|
128
|
+
version bracket automatically:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# Before (hardcoded — wrong on Rails 7.1+)
|
|
132
|
+
class CreateAuditLogs < ActiveRecord::Migration[7.0]
|
|
133
|
+
|
|
134
|
+
# After (dynamic — always matches the host app)
|
|
135
|
+
class CreateAuditLogs < ActiveRecord::Migration[7.1] # on Rails 7.1
|
|
136
|
+
class CreateAuditLogs < ActiveRecord::Migration[7.2] # on Rails 7.2
|
|
137
|
+
class CreateAuditLogs < ActiveRecord::Migration[8.0] # on Rails 8.0
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **`ArgumentError: wrong number of arguments (given 3, expected 0)`** on
|
|
141
|
+
`rails generate auditron:install` when running Rails 7.1+.
|
|
142
|
+
|
|
143
|
+
Rails 7.1 removed the third positional options hash from `migration_template`.
|
|
144
|
+
Passing `migration_version:` as a keyword argument to `migration_template`
|
|
145
|
+
caused an `ArgumentError` crash immediately after the user confirmed
|
|
146
|
+
installation — no files were created.
|
|
147
|
+
|
|
148
|
+
The generator now sets `@migration_version` as an instance variable before
|
|
149
|
+
calling `migration_template`, which makes it available inside the ERB
|
|
150
|
+
template directly. This approach is compatible with Rails 6.0 through 8.x.
|
|
151
|
+
|
|
152
|
+
### Compatibility
|
|
153
|
+
|
|
154
|
+
This release is fully backwards-compatible. No changes to public API,
|
|
155
|
+
configuration, model DSL, or query interface.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
10
159
|
## [1.0.0] - 2026-04-04
|
|
11
160
|
|
|
12
161
|
### Added
|
|
@@ -61,7 +210,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
61
210
|
|
|
62
211
|
## Compatibility
|
|
63
212
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
213
|
+
| Version | Ruby | ActiveRecord | Rails | Databases |
|
|
214
|
+
|---------|---------|--------------|---------------|----------------------------|
|
|
215
|
+
| 1.1.0 | >= 3.0 | >= 7.0 | >= 7.0 (opt.) | PostgreSQL, MySQL, SQLite |
|
|
216
|
+
| 1.0.1 | >= 3.0 | >= 7.0 | >= 7.0 (opt.) | PostgreSQL, MySQL, SQLite |
|
|
217
|
+
| 1.0.0 | >= 3.0 | >= 7.0 | >= 7.0 (opt.) | PostgreSQL, MySQL, SQLite |
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Auditron
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
[](https://badge.fury.io/rb/auditron)
|
|
5
|
+

|
|
3
6
|
> Audit logging for API-first Rails apps — built-in retention, flexible actor tracking, and a clean query DSL.
|
|
4
7
|
|
|
5
8
|
---
|
data/lib/auditron/audit_log.rb
CHANGED
data/lib/auditron/version.rb
CHANGED
|
@@ -11,110 +11,268 @@ module Auditron
|
|
|
11
11
|
source_root File.expand_path("templates", __dir__)
|
|
12
12
|
|
|
13
13
|
def install
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
print_logo
|
|
15
|
+
print_intro
|
|
16
16
|
return cancel_install unless confirm_install?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# ── Step 1: Migration ────────────────────────────────────────────────
|
|
19
|
+
puts_out ""
|
|
20
|
+
puts_out cyan(" ┌─ Step 1: Migration ──────────────────────────────────────────────┐")
|
|
21
|
+
puts_out ""
|
|
22
|
+
puts_out " \e[37mCreating audit_logs migration...\e[0m"
|
|
23
|
+
puts_out ""
|
|
24
|
+
|
|
25
|
+
@migration_version = migration_version
|
|
20
26
|
migration_template(
|
|
21
27
|
"create_audit_logs.rb.erb",
|
|
22
28
|
"db/migrate/create_audit_logs.rb"
|
|
23
29
|
)
|
|
24
|
-
say ""
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
# ── Step 2: Initializer ──────────────────────────────────────────────
|
|
32
|
+
puts_out ""
|
|
33
|
+
puts_out cyan(" ┌─ Step 2: Initializer ────────────────────────────────────────────┐")
|
|
34
|
+
puts_out ""
|
|
35
|
+
|
|
36
|
+
@cfg = {}
|
|
37
|
+
collect_ignored_fields
|
|
38
|
+
collect_store_ip
|
|
39
|
+
collect_retention_days
|
|
40
|
+
collect_actor_method
|
|
41
|
+
|
|
42
|
+
write_initializer
|
|
43
|
+
|
|
44
|
+
print_next_steps
|
|
45
|
+
print_done
|
|
28
46
|
end
|
|
29
47
|
|
|
30
48
|
private
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
say " [+] Add indexes for fast querying", :green
|
|
50
|
-
say " [+] Track: auditable, actor, action, changed fields, IP", :green
|
|
51
|
-
say ""
|
|
52
|
-
say " ---------------------------------------------------", :cyan
|
|
53
|
-
say ""
|
|
54
|
-
say " After install, add to any model:", :yellow
|
|
55
|
-
say ""
|
|
56
|
-
say " class User < ApplicationRecord", :white
|
|
57
|
-
say " auditable only: [:email, :role, :status]", :green
|
|
58
|
-
say " end", :white
|
|
59
|
-
say ""
|
|
60
|
-
say " ---------------------------------------------------", :cyan
|
|
61
|
-
say ""
|
|
50
|
+
# =========================================================================
|
|
51
|
+
# Interactive config collection
|
|
52
|
+
# =========================================================================
|
|
53
|
+
|
|
54
|
+
def collect_ignored_fields
|
|
55
|
+
puts_out " \e[37mIgnored fields\e[0m"
|
|
56
|
+
puts_out " \e[33m (Comma-separated field names to exclude from change tracking)\e[0m"
|
|
57
|
+
puts_out " \e[33m (Press Enter to use default: updated_at, created_at)\e[0m"
|
|
58
|
+
print_out " \e[36m›\e[0m \e[33m[updated_at, created_at]\e[0m: "
|
|
59
|
+
|
|
60
|
+
raw = $stdin.gets.to_s.strip
|
|
61
|
+
if raw.empty?
|
|
62
|
+
@cfg[:ignored_fields] = %i[updated_at created_at]
|
|
63
|
+
else
|
|
64
|
+
@cfg[:ignored_fields] = raw.split(",").map { |f| f.strip.to_sym }
|
|
65
|
+
end
|
|
66
|
+
puts_out ""
|
|
62
67
|
end
|
|
63
68
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
def collect_store_ip
|
|
70
|
+
puts_out " \e[37mStore IP address\e[0m"
|
|
71
|
+
puts_out " \e[33m (When true, the actor's IP is saved on every audit log)\e[0m"
|
|
72
|
+
print_out " \e[36m Store IP in audit logs? (y/n)\e[0m "
|
|
73
|
+
|
|
74
|
+
answer = $stdin.gets.to_s.strip.downcase
|
|
75
|
+
@cfg[:store_ip] = answer.start_with?("y")
|
|
76
|
+
puts_out ""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def collect_retention_days
|
|
80
|
+
puts_out " \e[37mRetention period\e[0m"
|
|
81
|
+
puts_out " \e[33m (Auto-delete audit logs older than N days. Press Enter to keep forever)\e[0m"
|
|
82
|
+
print_out " \e[36m›\e[0m \e[33m[nil — keep forever]\e[0m: "
|
|
83
|
+
|
|
84
|
+
raw = $stdin.gets.to_s.strip
|
|
85
|
+
@cfg[:retention_days] = raw.empty? ? nil : raw.to_i
|
|
86
|
+
puts_out ""
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def collect_actor_method
|
|
90
|
+
puts_out " \e[37mCurrent actor method\e[0m"
|
|
91
|
+
puts_out " \e[33m (The controller method / variable that returns the logged-in user)\e[0m"
|
|
92
|
+
print_out " \e[36m›\e[0m \e[33m[current_user]\e[0m: "
|
|
93
|
+
|
|
94
|
+
raw = $stdin.gets.to_s.strip
|
|
95
|
+
@cfg[:actor_method] = raw.empty? ? "current_user" : raw
|
|
96
|
+
puts_out ""
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# =========================================================================
|
|
100
|
+
# Write initializer
|
|
101
|
+
# =========================================================================
|
|
102
|
+
|
|
103
|
+
def write_initializer
|
|
104
|
+
dir = File.join(destination_root, "config", "initializers")
|
|
105
|
+
path = File.join(dir, "auditron.rb")
|
|
106
|
+
FileUtils.mkdir_p(dir)
|
|
107
|
+
File.write(path, build_initializer_content)
|
|
108
|
+
puts_out "\e[32m ✅ Created config/initializers/auditron.rb\e[0m"
|
|
109
|
+
puts_out ""
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_initializer_content
|
|
113
|
+
ignored = @cfg[:ignored_fields].map { |f| ":#{f}" }.join(", ")
|
|
114
|
+
retention = @cfg[:retention_days].nil? ? "nil" : @cfg[:retention_days].to_s
|
|
115
|
+
|
|
116
|
+
b = Lines.new
|
|
117
|
+
b << "# frozen_string_literal: true"
|
|
118
|
+
b << ""
|
|
119
|
+
b << "# Auditron configuration"
|
|
120
|
+
b << "# Generated by: rails generate auditron:install"
|
|
121
|
+
b << "# Auditron version: #{Auditron::VERSION}"
|
|
122
|
+
b << ""
|
|
123
|
+
b << "Auditron.configure do |config|"
|
|
124
|
+
b << ""
|
|
125
|
+
b << " # ── Ignored Fields ───────────────────────────────────────────────────"
|
|
126
|
+
b << " # Fields excluded from change tracking across all auditable models."
|
|
127
|
+
b << " config.ignored_fields = [#{ignored}]"
|
|
128
|
+
b << ""
|
|
129
|
+
b << " # ── IP Tracking ──────────────────────────────────────────────────────"
|
|
130
|
+
b << " # When true, the actor's remote IP is stored on every audit log."
|
|
131
|
+
b << " config.store_ip = #{@cfg[:store_ip]}"
|
|
132
|
+
b << ""
|
|
133
|
+
b << " # ── Retention ────────────────────────────────────────────────────────"
|
|
134
|
+
b << " # Auto-purge logs older than this many days. nil = keep forever."
|
|
135
|
+
b << " config.retention_days = #{retention}"
|
|
136
|
+
b << ""
|
|
137
|
+
b << " # ── Actor Resolution ─────────────────────────────────────────────────"
|
|
138
|
+
b << " # Proc called to resolve the current actor from the request context."
|
|
139
|
+
b << " # Set Auditron.current_actor in a before_action in your controller."
|
|
140
|
+
b << " # Example (ApplicationController):"
|
|
141
|
+
b << " # before_action :set_audit_actor"
|
|
142
|
+
b << " # def set_audit_actor"
|
|
143
|
+
b << " # Auditron.current_actor = #{@cfg[:actor_method]}"
|
|
144
|
+
b << " # end"
|
|
145
|
+
b << ""
|
|
146
|
+
b << "end"
|
|
147
|
+
b << ""
|
|
148
|
+
b.to_s
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# =========================================================================
|
|
152
|
+
# Logo & intro
|
|
153
|
+
# =========================================================================
|
|
154
|
+
|
|
155
|
+
LOGO = <<~LOGO
|
|
156
|
+
|
|
157
|
+
█████╗ ██╗ ██╗██████╗ ██╗████████╗██████╗ ██████╗ ███╗ ██╗
|
|
158
|
+
██╔══██╗██║ ██║██╔══██╗██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║
|
|
159
|
+
███████║██║ ██║██║ ██║██║ ██║ ██████╔╝██║ ██║██╔██╗ ██║
|
|
160
|
+
██╔══██║██║ ██║██║ ██║██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║
|
|
161
|
+
██║ ██║╚██████╔╝██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║
|
|
162
|
+
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
69
163
|
|
|
164
|
+
Audit Logging for Rails ─── v#{Auditron::VERSION}
|
|
165
|
+
|
|
166
|
+
LOGO
|
|
167
|
+
|
|
168
|
+
def print_logo
|
|
169
|
+
puts_out ""
|
|
170
|
+
LOGO.each_line { |line| $stdout.print "\e[36m#{line.chomp}\e[0m\n" }
|
|
171
|
+
puts_out ""
|
|
172
|
+
$stdout.flush
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def print_intro
|
|
176
|
+
puts_out " \e[36m#{"─" * 68}\e[0m"
|
|
177
|
+
puts_out ""
|
|
178
|
+
puts_out " \e[37mThis wizard will:\e[0m"
|
|
179
|
+
puts_out ""
|
|
180
|
+
puts_out " \e[32m[+] Create the audit_logs migration\e[0m"
|
|
181
|
+
puts_out " \e[32m[+] Create config/initializers/auditron.rb\e[0m"
|
|
182
|
+
puts_out " \e[32m[+] Ask you a few quick questions to tailor the config\e[0m"
|
|
183
|
+
puts_out ""
|
|
184
|
+
puts_out " \e[33m All settings can be changed later by editing the initializer.\e[0m"
|
|
185
|
+
puts_out ""
|
|
186
|
+
puts_out " \e[36m#{"─" * 68}\e[0m"
|
|
187
|
+
puts_out ""
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# =========================================================================
|
|
191
|
+
# Confirm
|
|
192
|
+
# =========================================================================
|
|
193
|
+
|
|
194
|
+
def confirm_install?
|
|
195
|
+
print_out "\e[33m Ready to install Auditron? (y/n) \e[0m"
|
|
196
|
+
answer = $stdin.gets.to_s.strip.downcase
|
|
70
197
|
answer == "y" || answer == "yes" || answer == ""
|
|
71
198
|
end
|
|
72
199
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
say ""
|
|
109
|
-
say " Happy auditing!", :cyan
|
|
110
|
-
say ""
|
|
200
|
+
# =========================================================================
|
|
201
|
+
# Next steps & done
|
|
202
|
+
# =========================================================================
|
|
203
|
+
|
|
204
|
+
def print_next_steps
|
|
205
|
+
puts_out " \e[36m#{"─" * 68}\e[0m"
|
|
206
|
+
puts_out ""
|
|
207
|
+
puts_out " \e[37mNext steps:\e[0m"
|
|
208
|
+
puts_out ""
|
|
209
|
+
puts_out " \e[33m1. Run the migration:\e[0m"
|
|
210
|
+
puts_out " \e[32mrails db:migrate\e[0m"
|
|
211
|
+
puts_out ""
|
|
212
|
+
puts_out " \e[33m2. Set current actor in ApplicationController:\e[0m"
|
|
213
|
+
puts_out " \e[32mbefore_action :set_audit_actor\e[0m"
|
|
214
|
+
puts_out " \e[32mdef set_audit_actor\e[0m"
|
|
215
|
+
puts_out " \e[32mAuditron.current_actor = #{@cfg[:actor_method]}\e[0m"
|
|
216
|
+
puts_out " \e[32mend\e[0m"
|
|
217
|
+
puts_out ""
|
|
218
|
+
puts_out " \e[33m3. Add auditable to your models:\e[0m"
|
|
219
|
+
puts_out " \e[32mauditable only: [:email, :role]\e[0m"
|
|
220
|
+
puts_out ""
|
|
221
|
+
puts_out " \e[33m4. Query your logs:\e[0m"
|
|
222
|
+
puts_out " \e[32muser.audit_logs\e[0m"
|
|
223
|
+
puts_out " \e[32mAuditLog.by(admin).action(:deleted).since(1.week.ago)\e[0m"
|
|
224
|
+
puts_out ""
|
|
225
|
+
puts_out " \e[36m#{"─" * 68}\e[0m"
|
|
226
|
+
puts_out ""
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def print_done
|
|
230
|
+
puts_out " \e[32m✅ Auditron installed successfully!\e[0m"
|
|
231
|
+
puts_out " \e[32m✅ Run 'rails db:migrate' to complete setup.\e[0m"
|
|
232
|
+
puts_out ""
|
|
233
|
+
puts_out " \e[36mHappy auditing!\e[0m"
|
|
234
|
+
puts_out ""
|
|
111
235
|
end
|
|
112
236
|
|
|
113
237
|
def cancel_install
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
238
|
+
puts_out ""
|
|
239
|
+
puts_out " \e[31m[CANCELLED] Installation cancelled.\e[0m"
|
|
240
|
+
puts_out " \e[33mRun 'rails generate auditron:install' again when ready.\e[0m"
|
|
241
|
+
puts_out ""
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# =========================================================================
|
|
245
|
+
# IO helpers — bypass Thor to prevent duplicate output
|
|
246
|
+
# =========================================================================
|
|
247
|
+
|
|
248
|
+
def puts_out(str = "")
|
|
249
|
+
$stdout.puts str
|
|
250
|
+
$stdout.flush
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def print_out(str)
|
|
254
|
+
$stdout.print str
|
|
255
|
+
$stdout.flush
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def cyan(str) = "\e[36m#{str}\e[0m"
|
|
259
|
+
|
|
260
|
+
# =========================================================================
|
|
261
|
+
# Migration version
|
|
262
|
+
# =========================================================================
|
|
263
|
+
|
|
264
|
+
def migration_version
|
|
265
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# =========================================================================
|
|
269
|
+
# Line buffer for building file content
|
|
270
|
+
# =========================================================================
|
|
271
|
+
|
|
272
|
+
class Lines
|
|
273
|
+
def initialize = (@buf = [])
|
|
274
|
+
def <<(str) = @buf << str
|
|
275
|
+
def to_s = @buf.join("\n") + "\n"
|
|
118
276
|
end
|
|
119
277
|
end
|
|
120
278
|
end
|
|
@@ -1,21 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration generated by Auditron <%= Auditron::VERSION %>
|
|
4
|
+
# Rails version detected: ActiveRecord::Migration<%= @migration_version %>
|
|
5
|
+
#
|
|
6
|
+
# This migration is version-aware — @migration_version is set automatically
|
|
7
|
+
# by the generator to match your app's installed Rails version.
|
|
8
|
+
|
|
9
|
+
class CreateAuditLogs < ActiveRecord::Migration<%= @migration_version %>
|
|
2
10
|
def change
|
|
11
|
+
# Detect database adapter to use correct JSON column type
|
|
12
|
+
# PostgreSQL supports jsonb (faster, indexable)
|
|
13
|
+
# MySQL and SQLite use json
|
|
14
|
+
json_type = postgresql? ? :jsonb : :json
|
|
15
|
+
|
|
3
16
|
create_table :audit_logs do |t|
|
|
17
|
+
# ── The record being audited (polymorphic) ──────────────────────────
|
|
4
18
|
t.string :auditable_type, null: false
|
|
5
19
|
t.bigint :auditable_id, null: false
|
|
20
|
+
|
|
21
|
+
# ── What happened ────────────────────────────────────────────────────
|
|
22
|
+
# Examples: "created", "updated", "deleted", "approved", "rejected"
|
|
6
23
|
t.string :action, null: false
|
|
7
|
-
|
|
24
|
+
|
|
25
|
+
# ── What changed (JSON serialised diff) ─────────────────────────────
|
|
26
|
+
# Stores before/after values: { "email": ["old@x.com", "new@x.com"] }
|
|
27
|
+
t.column :changed_fields, json_type
|
|
28
|
+
|
|
29
|
+
# ── Who did it (polymorphic actor, e.g. User, AdminUser) ─────────────
|
|
8
30
|
t.string :actor_type
|
|
9
31
|
t.bigint :actor_id
|
|
32
|
+
|
|
33
|
+
# ── Request context ───────────────────────────────────────────────────
|
|
10
34
|
t.string :ip_address
|
|
11
|
-
t.
|
|
35
|
+
t.column :metadata, json_type
|
|
12
36
|
|
|
37
|
+
# ── Timestamp (no updated_at — audit logs are immutable) ─────────────
|
|
13
38
|
t.datetime :created_at, null: false
|
|
14
39
|
end
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
add_index :audit_logs,
|
|
19
|
-
|
|
41
|
+
# ── Indexes ───────────────────────────────────────────────────────────
|
|
42
|
+
# Fetch all logs for a specific record (most common query)
|
|
43
|
+
add_index :audit_logs, %i[auditable_type auditable_id],
|
|
44
|
+
name: "idx_audit_logs_on_auditable"
|
|
45
|
+
|
|
46
|
+
# Fetch all actions performed by a specific actor
|
|
47
|
+
add_index :audit_logs, %i[actor_type actor_id],
|
|
48
|
+
name: "idx_audit_logs_on_actor"
|
|
49
|
+
|
|
50
|
+
# Filter by action type (e.g. all :deleted events)
|
|
51
|
+
add_index :audit_logs, :action,
|
|
52
|
+
name: "idx_audit_logs_on_action"
|
|
53
|
+
|
|
54
|
+
# Time-range queries and retention cleanup
|
|
55
|
+
add_index :audit_logs, :created_at,
|
|
56
|
+
name: "idx_audit_logs_on_created_at"
|
|
57
|
+
|
|
58
|
+
# GIN index only available on PostgreSQL with jsonb
|
|
59
|
+
if postgresql?
|
|
60
|
+
add_index :audit_logs, :changed_fields, using: :gin
|
|
61
|
+
add_index :audit_logs, :metadata, using: :gin
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def postgresql?
|
|
68
|
+
ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
|
|
20
69
|
end
|
|
21
70
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: auditron
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shailendra Kumar
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -155,7 +155,7 @@ metadata:
|
|
|
155
155
|
homepage_uri: https://github.com/spatelpatidar/auditron
|
|
156
156
|
source_code_uri: https://github.com/spatelpatidar/auditron
|
|
157
157
|
changelog_uri: https://github.com/spatelpatidar/auditron/blob/main/CHANGELOG.md
|
|
158
|
-
bug_tracker_uri: https://github.com/spatelpatidar/auditron/
|
|
158
|
+
bug_tracker_uri: https://github.com/spatelpatidar/auditron/issues
|
|
159
159
|
rubygems_mfa_required: 'true'
|
|
160
160
|
post_install_message:
|
|
161
161
|
rdoc_options: []
|