well_formed-paper_trail

PaperTrail versioning integration for WellFormed form objects.

Automatically sets PaperTrail.request.whodunnit to the form's user around every save and perform, so version records are attributed to the correct user without any controller wiring.

Installation

bundle add well_formed-paper_trail

Usage

Require the gem in your application:

require "well_formed-paper_trail"

WellFormed::PaperTrail is automatically included into all WellFormed forms — no include required. With PaperTrail configured on your models, versions are immediately attributed to the form's user:

class UpdateArticleForm < WellFormed::ResourceForm
  resource_alias :article

  attribute :title, :string
  attribute :body,  :string

  validates :title, presence: true
end

form = UpdateArticleForm.new(article, current_user, article_params)
form.save
# => PaperTrail::Version created with whodunnit: current_user.id.to_s

Works for ActionForm too — any model changes made inside perform are attributed correctly:

class PublishArticleForm < WellFormed::ActionForm
  resource_alias :article

  def perform
    article.publish!   # triggers a PaperTrail version attributed to user
  end
end

Custom whodunnit

By default, whodunnit is set to user&.id&.to_s. There are two ways to override this.

Global default

Set a global proc in an initializer. It receives the form's user as its argument:

# config/initializers/well_formed_paper_trail.rb
WellFormed::PaperTrail.whodunnit = ->(user) { user&.email }

Per-form override

Use paper_trail_whodunnit to override on a specific form class. The block is evaluated in the context of the form instance, so all form attributes and helpers (including user) are available:

class UpdateArticleForm < WellFormed::ResourceForm
  paper_trail_whodunnit { user.email }
end
class AdminUpdateForm < WellFormed::ResourceForm
  paper_trail_whodunnit { "admin:#{user.id}" }
end

The per-form macro takes precedence over the global config. It is also inherited by subclasses and can be overridden per subclass:

class BaseForm < WellFormed::ResourceForm
  paper_trail_whodunnit { user.email }
end

class AuditedForm < BaseForm
  paper_trail_whodunnit { "#{user.role}:#{user.id}" }  # overrides parent
end

The priority order is: per-form macro → global config → user&.id&.to_s.

API

Method Description
WellFormed::PaperTrail.whodunnit = ->(user) { ... } Global default — proc receives user, return value used as whodunnit
paper_trail_whodunnit { ... } Per-form macro — block evaluated on the form instance, takes precedence over global config. Inherits from superclass.

How it works

whodunnit is set via PaperTrail.request(whodunnit:) { ... } inside an around_save callback (for ResourceForm and Struct) or an around_perform callback (for ActionForm). This is the thread-safe, block-scoped API recommended by PaperTrail — the previous whodunnit value is always restored after the block exits, even if an exception is raised.

License

MIT