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



321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/whodunit/stampable.rb', line 321

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



285
286
287
288
289
290
# File 'lib/whodunit/stampable.rb', line 285

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



307
308
309
310
311
312
# File 'lib/whodunit/stampable.rb', line 307

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



239
240
241
242
243
# File 'lib/whodunit/stampable.rb', line 239

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



273
274
275
276
277
# File 'lib/whodunit/stampable.rb', line 273

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



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

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



296
297
298
299
300
301
# File 'lib/whodunit/stampable.rb', line 296

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