Module: Whodunit::Stampable

Extended by:
ActiveSupport::Concern
Defined in:
lib/whodunit/stampable.rb

Overview

Main concern for adding creator/updater/deleter tracking to ActiveRecord models.

This module provides automatic tracking of who created, updated, and deleted records. It intelligently sets up callbacks and associations based on available columns and soft-delete detection.

rubocop:disable Metrics/ModuleLength

Examples:

Basic usage

class Post < ApplicationRecord
  include Whodunit::Stampable
end

# Requires creator_id and/or updater_id columns
# Automatically adds deleter_id tracking if soft-delete is detected

Migration

class CreatePosts < ActiveRecord::Migration[7.0]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :body
      t.whodunit_stamps  # Adds creator_id, updater_id columns
      t.timestamps
    end
  end
end

Manual deleter tracking

class Post < ApplicationRecord
  include Whodunit::Stampable

  # Force enable deleter tracking even without soft-delete
  enable_whodunit_deleter!
end

Since:

  • 0.1.0

Callback Methods collapse

Column Presence Checks collapse

Instance Method Details

#being_soft_deleted?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if the current update operation is a soft-delete.

Uses ActiveRecord’s dirty tracking to detect if any soft-delete columns are being changed from nil to a timestamp, which indicates a soft-delete operation.

Returns:

  • (Boolean)

    true if this update is setting a soft-delete timestamp

Since:

  • 0.1.0



278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/whodunit/stampable.rb', line 278

def being_soft_deleted?
  return false unless deleter_column?
  return false unless Whodunit::Current.user_id

  soft_delete_column = self.class.whodunit_setting(:soft_delete_column)
  return false unless soft_delete_column

  # Simple: just check the configured soft-delete column
  column_name = soft_delete_column.to_s
  attribute_changed?(column_name) &&
    attribute_was(column_name).nil? &&
    !send(column_name).nil?
end

#creator_column?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if the model has a creator column and it’s enabled.

Returns:

  • (Boolean)

    true if the creator column exists and is enabled

Since:

  • 0.1.0



242
243
244
245
246
247
# File 'lib/whodunit/stampable.rb', line 242

def creator_column?
  return false unless self.class.model_creator_enabled?

  column_name = self.class.whodunit_setting(:creator_column).to_s
  self.class.column_names.include?(column_name)
end

#deleter_column?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if the model has a deleter column and it’s enabled.

Returns:

  • (Boolean)

    true if the deleter column exists and is enabled

Since:

  • 0.1.0



264
265
266
267
268
269
# File 'lib/whodunit/stampable.rb', line 264

def deleter_column?
  return false unless self.class.model_deleter_enabled?

  column_name = self.class.whodunit_setting(:deleter_column).to_s
  self.class.column_names.include?(column_name)
end

#set_whodunit_creatorvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Set the creator ID when a record is created.

This method is automatically called before_create if the model has a creator column. It sets the creator_id to the current user from Whodunit::Current.

Since:

  • 0.1.0



196
197
198
199
200
# File 'lib/whodunit/stampable.rb', line 196

def set_whodunit_creator
  return unless Whodunit::Current.user_id

  self[self.class.whodunit_setting(:creator_column)] = Whodunit::Current.user_id
end

#set_whodunit_deletervoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Set the deleter ID when a record is destroyed or soft-deleted.

This method is automatically called in two scenarios: 1. before_destroy for hard deletes (if deleter column exists) 2. before_update for soft-deletes (when being_soft_deleted? returns true)

It sets the deleter_id to the current user from Whodunit::Current.

Since:

  • 0.1.0



230
231
232
233
234
# File 'lib/whodunit/stampable.rb', line 230

def set_whodunit_deleter
  return unless Whodunit::Current.user_id

  self[self.class.whodunit_setting(:deleter_column)] = Whodunit::Current.user_id
end

#set_whodunit_updatervoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Set the updater ID when a record is updated.

This method is automatically called before_update if the model has an updater column. It sets the updater_id to the current user from Whodunit::Current. Does not run on new records (creation) or during soft-delete operations.

Since:

  • 0.1.0



210
211
212
213
214
215
216
217
218
# File 'lib/whodunit/stampable.rb', line 210

def set_whodunit_updater
  return unless Whodunit::Current.user_id

  return if new_record? # Don't set updater on creation

  return if being_soft_deleted? # Don't set updater during soft-delete

  self[self.class.whodunit_setting(:updater_column)] = Whodunit::Current.user_id
end

#updater_column?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if the model has an updater column and it’s enabled.

Returns:

  • (Boolean)

    true if the updater column exists and is enabled

Since:

  • 0.1.0



253
254
255
256
257
258
# File 'lib/whodunit/stampable.rb', line 253

def updater_column?
  return false unless self.class.model_updater_enabled?

  column_name = self.class.whodunit_setting(:updater_column).to_s
  self.class.column_names.include?(column_name)
end