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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44f360e57980c1ff2fd3b0c87daefd2bad45eb643b588cd38dea1e5912f2af80
4
- data.tar.gz: 1f14c7d53bd1689b5ee39e5b5bc1fc99d05bf58d2e5f0426287193a95b3edb72
3
+ metadata.gz: 8d81d9849d620fd0965ee9e42495554abb5c9cb3d03db2768dbde6cc6cf84933
4
+ data.tar.gz: ab9d9baceb9c1942f9f52e9c8863e29843378bb481d299e6f84d1dffed47409e
5
5
  SHA512:
6
- metadata.gz: 278d96b4441fa49d9f4124dff4ac5917c4ab38b09265e53d2346bea57140771dece7d3f70e0049ea6d33f31aa53b7d0e08541f3521e7a233e9f2821a55e5ddf9
7
- data.tar.gz: 56c8b00ff1b7d71f7b6a7942039fd19c70c4a7716a9cafdf9f54545bcdaf2898741a2610b088c6f844625e8ba0a6fd4d4123c5830498efeff30372e7ffd3e5a3
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
- - Ruby `>= 3.0`
65
- - ActiveRecord `>= 7.0`
66
- - Rails `>= 7.0` (optional)
67
- - PostgreSQL, MySQL, SQLite
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
+ [![Gem Version](https://badge.fury.io/rb/auditron.svg?icon=si%3Arubygems&icon_color=%23ce0303)](https://badge.fury.io/rb/auditron)
5
+ ![GitHub Repo Views](https://gitviews.com/repo/spatelpatidar/auditron.svg)
3
6
  > Audit logging for API-first Rails apps — built-in retention, flexible actor tracking, and a clean query DSL.
4
7
 
5
8
  ---
@@ -43,6 +43,7 @@ module Auditron
43
43
 
44
44
  def changed_fields
45
45
  value = self[:changed_fields]
46
+ return {} if value.nil?
46
47
  return value if value.is_a?(Hash)
47
48
  JSON.parse(value.to_s)
48
49
  rescue JSON::ParserError
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Auditron
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -11,110 +11,268 @@ module Auditron
11
11
  source_root File.expand_path("templates", __dir__)
12
12
 
13
13
  def install
14
- display_banner
15
- display_intro
14
+ print_logo
15
+ print_intro
16
16
  return cancel_install unless confirm_install?
17
17
 
18
- say ""
19
- say " Creating migration...", :cyan
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
- display_initializer_hint
27
- display_success
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
- def display_banner
33
- say ""
34
- say " +===================================================+", :cyan
35
- say " | |", :cyan
36
- say " | AUDITRON -- Audit Logging Gem |", :cyan
37
- say " | v#{Auditron::VERSION.ljust(6)} |", :cyan
38
- say " | |", :cyan
39
- say " +===================================================+", :cyan
40
- say ""
41
- end
42
-
43
- def display_intro
44
- say " Auditron will set up audit logging for your Rails app.", :white
45
- say ""
46
- say " This installer will:", :white
47
- say ""
48
- say " [+] Create the audit_logs migration", :green
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 confirm_install?
65
- answer = ask(
66
- " Ready to install? This will create the audit_logs migration. [Y/n]:",
67
- :yellow
68
- ).strip.downcase
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
- def display_initializer_hint
74
- say " ---------------------------------------------------", :cyan
75
- say ""
76
- say " Next steps:", :white
77
- say ""
78
- say " 1. Run the migration:", :yellow
79
- say " rails db:migrate", :green
80
- say ""
81
- say " 2. Create config/initializers/auditron.rb:", :yellow
82
- say " Auditron.configure do |config|", :green
83
- say " config.ignored_fields = %i[updated_at created_at]", :green
84
- say " config.store_ip = false", :green
85
- say " config.retention_days = nil", :green
86
- say " end", :green
87
- say ""
88
- say " 3. Set current actor in ApplicationController:", :yellow
89
- say " before_action :set_audit_actor", :green
90
- say " def set_audit_actor", :green
91
- say " Auditron.current_actor = @current_user", :green
92
- say " end", :green
93
- say ""
94
- say " 4. Add auditable to your models:", :yellow
95
- say " auditable only: [:email, :role]", :green
96
- say ""
97
- say " 5. Query your logs:", :yellow
98
- say " user.audit_logs", :green
99
- say " AuditLog.by(admin).action(:deleted).since(1.week.ago)", :green
100
- say ""
101
- say " ---------------------------------------------------", :cyan
102
- say ""
103
- end
104
-
105
- def display_success
106
- say " [OK] Auditron installed successfully!", :green
107
- say " [OK] Run 'rails db:migrate' to complete setup.", :green
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
- say ""
115
- say " [CANCELLED] Installation cancelled.", :red
116
- say " Run 'rails generate auditron:install' again when ready.", :yellow
117
- say ""
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
- class CreateAuditLogs < ActiveRecord::Migration[7.0]
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
- t.text :changed_fields
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.text :metadata
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
- add_index :audit_logs, [:auditable_type, :auditable_id]
17
- add_index :audit_logs, [:actor_type, :actor_id]
18
- add_index :audit_logs, :action
19
- add_index :audit_logs, :created_at
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.0.0
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-08 00:00:00.000000000 Z
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/auditron/issues
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: []