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.
@@ -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. It intelligently detects soft-delete
8
- # implementations and adds appropriate indexes for performance.
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 columns
15
- # # Adds deleter_id if soft-delete detected
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 creator and updater columns to an existing table.
42
- # It optionally adds a deleter column based on soft-delete detection or explicit configuration.
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 auto-detect soft-delete,
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
- add_column table_name, Whodunit.creator_column, creator_type || Whodunit.creator_data_type, null: true
60
- add_column table_name, Whodunit.updater_column, updater_type || Whodunit.updater_data_type, null: true
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?(table_name, 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 actually exist.
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 auto-detect soft-delete,
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
- remove_column table_name, Whodunit.creator_column if column_exists?(table_name, Whodunit.creator_column)
85
- remove_column table_name, Whodunit.updater_column if column_exists?(table_name, Whodunit.updater_column)
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?(table_name, 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 auto-detect soft-delete,
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
- table_def.column Whodunit.creator_column, creator_type || Whodunit.creator_data_type, null: true
139
- table_def.column Whodunit.updater_column, updater_type || Whodunit.updater_data_type, null: true
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
- def should_include_deleter?(table_name, include_deleter)
150
- case include_deleter
151
- when :auto
152
- soft_delete_detected_for_table?(table_name)
153
- when true
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 with auto-detection
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
- case include_deleter
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
- # Detect soft-delete patterns in existing table
171
- def soft_delete_detected_for_table?(table_name)
172
- return false unless table_exists?(table_name)
173
-
174
- soft_delete_columns = %w[
175
- deleted_at destroyed_at discarded_at archived_at
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
- return unless should_include_deleter?(table_name, include_deleter)
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
- table_def.index Whodunit.creator_column, name: "index_#{table_name}_on_creator"
199
- table_def.index Whodunit.updater_column, name: "index_#{table_name}_on_updater"
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
 
@@ -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
 
@@ -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
- # Only add destroy callback if soft-delete is detected
48
- if Whodunit.auto_detect_soft_delete &&
49
- Whodunit::SoftDeleteDetector.enabled_for?(self)
50
- before_destroy :set_whodunit_deleter, if: :deleter_column?
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 ||= Whodunit::SoftDeleteDetector.enabled_for?(self)
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?(Whodunit.creator_column.to_s)
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?(Whodunit.updater_column.to_s)
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?(Whodunit.deleter_column.to_s)
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: Whodunit.user_class_name,
96
- foreign_key: Whodunit.creator_column,
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: Whodunit.user_class_name,
103
- foreign_key: Whodunit.updater_column,
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: Whodunit.user_class_name,
110
- foreign_key: Whodunit.deleter_column,
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[Whodunit.creator_column] = Whodunit::Current.user_id
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
- self[Whodunit.updater_column] = Whodunit::Current.user_id
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
- # This method is automatically called before_destroy if the model has a deleter column
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[Whodunit.deleter_column] = Whodunit::Current.user_id
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.column_names.include?(Whodunit.creator_column.to_s)
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.column_names.include?(Whodunit.updater_column.to_s)
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.column_names.include?(Whodunit.deleter_column.to_s)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Whodunit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end