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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ce926c88b69914bd703491e1bf11f890f64a4c4f5679929d1b898306ea18fcd
|
4
|
+
data.tar.gz: 3b277cf8bbf1f36e494f90f959941c38b3829f541b5b36b1168b98a276d830f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68b739ee97b145af1451991f589dfd9353da7ff579592e9f45071c532ed095e2b83e166b30b987bdbd6a22f1f47b421d1dc76fce7818491889376a0249ecdd21
|
7
|
+
data.tar.gz: 3796e3c88a21640063c504d27f3fa34a16f077eac5653826cd7a0a4ad513288dec0aa88383afa633daac57d25719711e2325cc33e01079f00725f76d2d22270d
|
data/.rubocop.yml
CHANGED
@@ -6,13 +6,14 @@ plugins:
|
|
6
6
|
|
7
7
|
AllCops:
|
8
8
|
NewCops: enable
|
9
|
-
TargetRubyVersion: 3.
|
9
|
+
TargetRubyVersion: 3.1
|
10
10
|
Exclude:
|
11
11
|
- 'vendor/**/*'
|
12
12
|
- 'tmp/**/*'
|
13
13
|
- 'coverage/**/*'
|
14
14
|
- 'bin/**/*'
|
15
15
|
- 'sig/**/*'
|
16
|
+
- 'example/**/*'
|
16
17
|
|
17
18
|
# Layout & Formatting
|
18
19
|
Layout/LineLength:
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.2.1] - 2025-01-21
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- **ApplicationRecord Integration**: `whodunit install` now prompts to automatically add `Whodunit::Stampable` to `ApplicationRecord` for convenient all-model stamping
|
15
|
+
- **Enhanced Configuration Template**: Detailed explanations and examples for each configuration option in generated initializer
|
16
|
+
- **Post-install Message**: Helpful instructions displayed after gem installation via `bundle add whodunit`
|
17
|
+
- **Comprehensive Test Coverage**: Full test suite for ApplicationRecord integration with edge case handling
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- **Improved CLI Experience**: More user-friendly prompts and messages throughout the installation process
|
22
|
+
- **Better Documentation**: Updated README with corrected installation commands and new ApplicationRecord feature
|
23
|
+
- **Code Organization**: Extracted ApplicationRecord integration logic into separate module for better maintainability
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
|
27
|
+
- **Gemspec Consistency**: Corrected post-install message to show `whodunit install` instead of incorrect Rails generator command
|
28
|
+
- **RuboCop Compliance**: Fixed all style issues and reduced complexity across codebase
|
29
|
+
|
30
|
+
## [0.2.0] - 2025-01-20
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- New `whodunit install` CLI command to generate configuration initializer
|
35
|
+
- Per-model configuration override capability via `whodunit_config` block
|
36
|
+
- Column enabling/disabling - set individual columns to `nil` to disable them
|
37
|
+
- Configuration validation to prevent disabling both creator and updater columns
|
38
|
+
- Comprehensive generator with Rails app detection and safety prompts
|
39
|
+
- Enhanced YARD documentation reflecting simplified architecture
|
40
|
+
|
41
|
+
### Changed
|
42
|
+
|
43
|
+
- **BREAKING**: `soft_delete_column` now defaults to `nil` instead of `:deleted_at`
|
44
|
+
- **BREAKING**: Removed automatic soft-delete detection - now purely configuration-based
|
45
|
+
- **BREAKING**: Simplified `being_soft_deleted?` logic to check only configured column
|
46
|
+
- Migration helpers now respect column enabling/disabling configuration
|
47
|
+
- Updated all documentation to reflect simplified, configuration-based approach
|
48
|
+
- Improved test infrastructure with better configuration isolation
|
49
|
+
|
50
|
+
### Removed
|
51
|
+
|
52
|
+
- **BREAKING**: `SoftDeleteDetector` class and all auto-detection logic
|
53
|
+
- **BREAKING**: `SOFT_DELETE_COLUMNS` constant and pattern-matching detection
|
54
|
+
- Complex database schema introspection for soft-delete detection
|
55
|
+
|
56
|
+
### Performance
|
57
|
+
|
58
|
+
- Eliminated expensive auto-detection queries during model initialization
|
59
|
+
- Reduced computational overhead by trusting user configuration
|
60
|
+
- Simplified callback and association setup based on explicit configuration
|
61
|
+
|
62
|
+
## [0.1.0] - 2025-01-15
|
63
|
+
|
10
64
|
### Added
|
11
65
|
|
12
66
|
- Initial release of Whodunit gem
|
@@ -36,9 +90,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
36
90
|
|
37
91
|
- Rails 7.2+
|
38
92
|
- Ruby 3.1+
|
39
|
-
|
40
|
-
## [0.1.0] - TBD
|
41
|
-
|
42
|
-
### Added
|
43
|
-
|
44
|
-
- Initial gem structure and basic functionality
|
data/README.md
CHANGED
@@ -34,6 +34,23 @@ And then execute:
|
|
34
34
|
|
35
35
|
$ bundle install
|
36
36
|
|
37
|
+
### What's Next?
|
38
|
+
|
39
|
+
After installation, you have a few options:
|
40
|
+
|
41
|
+
1. **Generate Configuration & Setup** (Recommended):
|
42
|
+
```bash
|
43
|
+
whodunit install
|
44
|
+
```
|
45
|
+
This will:
|
46
|
+
- Create `config/initializers/whodunit.rb` with all available configuration options
|
47
|
+
- Optionally add `Whodunit::Stampable` to your `ApplicationRecord` for automatic stamping on all models
|
48
|
+
- Provide clear next steps for adding stamp columns to your database
|
49
|
+
|
50
|
+
2. **Quick Setup**: Jump directly to adding stamp columns to your models (see Quick Start below)
|
51
|
+
|
52
|
+
3. **Learn More**: Check the [Complete Documentation](https://kanutocd.github.io/whodunit) for advanced configuration
|
53
|
+
|
37
54
|
## Quick Start
|
38
55
|
|
39
56
|
### 1. Add Stamp Columns
|
@@ -106,14 +123,25 @@ user.updater # => User who last updated this record
|
|
106
123
|
user.deleter # => User who deleted this record (if soft-delete enabled)
|
107
124
|
```
|
108
125
|
|
109
|
-
##
|
126
|
+
## Soft-Delete Support
|
110
127
|
|
111
|
-
Whodunit automatically
|
128
|
+
Whodunit automatically tracks who deleted records when using soft-delete. Simply configure your soft-delete column:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# Most common soft-delete column (default)
|
132
|
+
config.soft_delete_column = :deleted_at
|
112
133
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
134
|
+
# For Discard gem users
|
135
|
+
config.soft_delete_column = :discarded_at
|
136
|
+
|
137
|
+
# For custom implementations
|
138
|
+
config.soft_delete_column = :archived_at
|
139
|
+
|
140
|
+
# Disable soft-delete support
|
141
|
+
config.soft_delete_column = nil
|
142
|
+
```
|
143
|
+
|
144
|
+
When configured, Whodunit will automatically add the `deleter_id` column to migrations when the soft-delete column is detected in your table.
|
117
145
|
|
118
146
|
## Configuration
|
119
147
|
|
@@ -124,7 +152,8 @@ Whodunit.configure do |config|
|
|
124
152
|
config.creator_column = :created_by_id # Default: :creator_id
|
125
153
|
config.updater_column = :updated_by_id # Default: :updater_id
|
126
154
|
config.deleter_column = :deleted_by_id # Default: :deleter_id
|
127
|
-
config.
|
155
|
+
config.soft_delete_column = :discarded_at # Default: nil
|
156
|
+
config.auto_inject_whodunit_stamps = false # Default: true
|
128
157
|
|
129
158
|
# Column data type configuration
|
130
159
|
config.column_data_type = :integer # Default: :bigint (applies to all columns)
|
@@ -142,6 +171,46 @@ By default, all stamp columns use `:bigint` data type. You can customize this in
|
|
142
171
|
- **Individual**: Set specific column types to override the global default
|
143
172
|
- **Per-migration**: Override types on a per-migration basis (see Migration Helpers)
|
144
173
|
|
174
|
+
### Automatic Injection (Rails Integration)
|
175
|
+
|
176
|
+
By default, Whodunit automatically adds stamp columns to your migrations, just like how Rails automatically handles `timestamps`:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# Automatic injection is enabled by default!
|
180
|
+
# Your migrations automatically get whodunit stamps:
|
181
|
+
class CreatePosts < ActiveRecord::Migration[8.0]
|
182
|
+
def change
|
183
|
+
create_table :posts do |t|
|
184
|
+
t.string :title
|
185
|
+
t.text :body
|
186
|
+
t.timestamps
|
187
|
+
# t.whodunit_stamps automatically added after t.timestamps!
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Disable automatic injection globally:
|
193
|
+
Whodunit.configure do |config|
|
194
|
+
config.auto_inject_whodunit_stamps = false
|
195
|
+
end
|
196
|
+
|
197
|
+
# Skip auto-injection for specific tables:
|
198
|
+
create_table :system_logs do |t|
|
199
|
+
t.string :message
|
200
|
+
t.timestamps skip_whodunit_stamps: true
|
201
|
+
end
|
202
|
+
|
203
|
+
# Or add manually if you want specific options:
|
204
|
+
create_table :posts do |t|
|
205
|
+
t.string :title
|
206
|
+
t.whodunit_stamps include_deleter: true # Manual override
|
207
|
+
t.timestamps
|
208
|
+
# No auto-injection since already added manually
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
This feature respects soft-delete auto-detection and includes the deleter column when appropriate.
|
213
|
+
|
145
214
|
## Manual User Setting
|
146
215
|
|
147
216
|
For background jobs, tests, or special scenarios:
|
data/exe/whodunit
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/whodunit/generator"
|
5
|
+
|
6
|
+
# Parse command line arguments
|
7
|
+
command = ARGV[0]
|
8
|
+
|
9
|
+
case command
|
10
|
+
when "install"
|
11
|
+
Whodunit::Generator.install_initializer
|
12
|
+
when "help", "--help", "-h", nil
|
13
|
+
puts Whodunit::Generator.help_message
|
14
|
+
else
|
15
|
+
puts "Unknown command: #{command}"
|
16
|
+
puts Whodunit::Generator.help_message
|
17
|
+
exit 1
|
18
|
+
end
|
@@ -147,8 +147,8 @@ module Whodunit
|
|
147
147
|
# without_whodunit_user do
|
148
148
|
# Post.bulk_import(data) # No creator_id will be set
|
149
149
|
# end
|
150
|
-
def without_whodunit_user(&
|
151
|
-
with_whodunit_user(nil, &
|
150
|
+
def without_whodunit_user(&)
|
151
|
+
with_whodunit_user(nil, &)
|
152
152
|
end
|
153
153
|
|
154
154
|
class_methods do
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Whodunit
|
4
|
+
class Generator
|
5
|
+
# Handles ApplicationRecord integration functionality
|
6
|
+
module ApplicationRecordIntegration
|
7
|
+
def self.handle_application_record_integration!
|
8
|
+
application_record_file = "app/models/application_record.rb"
|
9
|
+
|
10
|
+
return unless application_record_exists?(application_record_file)
|
11
|
+
|
12
|
+
content = File.read(application_record_file)
|
13
|
+
return if stampable_already_included?(content)
|
14
|
+
|
15
|
+
return unless user_wants_application_record_integration?
|
16
|
+
|
17
|
+
add_stampable_to_application_record!(application_record_file, content)
|
18
|
+
end
|
19
|
+
|
20
|
+
private_class_method def self.application_record_exists?(file_path)
|
21
|
+
return true if File.exist?(file_path)
|
22
|
+
|
23
|
+
puts "⚠️ ApplicationRecord not found at #{file_path}"
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
private_class_method def self.stampable_already_included?(content)
|
28
|
+
return false unless content.include?("Whodunit::Stampable")
|
29
|
+
|
30
|
+
puts "✅ Whodunit::Stampable already included in ApplicationRecord"
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method def self.user_wants_application_record_integration?
|
35
|
+
puts ""
|
36
|
+
puts "🤔 Do you want to include Whodunit::Stampable in ApplicationRecord?"
|
37
|
+
puts " This will automatically enable stamping for ALL your models."
|
38
|
+
puts " (You can always include it manually in specific models instead)"
|
39
|
+
print " Add to ApplicationRecord? (Y/n): "
|
40
|
+
|
41
|
+
response = $stdin.gets.chomp.downcase
|
42
|
+
!%w[n no].include?(response)
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method def self.add_stampable_to_application_record!(file_path, content)
|
46
|
+
updated_content = try_primary_pattern(content) || try_fallback_pattern(content)
|
47
|
+
|
48
|
+
if updated_content
|
49
|
+
write_updated_application_record!(file_path, updated_content)
|
50
|
+
else
|
51
|
+
show_manual_integration_message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private_class_method def self.try_primary_pattern(content)
|
56
|
+
return unless content.match?(/^(\s*class ApplicationRecord < ActiveRecord::Base\s*)$/)
|
57
|
+
|
58
|
+
content.gsub(
|
59
|
+
/^(\s*class ApplicationRecord < ActiveRecord::Base\s*)$/,
|
60
|
+
"\\1\n include Whodunit::Stampable"
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
private_class_method def self.try_fallback_pattern(content)
|
65
|
+
return unless content.match?(/^(\s*class ApplicationRecord.*\n)/)
|
66
|
+
|
67
|
+
content.gsub(
|
68
|
+
/^(\s*class ApplicationRecord.*\n)/,
|
69
|
+
"\\1 include Whodunit::Stampable\n"
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
private_class_method def self.write_updated_application_record!(file_path, content)
|
74
|
+
File.write(file_path, content)
|
75
|
+
puts "✅ Added Whodunit::Stampable to ApplicationRecord"
|
76
|
+
puts " All your models will now automatically include stamping!"
|
77
|
+
end
|
78
|
+
|
79
|
+
private_class_method def self.show_manual_integration_message
|
80
|
+
puts "⚠️ Could not automatically modify ApplicationRecord"
|
81
|
+
puts " Please manually add: include Whodunit::Stampable"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require_relative "generator/application_record_integration"
|
5
|
+
|
6
|
+
module Whodunit
|
7
|
+
# Generator for creating Whodunit configuration files
|
8
|
+
class Generator
|
9
|
+
INITIALIZER_CONTENT = <<~RUBY
|
10
|
+
# frozen_string_literal: true
|
11
|
+
|
12
|
+
# Whodunit configuration
|
13
|
+
# This file was generated by `whodunit install` command.
|
14
|
+
# Uncomment and modify the options you want to customize.
|
15
|
+
|
16
|
+
# Whodunit.configure do |config|
|
17
|
+
# # User model configuration
|
18
|
+
# # Specify which model represents users in your application
|
19
|
+
# config.user_class = 'Account' # Default: 'User'
|
20
|
+
# # Use 'Account', 'Admin', etc. if your user model has a different name
|
21
|
+
#
|
22
|
+
# # Column name configuration
|
23
|
+
# # Customize the names of the tracking columns in your database
|
24
|
+
# config.creator_column = :created_by_id # Default: :creator_id
|
25
|
+
# # Column that stores who created the record
|
26
|
+
# config.updater_column = :updated_by_id # Default: :updater_id
|
27
|
+
# # Column that stores who last updated the record
|
28
|
+
# config.deleter_column = :deleted_by_id # Default: :deleter_id
|
29
|
+
# # Column that stores who deleted the record (soft-delete only)
|
30
|
+
#
|
31
|
+
# # Soft-delete integration
|
32
|
+
# # Enable tracking of who deleted records when using soft-delete gems
|
33
|
+
# config.soft_delete_column = :discarded_at # Default: nil (disabled)
|
34
|
+
# # Set to :deleted_at for Paranoia gem
|
35
|
+
# # Set to :discarded_at for Discard gem
|
36
|
+
# # Set to your custom soft-delete column name
|
37
|
+
# # Set to nil to disable soft-delete tracking
|
38
|
+
#
|
39
|
+
# # Migration auto-injection
|
40
|
+
# # Control whether whodunit stamps are automatically added to new migrations
|
41
|
+
# config.auto_inject_whodunit_stamps = false # Default: true
|
42
|
+
# # When true, automatically adds t.whodunit_stamps to create_table migrations
|
43
|
+
# # When false, you must manually add t.whodunit_stamps to your migrations
|
44
|
+
#
|
45
|
+
# # Column data type configuration
|
46
|
+
# # Configure what data types to use for the tracking columns
|
47
|
+
# config.column_data_type = :integer # Default: :bigint
|
48
|
+
# # Global default for all stamp columns
|
49
|
+
# # Common options: :bigint, :integer, :string, :uuid
|
50
|
+
#
|
51
|
+
# # Individual column type overrides
|
52
|
+
# # Override the data type for specific columns (takes precedence over column_data_type)
|
53
|
+
# config.creator_column_type = :string # Default: nil (uses column_data_type)
|
54
|
+
# # Useful if your user IDs are strings or UUIDs
|
55
|
+
# config.updater_column_type = :uuid # Default: nil (uses column_data_type)
|
56
|
+
# # Set to match your user model's primary key type
|
57
|
+
# config.deleter_column_type = :integer # Default: nil (uses column_data_type)
|
58
|
+
# # Only used when soft_delete_column is configured
|
59
|
+
# end
|
60
|
+
RUBY
|
61
|
+
|
62
|
+
def self.install_initializer
|
63
|
+
config_dir = "config/initializers"
|
64
|
+
config_file = File.join(config_dir, "whodunit.rb")
|
65
|
+
|
66
|
+
validate_rails_application!
|
67
|
+
ensure_config_directory_exists!(config_dir)
|
68
|
+
handle_existing_file!(config_file)
|
69
|
+
create_initializer_file!(config_file)
|
70
|
+
ApplicationRecordIntegration.handle_application_record_integration!
|
71
|
+
show_success_message(config_file)
|
72
|
+
end
|
73
|
+
|
74
|
+
private_class_method def self.validate_rails_application!
|
75
|
+
return if File.exist?("config/application.rb")
|
76
|
+
|
77
|
+
puts "❌ Error: This doesn't appear to be a Rails application."
|
78
|
+
puts " Make sure you're in the root directory of your Rails app."
|
79
|
+
exit 1
|
80
|
+
end
|
81
|
+
|
82
|
+
private_class_method def self.ensure_config_directory_exists!(config_dir)
|
83
|
+
return if Dir.exist?(config_dir)
|
84
|
+
|
85
|
+
puts "📁 Creating #{config_dir} directory..."
|
86
|
+
FileUtils.mkdir_p(config_dir)
|
87
|
+
end
|
88
|
+
|
89
|
+
private_class_method def self.handle_existing_file!(config_file)
|
90
|
+
return unless File.exist?(config_file)
|
91
|
+
|
92
|
+
puts "⚠️ #{config_file} already exists!"
|
93
|
+
print " Do you want to overwrite it? (y/N): "
|
94
|
+
response = $stdin.gets.chomp.downcase
|
95
|
+
return if %w[y yes].include?(response)
|
96
|
+
|
97
|
+
puts " Cancelled."
|
98
|
+
exit 0
|
99
|
+
end
|
100
|
+
|
101
|
+
private_class_method def self.create_initializer_file!(config_file)
|
102
|
+
File.write(config_file, INITIALIZER_CONTENT)
|
103
|
+
puts "✅ Generated #{config_file}"
|
104
|
+
end
|
105
|
+
|
106
|
+
private_class_method def self.show_success_message(config_file)
|
107
|
+
puts ""
|
108
|
+
puts "📝 Next steps:"
|
109
|
+
puts " 1. Edit #{config_file} to customize your configuration"
|
110
|
+
puts " 2. Uncomment the options you want to change"
|
111
|
+
puts " 3. Add stamp columns to your models with migrations:"
|
112
|
+
puts " rails generate migration AddStampsToUsers"
|
113
|
+
puts " # Then add: add_whodunit_stamps :users"
|
114
|
+
puts " 4. Restart your Rails server to apply changes"
|
115
|
+
puts ""
|
116
|
+
puts "📖 For more information, see: https://github.com/kanutocd/whodunit?tab=readme-ov-file#configuration"
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.help_message
|
120
|
+
<<~HELP
|
121
|
+
Whodunit - Lightweight creator/updater/deleter tracking for ActiveRecord
|
122
|
+
|
123
|
+
Usage:
|
124
|
+
whodunit install Generate config/initializers/whodunit.rb
|
125
|
+
whodunit help Show this help message
|
126
|
+
|
127
|
+
Examples:
|
128
|
+
whodunit install # Creates config/initializers/whodunit.rb with sample configuration
|
129
|
+
|
130
|
+
For more information, visit: https://github.com/kanutocd/whodunit
|
131
|
+
HELP
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|