whodunit 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +46 -0
- data/README.md +77 -1
- data/lib/whodunit/generator/application_record_integration.rb +85 -0
- data/lib/whodunit/generator.rb +38 -3
- data/lib/whodunit/stampable.rb +29 -0
- data/lib/whodunit/version.rb +1 -1
- data/lib/whodunit.rb +154 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 323bc72651528d7bf5e205c0388462078acc718ea39b14b5775517644a2115fc
|
4
|
+
data.tar.gz: d20344c823d5716ae218497ca92f61f41c6660c8e8f27a5479b0e52bc64b6b25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f893afd5307dfa5cdabd58c8de25baa2e979809f9d2c810cdb5dd029a90ebb07f79a61ab24c149e9545159aafef09b3494e2fe15cdee93a6c66e1ae35c44bd2e
|
7
|
+
data.tar.gz: 9df8c395516af0720e71fd2a5e8b03a3471f4a85684f8b8a7b274222f74a19feeb1bb9a2b5b3e4795fbcf8938aef769a87e1dd380668838198efad31215d4e3b
|
data/.rubocop.yml
CHANGED
@@ -13,6 +13,7 @@ AllCops:
|
|
13
13
|
- 'coverage/**/*'
|
14
14
|
- 'bin/**/*'
|
15
15
|
- 'sig/**/*'
|
16
|
+
- 'example/**/*'
|
16
17
|
|
17
18
|
# Layout & Formatting
|
18
19
|
Layout/LineLength:
|
@@ -37,9 +38,17 @@ Metrics/MethodLength:
|
|
37
38
|
Exclude:
|
38
39
|
- 'spec/**/*'
|
39
40
|
|
41
|
+
# Allow longer modules for main configuration
|
42
|
+
Metrics/ModuleLength:
|
43
|
+
Exclude:
|
44
|
+
- 'lib/whodunit.rb'
|
45
|
+
|
40
46
|
# RSpec specific configurations
|
41
47
|
RSpec/ExampleLength:
|
42
48
|
Max: 15
|
49
|
+
Exclude:
|
50
|
+
- 'spec/whodunit/reverse_associations_integration_spec.rb'
|
51
|
+
- 'spec/whodunit/reverse_associations_spec.rb'
|
43
52
|
|
44
53
|
RSpec/MultipleExpectations:
|
45
54
|
Max: 5
|
@@ -53,10 +62,21 @@ RSpec/ContextWording:
|
|
53
62
|
RSpec/DescribeClass:
|
54
63
|
Exclude:
|
55
64
|
- 'spec/integration/**/*'
|
65
|
+
- 'spec/whodunit/reverse_associations_integration_spec.rb'
|
56
66
|
|
57
67
|
RSpec/VerifiedDoubles:
|
58
68
|
Enabled: false
|
59
69
|
|
70
|
+
RSpec/MessageSpies:
|
71
|
+
Exclude:
|
72
|
+
- 'spec/whodunit/reverse_associations_integration_spec.rb'
|
73
|
+
- 'spec/whodunit/reverse_associations_spec.rb'
|
74
|
+
- 'spec/whodunit/stampable_spec.rb'
|
75
|
+
|
76
|
+
RSpec/SpecFilePathFormat:
|
77
|
+
Exclude:
|
78
|
+
- 'spec/whodunit/reverse_associations_spec.rb'
|
79
|
+
|
60
80
|
# Allow positional boolean arguments for standard Ruby method signatures
|
61
81
|
Style/OptionalBooleanParameter:
|
62
82
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.3.0] - 2025-01-24
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- **Automatic Reverse Associations**: When models include `Whodunit::Stampable`, reverse associations are automatically created on the user class (e.g., `user.created_posts`, `user.updated_comments`, `user.deleted_documents`)
|
15
|
+
- **Model Registry System**: Tracks models that include Stampable for automatic reverse association setup
|
16
|
+
- **Per-Model Reverse Association Control**: Models can disable reverse associations with `disable_whodunit_reverse_associations!`
|
17
|
+
- **Reverse Association Configuration**: Global configuration options including `auto_setup_reverse_associations`, `reverse_association_prefix`, and `reverse_association_suffix`
|
18
|
+
- **Smart Model Capability Detection**: Automatically detects which associations to create based on model capabilities (creator, updater, deleter, soft-delete)
|
19
|
+
- **Custom Column Support**: Reverse associations respect per-model custom column configurations
|
20
|
+
- **Manual Setup Methods**: `setup_whodunit_reverse_associations!` and `setup_all_reverse_associations` for manual control
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
|
24
|
+
- **Enhanced Stampable Module**: Now automatically registers models for reverse association setup when included
|
25
|
+
- **Improved Configuration**: Extended main configuration module with reverse association settings
|
26
|
+
- **Better Association Management**: Prevents duplicate associations and handles edge cases gracefully
|
27
|
+
|
28
|
+
### Features
|
29
|
+
|
30
|
+
- **Zero Configuration**: Reverse associations work automatically with sensible defaults
|
31
|
+
- **Thread Safe**: Built on existing thread-safe architecture
|
32
|
+
- **Flexible Naming**: Configurable prefixes and suffixes for association names
|
33
|
+
- **Comprehensive Testing**: Full test coverage for all reverse association functionality
|
34
|
+
- **RuboCop Compliant**: All code follows project style guidelines
|
35
|
+
|
36
|
+
## [0.2.1] - 2025-01-21
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- **ApplicationRecord Integration**: `whodunit install` now prompts to automatically add `Whodunit::Stampable` to `ApplicationRecord` for convenient all-model stamping
|
41
|
+
- **Enhanced Configuration Template**: Detailed explanations and examples for each configuration option in generated initializer
|
42
|
+
- **Post-install Message**: Helpful instructions displayed after gem installation via `bundle add whodunit`
|
43
|
+
- **Comprehensive Test Coverage**: Full test suite for ApplicationRecord integration with edge case handling
|
44
|
+
|
45
|
+
### Changed
|
46
|
+
|
47
|
+
- **Improved CLI Experience**: More user-friendly prompts and messages throughout the installation process
|
48
|
+
- **Better Documentation**: Updated README with corrected installation commands and new ApplicationRecord feature
|
49
|
+
- **Code Organization**: Extracted ApplicationRecord integration logic into separate module for better maintainability
|
50
|
+
|
51
|
+
### Fixed
|
52
|
+
|
53
|
+
- **Gemspec Consistency**: Corrected post-install message to show `whodunit install` instead of incorrect Rails generator command
|
54
|
+
- **RuboCop Compliance**: Fixed all style issues and reduced complexity across codebase
|
55
|
+
|
10
56
|
## [0.2.0] - 2025-01-20
|
11
57
|
|
12
58
|
### Added
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Whodunit
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/whodunit)
|
4
|
+
[](https://github.com/kanutocd/whodunit/actions)
|
5
|
+
[](https://codecov.io/gh/kanutocd/whodunit)
|
6
|
+
[](https://www.ruby-lang.org/en/)
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
8
|
+
|
3
9
|
Lightweight creator/updater/deleter tracking for Rails ActiveRecord models.
|
4
10
|
|
5
11
|
> **Fun Fact**: The term "whodunit" was coined by literary critic Donald Gordon in 1930 when reviewing a murder mystery novel for _American News of Books_. He described Milward Kennedy's _Half Mast Murder_ as "a satisfactory whodunit" - the first recorded use of this now-famous term for mystery stories! _([Source: Wikipedia](https://en.wikipedia.org/wiki/Whodunit))_
|
@@ -34,6 +40,26 @@ And then execute:
|
|
34
40
|
|
35
41
|
$ bundle install
|
36
42
|
|
43
|
+
### What's Next?
|
44
|
+
|
45
|
+
After installation, you have a few options:
|
46
|
+
|
47
|
+
1. **Generate Configuration & Setup** (Recommended):
|
48
|
+
|
49
|
+
```bash
|
50
|
+
whodunit install
|
51
|
+
```
|
52
|
+
|
53
|
+
This will:
|
54
|
+
|
55
|
+
- Create `config/initializers/whodunit.rb` with all available configuration options
|
56
|
+
- Optionally add `Whodunit::Stampable` to your `ApplicationRecord` for automatic stamping on all models
|
57
|
+
- Provide clear next steps for adding stamp columns to your database
|
58
|
+
|
59
|
+
2. **Quick Setup**: Jump directly to adding stamp columns to your models (see Quick Start below)
|
60
|
+
|
61
|
+
3. **Learn More**: Check the [Complete Documentation](https://kanutocd.github.io/whodunit) for advanced configuration
|
62
|
+
|
37
63
|
## Quick Start
|
38
64
|
|
39
65
|
### 1. Add Stamp Columns
|
@@ -106,6 +132,51 @@ user.updater # => User who last updated this record
|
|
106
132
|
user.deleter # => User who deleted this record (if soft-delete enabled)
|
107
133
|
```
|
108
134
|
|
135
|
+
## Reverse Associations (Automatic)
|
136
|
+
|
137
|
+
Whodunit automatically sets up reverse associations on your User model when you include `Whodunit::Stampable` in other models:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
# When you include Whodunit::Stampable in Post model:
|
141
|
+
class Post < ApplicationRecord
|
142
|
+
include Whodunit::Stampable
|
143
|
+
end
|
144
|
+
|
145
|
+
# Your User model automatically gets these associations:
|
146
|
+
user.created_posts # => Posts created by this user
|
147
|
+
user.updated_posts # => Posts last updated by this user
|
148
|
+
user.deleted_posts # => Posts deleted by this user (if soft-delete enabled)
|
149
|
+
```
|
150
|
+
|
151
|
+
### Configuring Reverse Associations
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
# config/initializers/whodunit.rb
|
155
|
+
Whodunit.configure do |config|
|
156
|
+
# Disable automatic reverse associations globally
|
157
|
+
config.auto_setup_reverse_associations = false
|
158
|
+
|
159
|
+
# Customize association names with prefix/suffix
|
160
|
+
config.reverse_association_prefix = "whodunit_"
|
161
|
+
config.reverse_association_suffix = "_tracked"
|
162
|
+
# Results in: user.whodunit_created_posts_tracked
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### Per-Model Control
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class Post < ApplicationRecord
|
170
|
+
include Whodunit::Stampable
|
171
|
+
|
172
|
+
# Disable reverse associations for this model only
|
173
|
+
disable_whodunit_reverse_associations!
|
174
|
+
end
|
175
|
+
|
176
|
+
# Or manually set up later
|
177
|
+
Post.setup_whodunit_reverse_associations!
|
178
|
+
```
|
179
|
+
|
109
180
|
## Soft-Delete Support
|
110
181
|
|
111
182
|
Whodunit automatically tracks who deleted records when using soft-delete. Simply configure your soft-delete column:
|
@@ -138,6 +209,11 @@ Whodunit.configure do |config|
|
|
138
209
|
config.soft_delete_column = :discarded_at # Default: nil
|
139
210
|
config.auto_inject_whodunit_stamps = false # Default: true
|
140
211
|
|
212
|
+
# Reverse association configuration
|
213
|
+
config.auto_setup_reverse_associations = false # Default: true
|
214
|
+
config.reverse_association_prefix = "track_" # Default: ""
|
215
|
+
config.reverse_association_suffix = "_logs" # Default: ""
|
216
|
+
|
141
217
|
# Column data type configuration
|
142
218
|
config.column_data_type = :integer # Default: :bigint (applies to all columns)
|
143
219
|
config.creator_column_type = :string # Default: nil (uses column_data_type)
|
@@ -327,7 +403,7 @@ end
|
|
327
403
|
| Feature | Whodunit | PaperTrail | Audited |
|
328
404
|
| --------------------- | -------- | ---------- | ------- |
|
329
405
|
| User tracking | ✅ | ✅ | ✅ |
|
330
|
-
| Change history |
|
406
|
+
| Change history | Via [whodunit-chronicles](https://github.com/kanutocd/whodunit-chronicles) | ✅ | ✅ |
|
331
407
|
| Performance overhead | None | High | Medium |
|
332
408
|
| Soft-delete detection | ✅ | ❌ | ❌ |
|
333
409
|
| Setup complexity | Low | Medium | Medium |
|
@@ -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
|
data/lib/whodunit/generator.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
|
+
require_relative "generator/application_record_integration"
|
4
5
|
|
5
6
|
module Whodunit
|
6
7
|
# Generator for creating Whodunit configuration files
|
@@ -13,18 +14,48 @@ module Whodunit
|
|
13
14
|
# Uncomment and modify the options you want to customize.
|
14
15
|
|
15
16
|
# Whodunit.configure do |config|
|
17
|
+
# # User model configuration
|
18
|
+
# # Specify which model represents users in your application
|
16
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
|
17
24
|
# config.creator_column = :created_by_id # Default: :creator_id
|
25
|
+
# # Column that stores who created the record
|
18
26
|
# config.updater_column = :updated_by_id # Default: :updater_id
|
27
|
+
# # Column that stores who last updated the record
|
19
28
|
# config.deleter_column = :deleted_by_id # Default: :deleter_id
|
20
|
-
#
|
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
|
21
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
|
22
44
|
#
|
23
45
|
# # Column data type configuration
|
24
|
-
#
|
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)
|
25
53
|
# config.creator_column_type = :string # Default: nil (uses column_data_type)
|
54
|
+
# # Useful if your user IDs are strings or UUIDs
|
26
55
|
# config.updater_column_type = :uuid # Default: nil (uses column_data_type)
|
56
|
+
# # Set to match your user model's primary key type
|
27
57
|
# config.deleter_column_type = :integer # Default: nil (uses column_data_type)
|
58
|
+
# # Only used when soft_delete_column is configured
|
28
59
|
# end
|
29
60
|
RUBY
|
30
61
|
|
@@ -36,6 +67,7 @@ module Whodunit
|
|
36
67
|
ensure_config_directory_exists!(config_dir)
|
37
68
|
handle_existing_file!(config_file)
|
38
69
|
create_initializer_file!(config_file)
|
70
|
+
ApplicationRecordIntegration.handle_application_record_integration!
|
39
71
|
show_success_message(config_file)
|
40
72
|
end
|
41
73
|
|
@@ -76,7 +108,10 @@ module Whodunit
|
|
76
108
|
puts "📝 Next steps:"
|
77
109
|
puts " 1. Edit #{config_file} to customize your configuration"
|
78
110
|
puts " 2. Uncomment the options you want to change"
|
79
|
-
puts " 3.
|
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"
|
80
115
|
puts ""
|
81
116
|
puts "📖 For more information, see: https://github.com/kanutocd/whodunit?tab=readme-ov-file#configuration"
|
82
117
|
end
|
data/lib/whodunit/stampable.rb
CHANGED
@@ -52,6 +52,10 @@ module Whodunit
|
|
52
52
|
|
53
53
|
# Set up associations - call on the class
|
54
54
|
setup_whodunit_associations
|
55
|
+
|
56
|
+
# Register this model for reverse association setup
|
57
|
+
# This happens immediately, but the check for enabled status is done in register_model
|
58
|
+
Whodunit.register_model(self)
|
55
59
|
end
|
56
60
|
|
57
61
|
class_methods do # rubocop:disable Metrics/BlockLength
|
@@ -82,6 +86,31 @@ module Whodunit
|
|
82
86
|
@soft_delete_enabled = false
|
83
87
|
end
|
84
88
|
|
89
|
+
# Disable reverse association setup for this model
|
90
|
+
# Call this method in the model class to prevent automatic reverse associations
|
91
|
+
# @example
|
92
|
+
# class Post < ApplicationRecord
|
93
|
+
# include Whodunit::Stampable
|
94
|
+
# disable_whodunit_reverse_associations!
|
95
|
+
# end
|
96
|
+
def disable_whodunit_reverse_associations!
|
97
|
+
@whodunit_reverse_associations_disabled = true
|
98
|
+
# Remove from registered models if already registered
|
99
|
+
Whodunit.registered_models.delete(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if reverse associations are enabled for this model
|
103
|
+
# @return [Boolean] true if reverse associations should be set up
|
104
|
+
def whodunit_reverse_associations_enabled?
|
105
|
+
!@whodunit_reverse_associations_disabled
|
106
|
+
end
|
107
|
+
|
108
|
+
# Manually set up reverse associations for this model
|
109
|
+
# Can be called if reverse associations were disabled but you want to set them up later
|
110
|
+
def setup_whodunit_reverse_associations!
|
111
|
+
Whodunit.setup_reverse_associations_for_model(self) if whodunit_reverse_associations_enabled?
|
112
|
+
end
|
113
|
+
|
85
114
|
# Get effective configuration value (model override or global default)
|
86
115
|
def whodunit_setting(key)
|
87
116
|
return @whodunit_config_overrides[key] if @whodunit_config_overrides&.key?(key)
|
data/lib/whodunit/version.rb
CHANGED
data/lib/whodunit.rb
CHANGED
@@ -83,6 +83,31 @@ module Whodunit
|
|
83
83
|
# @return [Symbol, nil] the deleter column data type
|
84
84
|
mattr_accessor :deleter_column_type, default: nil
|
85
85
|
|
86
|
+
# @!group Reverse Association Configuration
|
87
|
+
|
88
|
+
# Whether to automatically set up reverse associations on the user class (default: true)
|
89
|
+
# When enabled, including Whodunit::Stampable in a model will automatically add
|
90
|
+
# has_many associations to the user class (e.g., has_many :created_posts)
|
91
|
+
# @return [Boolean] auto reverse association setting
|
92
|
+
mattr_accessor :auto_setup_reverse_associations, default: true
|
93
|
+
|
94
|
+
# Prefix for reverse association names (default: "")
|
95
|
+
# Used to generate association names like "created_posts", "updated_comments"
|
96
|
+
# @return [String] the prefix for reverse association names
|
97
|
+
mattr_accessor :reverse_association_prefix, default: ""
|
98
|
+
|
99
|
+
# Suffix for reverse association names (default: "")
|
100
|
+
# Used to generate association names like "posts_created", "comments_updated"
|
101
|
+
# @return [String] the suffix for reverse association names
|
102
|
+
mattr_accessor :reverse_association_suffix, default: ""
|
103
|
+
|
104
|
+
# @!group Model Registry
|
105
|
+
|
106
|
+
# Registry to track models that include Whodunit::Stampable
|
107
|
+
# This is used to set up reverse associations on the user class
|
108
|
+
# @return [Array<Class>] array of model classes that include Stampable
|
109
|
+
mattr_accessor :registered_models, default: []
|
110
|
+
|
86
111
|
# Configure Whodunit settings
|
87
112
|
#
|
88
113
|
# @example
|
@@ -151,6 +176,135 @@ module Whodunit
|
|
151
176
|
!deleter_column.nil?
|
152
177
|
end
|
153
178
|
|
179
|
+
# @!group Model Registration & Reverse Associations
|
180
|
+
|
181
|
+
# Register a model class that includes Whodunit::Stampable
|
182
|
+
# This is called automatically when Stampable is included
|
183
|
+
# @param [Class] model_class the model class to register
|
184
|
+
# @return [void]
|
185
|
+
def self.register_model(model_class)
|
186
|
+
return unless auto_setup_reverse_associations
|
187
|
+
return if registered_models.include?(model_class)
|
188
|
+
return if model_class.respond_to?(:whodunit_reverse_associations_enabled?) &&
|
189
|
+
!model_class.whodunit_reverse_associations_enabled?
|
190
|
+
|
191
|
+
registered_models << model_class
|
192
|
+
setup_reverse_associations_for_model(model_class)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Set up reverse associations on the user class for a specific model
|
196
|
+
# @param [Class] model_class the model class to set up reverse associations for
|
197
|
+
# @return [void]
|
198
|
+
def self.setup_reverse_associations_for_model(model_class)
|
199
|
+
return unless auto_setup_reverse_associations
|
200
|
+
|
201
|
+
user_class_instance = resolve_user_class
|
202
|
+
return unless user_class_instance.respond_to?(:has_many)
|
203
|
+
|
204
|
+
model_plural = model_class.name.underscore.pluralize
|
205
|
+
|
206
|
+
setup_creator_reverse_association(user_class_instance, model_class, model_plural)
|
207
|
+
setup_updater_reverse_association(user_class_instance, model_class, model_plural)
|
208
|
+
setup_deleter_reverse_association(user_class_instance, model_class, model_plural)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Set up all reverse associations for all registered models
|
212
|
+
# This can be called manually if needed (e.g., after configuration changes)
|
213
|
+
# @return [void]
|
214
|
+
def self.setup_all_reverse_associations
|
215
|
+
registered_models.each do |model_class|
|
216
|
+
setup_reverse_associations_for_model(model_class)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Generate a reverse association name based on action and model name
|
221
|
+
# @param [String] action the action (created, updated, deleted)
|
222
|
+
# @param [String] model_plural the pluralized model name
|
223
|
+
# @return [String] the generated association name
|
224
|
+
def self.generate_reverse_association_name(action, model_plural)
|
225
|
+
"#{reverse_association_prefix}#{action}_#{model_plural}#{reverse_association_suffix}"
|
226
|
+
end
|
227
|
+
|
228
|
+
# Resolve the user class constant
|
229
|
+
# @return [Class, nil] the user class or nil if not found
|
230
|
+
def self.resolve_user_class
|
231
|
+
user_class.constantize
|
232
|
+
rescue StandardError
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
236
|
+
# Set up creator reverse association
|
237
|
+
# @param [Class] user_class_instance the user class
|
238
|
+
# @param [Class] model_class the model class
|
239
|
+
# @param [String] model_plural the pluralized model name
|
240
|
+
# @return [void]
|
241
|
+
def self.setup_creator_reverse_association(user_class_instance, model_class, model_plural)
|
242
|
+
return unless model_class.respond_to?(:model_creator_enabled?) && model_class.model_creator_enabled?
|
243
|
+
|
244
|
+
association_name = generate_reverse_association_name("created", model_plural)
|
245
|
+
setup_user_reverse_association(user_class_instance, association_name, model_class, :creator_id)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Set up updater reverse association
|
249
|
+
# @param [Class] user_class_instance the user class
|
250
|
+
# @param [Class] model_class the model class
|
251
|
+
# @param [String] model_plural the pluralized model name
|
252
|
+
# @return [void]
|
253
|
+
def self.setup_updater_reverse_association(user_class_instance, model_class, model_plural)
|
254
|
+
return unless model_class.respond_to?(:model_updater_enabled?) && model_class.model_updater_enabled?
|
255
|
+
|
256
|
+
association_name = generate_reverse_association_name("updated", model_plural)
|
257
|
+
setup_user_reverse_association(user_class_instance, association_name, model_class, :updater_id)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Set up deleter reverse association
|
261
|
+
# @param [Class] user_class_instance the user class
|
262
|
+
# @param [Class] model_class the model class
|
263
|
+
# @param [String] model_plural the pluralized model name
|
264
|
+
# @return [void]
|
265
|
+
def self.setup_deleter_reverse_association(user_class_instance, model_class, model_plural)
|
266
|
+
return unless model_class.respond_to?(:model_deleter_enabled?) && model_class.model_deleter_enabled?
|
267
|
+
return unless model_class.respond_to?(:soft_delete_enabled?) && model_class.soft_delete_enabled?
|
268
|
+
|
269
|
+
association_name = generate_reverse_association_name("deleted", model_plural)
|
270
|
+
setup_user_reverse_association(user_class_instance, association_name, model_class, :deleter_id)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Set up a specific reverse association on the user class
|
274
|
+
# @param [Class] user_class_instance the user class
|
275
|
+
# @param [String] association_name the name of the association
|
276
|
+
# @param [Class] model_class the model class
|
277
|
+
# @param [Symbol] foreign_key_column the foreign key column name
|
278
|
+
# @return [void]
|
279
|
+
def self.setup_user_reverse_association(user_class_instance, association_name, model_class, foreign_key_column)
|
280
|
+
actual_foreign_key = resolve_foreign_key(model_class, foreign_key_column)
|
281
|
+
|
282
|
+
# Check if association already exists to avoid duplicates
|
283
|
+
return if user_class_instance.reflect_on_association(association_name.to_sym)
|
284
|
+
|
285
|
+
user_class_instance.has_many association_name.to_sym,
|
286
|
+
class_name: model_class.name,
|
287
|
+
foreign_key: actual_foreign_key,
|
288
|
+
dependent: :nullify
|
289
|
+
end
|
290
|
+
|
291
|
+
# Resolve the actual foreign key column name from model configuration
|
292
|
+
# @param [Class] model_class the model class
|
293
|
+
# @param [Symbol] foreign_key_column the default foreign key column
|
294
|
+
# @return [Symbol] the actual foreign key column name
|
295
|
+
def self.resolve_foreign_key(model_class, foreign_key_column)
|
296
|
+
return foreign_key_column unless model_class.respond_to?(:whodunit_setting)
|
297
|
+
|
298
|
+
column_mapping = {
|
299
|
+
creator_id: :creator_column,
|
300
|
+
updater_id: :updater_column,
|
301
|
+
deleter_id: :deleter_column
|
302
|
+
}
|
303
|
+
|
304
|
+
setting_key = column_mapping[foreign_key_column]
|
305
|
+
setting_key ? (model_class.whodunit_setting(setting_key) || foreign_key_column) : foreign_key_column
|
306
|
+
end
|
307
|
+
|
154
308
|
# Validate that column configuration is valid
|
155
309
|
# @raise [Whodunit::Error] if both creator_column and updater_column are nil
|
156
310
|
def self.validate_column_configuration!
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whodunit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ken C. Demanawa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -217,6 +217,7 @@ files:
|
|
217
217
|
- lib/whodunit/controller_methods.rb
|
218
218
|
- lib/whodunit/current.rb
|
219
219
|
- lib/whodunit/generator.rb
|
220
|
+
- lib/whodunit/generator/application_record_integration.rb
|
220
221
|
- lib/whodunit/migration_helpers.rb
|
221
222
|
- lib/whodunit/railtie.rb
|
222
223
|
- lib/whodunit/stampable.rb
|
@@ -234,7 +235,11 @@ metadata:
|
|
234
235
|
bug_tracker_uri: https://github.com/kanutocd/whodunit/issues
|
235
236
|
documentation_uri: https://kanutocd.github.io/whodunit
|
236
237
|
rubygems_mfa_required: 'true'
|
237
|
-
post_install_message:
|
238
|
+
post_install_message: "\U0001F389 Thanks for installing Whodunit!\n\nWhat's next?\n\n1.
|
239
|
+
Generate configuration (recommended):\n whodunit install\n\n2. Add stamp columns
|
240
|
+
to your models:\n rails generate migration AddStampsToUsers\n # Then add: add_whodunit_stamps
|
241
|
+
:users\n\n3. Include Whodunit::Stampable in your models:\n class User < ApplicationRecord\n
|
242
|
+
\ include Whodunit::Stampable\n end\n\n\U0001F4D6 Complete documentation: https://kanutocd.github.io/whodunit\n"
|
238
243
|
rdoc_options: []
|
239
244
|
require_paths:
|
240
245
|
- lib
|