whodunit 0.1.0 → 0.2.1
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/.rubocop.yml +2 -1
- data/CHANGELOG.md +54 -6
- data/README.md +76 -7
- data/exe/whodunit +18 -0
- data/lib/whodunit/controller_methods.rb +2 -2
- data/lib/whodunit/generator/application_record_integration.rb +85 -0
- data/lib/whodunit/generator.rb +134 -0
- data/lib/whodunit/migration_helpers.rb +94 -56
- data/lib/whodunit/railtie.rb +6 -0
- data/lib/whodunit/stampable.rb +115 -35
- data/lib/whodunit/table_definition_extension.rb +57 -0
- data/lib/whodunit/version.rb +1 -1
- data/lib/whodunit.rb +46 -4
- metadata +17 -10
- data/lib/whodunit/soft_delete_detector.rb +0 -119
@@ -4,15 +4,20 @@ module Whodunit
|
|
4
4
|
# Database migration helpers for adding whodunit stamp columns.
|
5
5
|
#
|
6
6
|
# This module provides convenient methods for adding creator/updater/deleter
|
7
|
-
# tracking columns to database tables.
|
8
|
-
#
|
7
|
+
# tracking columns to database tables based on configuration. Uses the configured
|
8
|
+
# column names and data types, and adds appropriate indexes for performance.
|
9
|
+
#
|
10
|
+
# The helpers respect column enabling/disabling configuration:
|
11
|
+
# - Only adds columns that are enabled (not nil) in configuration
|
12
|
+
# - Uses configured column names and data types
|
13
|
+
# - Deleter column inclusion based on soft_delete_column configuration
|
9
14
|
#
|
10
15
|
# @example Add stamps to existing table
|
11
16
|
# class AddWhodunitStampsToPosts < ActiveRecord::Migration[7.0]
|
12
17
|
# def change
|
13
18
|
# add_whodunit_stamps :posts
|
14
|
-
# # Adds creator_id, updater_id
|
15
|
-
# # Adds deleter_id if
|
19
|
+
# # Adds enabled columns: creator_id, updater_id
|
20
|
+
# # Adds deleter_id if soft_delete_column is configured
|
16
21
|
# end
|
17
22
|
# end
|
18
23
|
#
|
@@ -28,38 +33,57 @@ module Whodunit
|
|
28
33
|
# end
|
29
34
|
# end
|
30
35
|
#
|
31
|
-
# @example Custom data types
|
36
|
+
# @example Custom data types and explicit deleter inclusion
|
32
37
|
# add_whodunit_stamps :posts,
|
33
38
|
# creator_type: :string,
|
34
39
|
# updater_type: :uuid,
|
35
40
|
# include_deleter: true
|
36
41
|
#
|
42
|
+
# @example With custom column configuration
|
43
|
+
# # In config/initializers/whodunit.rb:
|
44
|
+
# Whodunit.configure do |config|
|
45
|
+
# config.creator_column = :created_by_id
|
46
|
+
# config.deleter_column = nil # Disable deleter column
|
47
|
+
# config.soft_delete_column = :archived_at
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # Migration will use custom column names and only add enabled columns
|
51
|
+
#
|
37
52
|
# @since 0.1.0
|
38
53
|
module MigrationHelpers
|
39
|
-
# Add creator/updater stamp columns to an existing table.
|
54
|
+
# Add creator/updater/deleter stamp columns to an existing table.
|
40
55
|
#
|
41
|
-
# This method adds the configured
|
42
|
-
#
|
56
|
+
# This method adds the configured whodunit columns to an existing table based on
|
57
|
+
# the current configuration. Only adds columns that are enabled (not nil).
|
58
|
+
# The deleter column is included based on soft_delete_column configuration when
|
59
|
+
# include_deleter is :auto, or explicitly when include_deleter is true.
|
43
60
|
# Indexes are automatically added for performance.
|
44
61
|
#
|
45
62
|
# @param table_name [Symbol] the table to add stamps to
|
46
|
-
# @param include_deleter [Symbol, Boolean] :auto to
|
63
|
+
# @param include_deleter [Symbol, Boolean] :auto to check soft_delete_column configuration,
|
47
64
|
# true to force inclusion, false to exclude
|
48
65
|
# @param creator_type [Symbol, nil] data type for creator column (defaults to configured type)
|
49
66
|
# @param updater_type [Symbol, nil] data type for updater column (defaults to configured type)
|
50
67
|
# @param deleter_type [Symbol, nil] data type for deleter column (defaults to configured type)
|
51
68
|
# @return [void]
|
52
|
-
# @example Basic usage
|
69
|
+
# @example Basic usage (uses configuration)
|
53
70
|
# add_whodunit_stamps :posts
|
54
71
|
# @example With custom types
|
55
72
|
# add_whodunit_stamps :posts, creator_type: :string, updater_type: :uuid
|
56
|
-
# @example Force deleter column
|
73
|
+
# @example Force deleter column inclusion
|
57
74
|
# add_whodunit_stamps :posts, include_deleter: true
|
75
|
+
# @example Exclude deleter column
|
76
|
+
# add_whodunit_stamps :posts, include_deleter: false
|
58
77
|
def add_whodunit_stamps(table_name, include_deleter: :auto, creator_type: nil, updater_type: nil, deleter_type: nil)
|
59
|
-
|
60
|
-
|
78
|
+
if Whodunit.creator_enabled?
|
79
|
+
add_column table_name, Whodunit.creator_column, creator_type || Whodunit.creator_data_type, null: true
|
80
|
+
end
|
81
|
+
|
82
|
+
if Whodunit.updater_enabled?
|
83
|
+
add_column table_name, Whodunit.updater_column, updater_type || Whodunit.updater_data_type, null: true
|
84
|
+
end
|
61
85
|
|
62
|
-
if should_include_deleter?(
|
86
|
+
if should_include_deleter?(include_deleter)
|
63
87
|
add_column table_name, Whodunit.deleter_column, deleter_type || Whodunit.deleter_data_type, null: true
|
64
88
|
end
|
65
89
|
|
@@ -69,22 +93,30 @@ module Whodunit
|
|
69
93
|
# Remove stamp columns from an existing table.
|
70
94
|
#
|
71
95
|
# This method removes the configured creator, updater, and optionally deleter
|
72
|
-
# columns from an existing table. Only removes columns that
|
96
|
+
# columns from an existing table. Only removes columns that are enabled in
|
97
|
+
# configuration and actually exist in the database.
|
73
98
|
#
|
74
99
|
# @param table_name [Symbol] the table to remove stamps from
|
75
|
-
# @param include_deleter [Symbol, Boolean] :auto to
|
100
|
+
# @param include_deleter [Symbol, Boolean] :auto to check soft_delete_column configuration,
|
76
101
|
# true to force removal, false to exclude
|
77
102
|
# @param _options [Hash] additional options (reserved for future use)
|
78
103
|
# @return [void]
|
79
|
-
# @example Basic usage
|
104
|
+
# @example Basic usage (uses configuration)
|
80
105
|
# remove_whodunit_stamps :posts
|
81
106
|
# @example Force deleter removal
|
82
107
|
# remove_whodunit_stamps :posts, include_deleter: true
|
108
|
+
# @example Exclude deleter removal
|
109
|
+
# remove_whodunit_stamps :posts, include_deleter: false
|
83
110
|
def remove_whodunit_stamps(table_name, include_deleter: :auto, **_options)
|
84
|
-
|
85
|
-
|
111
|
+
if Whodunit.creator_enabled? && column_exists?(table_name, Whodunit.creator_column)
|
112
|
+
remove_column table_name, Whodunit.creator_column
|
113
|
+
end
|
114
|
+
|
115
|
+
if Whodunit.updater_enabled? && column_exists?(table_name, Whodunit.updater_column)
|
116
|
+
remove_column table_name, Whodunit.updater_column
|
117
|
+
end
|
86
118
|
|
87
|
-
if should_include_deleter?(
|
119
|
+
if should_include_deleter?(include_deleter) &&
|
88
120
|
column_exists?(table_name, Whodunit.deleter_column)
|
89
121
|
remove_column table_name, Whodunit.deleter_column
|
90
122
|
end
|
@@ -96,15 +128,19 @@ module Whodunit
|
|
96
128
|
# 1. Inside a create_table block (pass table definition as first argument)
|
97
129
|
# 2. As a standalone method in migrations (attempts to infer table name)
|
98
130
|
#
|
131
|
+
# Only adds columns that are enabled in configuration. For new tables,
|
132
|
+
# deleter column is only added when explicitly requested (include_deleter: true)
|
133
|
+
# to be conservative.
|
134
|
+
#
|
99
135
|
# @param table_def [ActiveRecord::ConnectionAdapters::TableDefinition, nil]
|
100
136
|
# the table definition (when used in create_table block) or nil
|
101
|
-
# @param include_deleter [Symbol, Boolean] :auto to
|
137
|
+
# @param include_deleter [Symbol, Boolean] :auto to check soft_delete_column configuration,
|
102
138
|
# true to force inclusion, false to exclude
|
103
139
|
# @param creator_type [Symbol, nil] data type for creator column (defaults to configured type)
|
104
140
|
# @param updater_type [Symbol, nil] data type for updater column (defaults to configured type)
|
105
141
|
# @param deleter_type [Symbol, nil] data type for deleter column (defaults to configured type)
|
106
142
|
# @return [void]
|
107
|
-
# @example In create_table block
|
143
|
+
# @example In create_table block (uses configuration)
|
108
144
|
# create_table :posts do |t|
|
109
145
|
# t.string :title
|
110
146
|
# t.whodunit_stamps
|
@@ -113,6 +149,10 @@ module Whodunit
|
|
113
149
|
# def change
|
114
150
|
# whodunit_stamps # Adds to inferred table
|
115
151
|
# end
|
152
|
+
# @example Force deleter column in new table
|
153
|
+
# create_table :posts do |t|
|
154
|
+
# t.whodunit_stamps include_deleter: true
|
155
|
+
# end
|
116
156
|
def whodunit_stamps(table_def = nil, include_deleter: :auto, creator_type: nil, updater_type: nil,
|
117
157
|
deleter_type: nil)
|
118
158
|
if table_def.nil?
|
@@ -135,8 +175,13 @@ module Whodunit
|
|
135
175
|
|
136
176
|
# Handle stamps when called within create_table block
|
137
177
|
def handle_table_definition_stamps(table_def, include_deleter, creator_type, updater_type, deleter_type)
|
138
|
-
|
139
|
-
|
178
|
+
if Whodunit.creator_enabled?
|
179
|
+
table_def.column Whodunit.creator_column, creator_type || Whodunit.creator_data_type, null: true
|
180
|
+
end
|
181
|
+
|
182
|
+
if Whodunit.updater_enabled?
|
183
|
+
table_def.column Whodunit.updater_column, updater_type || Whodunit.updater_data_type, null: true
|
184
|
+
end
|
140
185
|
|
141
186
|
if should_include_deleter_for_new_table?(include_deleter)
|
142
187
|
table_def.column Whodunit.deleter_column, deleter_type || Whodunit.deleter_data_type, null: true
|
@@ -145,46 +190,37 @@ module Whodunit
|
|
145
190
|
add_whodunit_indexes_for_create_table(table_def, include_deleter)
|
146
191
|
end
|
147
192
|
|
148
|
-
# Determine if deleter column should be included
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
true
|
155
|
-
else
|
156
|
-
false
|
157
|
-
end
|
193
|
+
# Determine if deleter column should be included based on configuration.
|
194
|
+
# When :auto, checks if soft_delete_column is configured (not nil).
|
195
|
+
# @param include_deleter [Symbol, Boolean] the inclusion preference
|
196
|
+
# @return [Boolean] true if deleter column should be included
|
197
|
+
def should_include_deleter?(include_deleter)
|
198
|
+
include_deleter == :auto ? soft_delete_enabled? : include_deleter.eql?(true)
|
158
199
|
end
|
159
200
|
|
160
|
-
# For new tables, be more conservative
|
201
|
+
# For new tables, be more conservative - only include deleter when explicitly requested.
|
202
|
+
# This prevents adding deleter columns to tables that may not need them.
|
203
|
+
# @param include_deleter [Symbol, Boolean] the inclusion preference
|
204
|
+
# @return [Boolean] true only when explicitly requested (true)
|
161
205
|
def should_include_deleter_for_new_table?(include_deleter)
|
162
|
-
|
163
|
-
when true
|
164
|
-
true
|
165
|
-
else
|
166
|
-
false # Don't auto-add for new tables, let user be explicit
|
167
|
-
end
|
206
|
+
include_deleter.eql?(true)
|
168
207
|
end
|
169
208
|
|
170
|
-
#
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
soft_deleted_at soft_destroyed_at removed_at
|
177
|
-
]
|
178
|
-
|
179
|
-
soft_delete_columns.any? { |col| column_exists?(table_name, col) || column_exists?(table_name, col.to_sym) }
|
209
|
+
# Check if soft-delete is enabled based on configuration.
|
210
|
+
# Uses the configured soft_delete_column - if it's set (not nil), soft delete is enabled.
|
211
|
+
# @return [Boolean] true if soft_delete_column is configured
|
212
|
+
def soft_delete_enabled?
|
213
|
+
# Simple configuration-based check - trust the user's configuration
|
214
|
+
Whodunit.soft_delete_enabled?
|
180
215
|
end
|
181
216
|
|
182
217
|
# Add indexes for performance
|
183
218
|
def add_whodunit_indexes(table_name, include_deleter)
|
184
|
-
add_index table_name, Whodunit.creator_column, name: "index_#{table_name}_on_creator"
|
185
|
-
add_index table_name, Whodunit.updater_column, name: "index_#{table_name}_on_updater"
|
219
|
+
add_index table_name, Whodunit.creator_column, name: "index_#{table_name}_on_creator" if Whodunit.creator_enabled?
|
186
220
|
|
187
|
-
|
221
|
+
add_index table_name, Whodunit.updater_column, name: "index_#{table_name}_on_updater" if Whodunit.updater_enabled?
|
222
|
+
|
223
|
+
return unless should_include_deleter?(include_deleter)
|
188
224
|
|
189
225
|
add_index table_name, Whodunit.deleter_column, name: "index_#{table_name}_on_deleter"
|
190
226
|
end
|
@@ -195,8 +231,10 @@ module Whodunit
|
|
195
231
|
return unless table_def.respond_to?(:index)
|
196
232
|
|
197
233
|
table_name = table_def.respond_to?(:name) ? table_def.name : "table"
|
198
|
-
|
199
|
-
table_def.index Whodunit.
|
234
|
+
|
235
|
+
table_def.index Whodunit.creator_column, name: "index_#{table_name}_on_creator" if Whodunit.creator_enabled?
|
236
|
+
|
237
|
+
table_def.index Whodunit.updater_column, name: "index_#{table_name}_on_updater" if Whodunit.updater_enabled?
|
200
238
|
|
201
239
|
return unless should_include_deleter_for_new_table?(include_deleter)
|
202
240
|
|
data/lib/whodunit/railtie.rb
CHANGED
@@ -24,11 +24,17 @@ module Whodunit
|
|
24
24
|
#
|
25
25
|
# This initializer adds the MigrationHelpers module to ActiveRecord,
|
26
26
|
# making methods like add_whodunit_stamps available in migrations.
|
27
|
+
# It also extends TableDefinition for automatic whodunit_stamps injection.
|
27
28
|
#
|
28
29
|
# @api private
|
29
30
|
initializer "whodunit.extend_active_record" do |_app|
|
30
31
|
ActiveSupport.on_load(:active_record) do
|
31
32
|
extend Whodunit::MigrationHelpers
|
33
|
+
|
34
|
+
# Extend TableDefinition for automatic whodunit_stamps injection
|
35
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.include(
|
36
|
+
Whodunit::TableDefinitionExtension
|
37
|
+
)
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
data/lib/whodunit/stampable.rb
CHANGED
@@ -36,78 +36,121 @@ module Whodunit
|
|
36
36
|
# end
|
37
37
|
#
|
38
38
|
# @since 0.1.0
|
39
|
+
# rubocop:disable Metrics/ModuleLength
|
39
40
|
module Stampable
|
40
41
|
extend ActiveSupport::Concern
|
41
42
|
|
42
43
|
included do
|
43
|
-
# Set up callbacks
|
44
|
+
# Set up callbacks - the if conditions will check per-model settings at runtime
|
44
45
|
before_create :set_whodunit_creator, if: :creator_column?
|
45
46
|
before_update :set_whodunit_updater, if: :updater_column?
|
46
47
|
|
47
|
-
#
|
48
|
-
if
|
49
|
-
|
50
|
-
|
51
|
-
end
|
48
|
+
# Add deleter tracking for both hard and soft deletes
|
49
|
+
# The if conditions will check per-model settings at runtime
|
50
|
+
before_destroy :set_whodunit_deleter, if: :deleter_column?
|
51
|
+
before_update :set_whodunit_deleter, if: :being_soft_deleted?
|
52
52
|
|
53
53
|
# Set up associations - call on the class
|
54
54
|
setup_whodunit_associations
|
55
55
|
end
|
56
56
|
|
57
57
|
class_methods do # rubocop:disable Metrics/BlockLength
|
58
|
+
# Per-model configuration storage
|
59
|
+
attr_accessor :whodunit_config_overrides
|
60
|
+
|
61
|
+
# Configure per-model settings
|
62
|
+
def whodunit_config
|
63
|
+
@whodunit_config_overrides ||= {}
|
64
|
+
yield @whodunit_config_overrides if block_given?
|
65
|
+
@whodunit_config_overrides
|
66
|
+
end
|
67
|
+
|
58
68
|
def soft_delete_enabled?
|
59
|
-
@soft_delete_enabled ||=
|
69
|
+
@soft_delete_enabled ||= model_soft_delete_enabled?
|
60
70
|
end
|
61
71
|
|
62
72
|
def enable_whodunit_deleter!
|
63
73
|
before_destroy :set_whodunit_deleter, if: :deleter_column?
|
74
|
+
before_update :set_whodunit_deleter, if: :being_soft_deleted?
|
64
75
|
setup_deleter_association
|
65
76
|
@soft_delete_enabled = true
|
66
77
|
end
|
67
78
|
|
68
79
|
def disable_whodunit_deleter!
|
69
80
|
skip_callback :destroy, :before, :set_whodunit_deleter
|
81
|
+
skip_callback :update, :before, :set_whodunit_deleter
|
70
82
|
@soft_delete_enabled = false
|
71
83
|
end
|
72
84
|
|
85
|
+
# Get effective configuration value (model override or global default)
|
86
|
+
def whodunit_setting(key)
|
87
|
+
return @whodunit_config_overrides[key] if @whodunit_config_overrides&.key?(key)
|
88
|
+
|
89
|
+
Whodunit.public_send(key)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check if creator column is enabled for this model
|
93
|
+
def model_creator_enabled?
|
94
|
+
creator_column = whodunit_setting(:creator_column)
|
95
|
+
!creator_column.nil?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check if updater column is enabled for this model
|
99
|
+
def model_updater_enabled?
|
100
|
+
updater_column = whodunit_setting(:updater_column)
|
101
|
+
!updater_column.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check if deleter column is enabled for this model
|
105
|
+
def model_deleter_enabled?
|
106
|
+
deleter_column = whodunit_setting(:deleter_column)
|
107
|
+
!deleter_column.nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check if soft delete is enabled for this model
|
111
|
+
def model_soft_delete_enabled?
|
112
|
+
soft_delete_column = whodunit_setting(:soft_delete_column)
|
113
|
+
!soft_delete_column.nil?
|
114
|
+
end
|
115
|
+
|
73
116
|
private
|
74
117
|
|
75
118
|
def setup_whodunit_associations
|
76
|
-
setup_creator_association if creator_column_exists?
|
77
|
-
setup_updater_association if updater_column_exists?
|
78
|
-
setup_deleter_association if deleter_column_exists? && soft_delete_enabled?
|
119
|
+
setup_creator_association if creator_column_exists? && model_creator_enabled?
|
120
|
+
setup_updater_association if updater_column_exists? && model_updater_enabled?
|
121
|
+
setup_deleter_association if deleter_column_exists? && model_deleter_enabled? && soft_delete_enabled?
|
79
122
|
end
|
80
123
|
|
81
124
|
def creator_column_exists?
|
82
|
-
column_names.include?(
|
125
|
+
model_creator_enabled? && column_names.include?(whodunit_setting(:creator_column).to_s)
|
83
126
|
end
|
84
127
|
|
85
128
|
def updater_column_exists?
|
86
|
-
column_names.include?(
|
129
|
+
model_updater_enabled? && column_names.include?(whodunit_setting(:updater_column).to_s)
|
87
130
|
end
|
88
131
|
|
89
132
|
def deleter_column_exists?
|
90
|
-
column_names.include?(
|
133
|
+
model_deleter_enabled? && column_names.include?(whodunit_setting(:deleter_column).to_s)
|
91
134
|
end
|
92
135
|
|
93
136
|
def setup_creator_association
|
94
137
|
belongs_to :creator,
|
95
|
-
class_name:
|
96
|
-
foreign_key:
|
138
|
+
class_name: whodunit_setting(:user_class).to_s,
|
139
|
+
foreign_key: whodunit_setting(:creator_column),
|
97
140
|
optional: true
|
98
141
|
end
|
99
142
|
|
100
143
|
def setup_updater_association
|
101
144
|
belongs_to :updater,
|
102
|
-
class_name:
|
103
|
-
foreign_key:
|
145
|
+
class_name: whodunit_setting(:user_class).to_s,
|
146
|
+
foreign_key: whodunit_setting(:updater_column),
|
104
147
|
optional: true
|
105
148
|
end
|
106
149
|
|
107
150
|
def setup_deleter_association
|
108
151
|
belongs_to :deleter,
|
109
|
-
class_name:
|
110
|
-
foreign_key:
|
152
|
+
class_name: whodunit_setting(:user_class).to_s,
|
153
|
+
foreign_key: whodunit_setting(:deleter_column),
|
111
154
|
optional: true
|
112
155
|
end
|
113
156
|
end
|
@@ -124,61 +167,98 @@ module Whodunit
|
|
124
167
|
def set_whodunit_creator
|
125
168
|
return unless Whodunit::Current.user_id
|
126
169
|
|
127
|
-
self[
|
170
|
+
self[self.class.whodunit_setting(:creator_column)] = Whodunit::Current.user_id
|
128
171
|
end
|
129
172
|
|
130
173
|
# Set the updater ID when a record is updated.
|
131
174
|
#
|
132
175
|
# This method is automatically called before_update if the model has an updater column.
|
133
176
|
# It sets the updater_id to the current user from Whodunit::Current.
|
134
|
-
# Does not run on new records (creation).
|
177
|
+
# Does not run on new records (creation) or during soft-delete operations.
|
135
178
|
#
|
136
179
|
# @return [void]
|
137
180
|
# @api private
|
138
181
|
def set_whodunit_updater
|
139
182
|
return unless Whodunit::Current.user_id
|
183
|
+
|
140
184
|
return if new_record? # Don't set updater on creation
|
141
185
|
|
142
|
-
|
186
|
+
return if being_soft_deleted? # Don't set updater during soft-delete
|
187
|
+
|
188
|
+
self[self.class.whodunit_setting(:updater_column)] = Whodunit::Current.user_id
|
143
189
|
end
|
144
190
|
|
145
|
-
# Set the deleter ID when a record is destroyed.
|
191
|
+
# Set the deleter ID when a record is destroyed or soft-deleted.
|
192
|
+
#
|
193
|
+
# This method is automatically called in two scenarios:
|
194
|
+
# 1. before_destroy for hard deletes (if deleter column exists)
|
195
|
+
# 2. before_update for soft-deletes (when being_soft_deleted? returns true)
|
146
196
|
#
|
147
|
-
#
|
148
|
-
# and soft-delete is enabled. It sets the deleter_id to the current user from Whodunit::Current.
|
197
|
+
# It sets the deleter_id to the current user from Whodunit::Current.
|
149
198
|
#
|
150
199
|
# @return [void]
|
151
200
|
# @api private
|
152
201
|
def set_whodunit_deleter
|
153
202
|
return unless Whodunit::Current.user_id
|
154
203
|
|
155
|
-
self[
|
204
|
+
self[self.class.whodunit_setting(:deleter_column)] = Whodunit::Current.user_id
|
156
205
|
end
|
157
206
|
|
158
207
|
# @!group Column Presence Checks
|
159
208
|
|
160
|
-
# Check if the model has a creator column.
|
209
|
+
# Check if the model has a creator column and it's enabled.
|
161
210
|
#
|
162
|
-
# @return [Boolean] true if the creator column exists
|
211
|
+
# @return [Boolean] true if the creator column exists and is enabled
|
163
212
|
# @api private
|
164
213
|
def creator_column?
|
165
|
-
self.class.
|
214
|
+
return false unless self.class.model_creator_enabled?
|
215
|
+
|
216
|
+
column_name = self.class.whodunit_setting(:creator_column).to_s
|
217
|
+
self.class.column_names.include?(column_name)
|
166
218
|
end
|
167
219
|
|
168
|
-
# Check if the model has an updater column.
|
220
|
+
# Check if the model has an updater column and it's enabled.
|
169
221
|
#
|
170
|
-
# @return [Boolean] true if the updater column exists
|
222
|
+
# @return [Boolean] true if the updater column exists and is enabled
|
171
223
|
# @api private
|
172
224
|
def updater_column?
|
173
|
-
self.class.
|
225
|
+
return false unless self.class.model_updater_enabled?
|
226
|
+
|
227
|
+
column_name = self.class.whodunit_setting(:updater_column).to_s
|
228
|
+
self.class.column_names.include?(column_name)
|
174
229
|
end
|
175
230
|
|
176
|
-
# Check if the model has a deleter column.
|
231
|
+
# Check if the model has a deleter column and it's enabled.
|
177
232
|
#
|
178
|
-
# @return [Boolean] true if the deleter column exists
|
233
|
+
# @return [Boolean] true if the deleter column exists and is enabled
|
179
234
|
# @api private
|
180
235
|
def deleter_column?
|
181
|
-
self.class.
|
236
|
+
return false unless self.class.model_deleter_enabled?
|
237
|
+
|
238
|
+
column_name = self.class.whodunit_setting(:deleter_column).to_s
|
239
|
+
self.class.column_names.include?(column_name)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Check if the current update operation is a soft-delete.
|
243
|
+
#
|
244
|
+
# Uses ActiveRecord's dirty tracking to detect if any soft-delete columns
|
245
|
+
# are being changed from nil to a timestamp, which indicates a soft-delete operation.
|
246
|
+
#
|
247
|
+
# @return [Boolean] true if this update is setting a soft-delete timestamp
|
248
|
+
# @api private
|
249
|
+
def being_soft_deleted?
|
250
|
+
return false unless deleter_column?
|
251
|
+
return false unless Whodunit::Current.user_id
|
252
|
+
|
253
|
+
soft_delete_column = self.class.whodunit_setting(:soft_delete_column)
|
254
|
+
return false unless soft_delete_column
|
255
|
+
|
256
|
+
# Simple: just check the configured soft-delete column
|
257
|
+
column_name = soft_delete_column.to_s
|
258
|
+
attribute_changed?(column_name) &&
|
259
|
+
attribute_was(column_name).nil? &&
|
260
|
+
!send(column_name).nil?
|
182
261
|
end
|
183
262
|
end
|
263
|
+
# rubocop:enable Metrics/ModuleLength
|
184
264
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Whodunit
|
4
|
+
# Extension for ActiveRecord::ConnectionAdapters::TableDefinition to automatically
|
5
|
+
# inject whodunit_stamps when creating tables.
|
6
|
+
#
|
7
|
+
# This module monkey-patches the TableDefinition's column creation methods to
|
8
|
+
# automatically add whodunit stamp columns when auto-injection is enabled.
|
9
|
+
#
|
10
|
+
# @example Enabling auto-injection
|
11
|
+
# Whodunit.configure do |config|
|
12
|
+
# config.auto_inject_whodunit_stamps = true
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # Now this migration will automatically include whodunit stamps:
|
16
|
+
# class CreatePosts < ActiveRecord::Migration[8.0]
|
17
|
+
# def change
|
18
|
+
# create_table :posts do |t|
|
19
|
+
# t.string :title
|
20
|
+
# t.text :body
|
21
|
+
# t.timestamps
|
22
|
+
# # t.whodunit_stamps automatically added at the end!
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @since 0.1.0
|
28
|
+
module TableDefinitionExtension
|
29
|
+
extend ActiveSupport::Concern
|
30
|
+
|
31
|
+
included do
|
32
|
+
# Track whether whodunit_stamps have been automatically added
|
33
|
+
attr_accessor :_whodunit_stamps_added
|
34
|
+
end
|
35
|
+
|
36
|
+
# Override timestamps to trigger automatic whodunit_stamps injection
|
37
|
+
def timestamps(**options)
|
38
|
+
result = super
|
39
|
+
|
40
|
+
# Auto-inject whodunit_stamps after timestamps if enabled and not already added
|
41
|
+
if Whodunit.auto_inject_whodunit_stamps &&
|
42
|
+
!@_whodunit_stamps_added &&
|
43
|
+
!options[:skip_whodunit_stamps]
|
44
|
+
whodunit_stamps(include_deleter: :auto)
|
45
|
+
@_whodunit_stamps_added = true
|
46
|
+
end
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
# Also override whodunit_stamps to track that they've been added
|
52
|
+
def whodunit_stamps(**options)
|
53
|
+
@_whodunit_stamps_added = true
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/whodunit/version.rb
CHANGED