mirror of
https://github.com/paper-trail-gem/paper_trail.git
synced 2022-11-09 11:33:19 -05:00
Store user-defined metadata.
This commit is contained in:
parent
e71252ce29
commit
55185cd448
4 changed files with 110 additions and 11 deletions
21
README.md
21
README.md
|
@ -6,11 +6,13 @@ PaperTrail lets you track changes to your models' data. It's good for auditing
|
|||
## Features
|
||||
|
||||
* Stores every create, update and destroy.
|
||||
* Does not store updates which don't change anything (or which only change attributes you are ignoring).
|
||||
* Does not store updates which don't change anything.
|
||||
* Does not store updates which only change attributes you are ignoring.
|
||||
* Allows you to get at every version, including the original, even once destroyed.
|
||||
* Allows you to get at every version even if the schema has since changed.
|
||||
* Automatically records who was responsible if your controller has a `current_user` method.
|
||||
* Allows you to set who is responsible at model-level (useful for migrations).
|
||||
* Allows you to store arbitrary metadata with each version (useful for filtering versions).
|
||||
* Can be turned off/on (useful for migrations).
|
||||
* No configuration necessary.
|
||||
* Stores everything in a single database table (generates migration for you).
|
||||
|
@ -141,6 +143,23 @@ In a migration or in `script/console` you can set who is responsible like this:
|
|||
>> widget.versions.last.whodunnit # Andy Stewart
|
||||
|
||||
|
||||
## Storing metadata
|
||||
|
||||
You can store arbitrary metadata alongside each version like this:
|
||||
|
||||
class Article < ActiveRecord::Base
|
||||
belongs_to :author
|
||||
has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
|
||||
:answer => 42 }
|
||||
end
|
||||
|
||||
PaperTrail will call your proc with the current article and store the result in the `author_id` column of the `versions` table. (Remember to add your metadata columns to the table.)
|
||||
|
||||
Why would you do this? In this example, `author_id` is an attribute of `Article` and PaperTrail will store it anyway in serialized (YAML) form in the `object` column of the `version` record. But let's say you wanted to pull out all versions for a particular author; without the metadata you would have to deserialize (reify) each `version` object to see if belonged to the author in question. Clearly this is inefficient. Using the metadata you can find just those versions you want:
|
||||
|
||||
Version.all(:conditions => ['author_id = ?', author_id])
|
||||
|
||||
|
||||
## Turning PaperTrail Off/On
|
||||
|
||||
Sometimes you don't want to store changes. Perhaps you are only interested in changes made
|
||||
|
|
|
@ -8,12 +8,18 @@ module PaperTrail
|
|||
module ClassMethods
|
||||
# Options:
|
||||
# :ignore an array of attributes for which a new +Version+ will not be created if only they change.
|
||||
# :meta a hash of extra data to store. You must add a column to the versions table for each key.
|
||||
# Values are objects or procs (which are called with +self+, i.e. the model with the paper
|
||||
# trail).
|
||||
def has_paper_trail(options = {})
|
||||
send :include, InstanceMethods
|
||||
|
||||
cattr_accessor :ignore
|
||||
self.ignore = (options[:ignore] || []).map &:to_s
|
||||
|
||||
cattr_accessor :meta
|
||||
self.meta = options[:meta] || {}
|
||||
|
||||
cattr_accessor :paper_trail_active
|
||||
self.paper_trail_active = true
|
||||
|
||||
|
@ -36,26 +42,36 @@ module PaperTrail
|
|||
|
||||
module InstanceMethods
|
||||
def record_create
|
||||
versions.create(:event => 'create',
|
||||
:whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
|
||||
if self.class.paper_trail_active
|
||||
versions.create merge_metadata(:event => 'create', :whodunnit => PaperTrail.whodunnit)
|
||||
end
|
||||
end
|
||||
|
||||
def record_update
|
||||
if changed_and_we_care? and self.class.paper_trail_active
|
||||
versions.build :event => 'update',
|
||||
versions.build merge_metadata(:event => 'update',
|
||||
:object => object_to_string(previous_version),
|
||||
:whodunnit => PaperTrail.whodunnit
|
||||
:whodunnit => PaperTrail.whodunnit)
|
||||
end
|
||||
end
|
||||
|
||||
def record_destroy
|
||||
versions.create(:event => 'destroy',
|
||||
if self.class.paper_trail_active
|
||||
versions.create merge_metadata(:event => 'destroy',
|
||||
:object => object_to_string(previous_version),
|
||||
:whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
|
||||
:whodunnit => PaperTrail.whodunnit)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merge_metadata(data)
|
||||
meta.each do |k,v|
|
||||
data[k] = v.respond_to?(:call) ? v.call(self) : v
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def previous_version
|
||||
previous = self.clone
|
||||
previous.id = id
|
||||
|
|
|
@ -18,7 +18,10 @@ class Fluxor < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class Article < ActiveRecord::Base
|
||||
has_paper_trail :ignore => [:title]
|
||||
has_paper_trail :ignore => [:title],
|
||||
:meta => {:answer => 42,
|
||||
:question => Proc.new { "31 + 11 = #{31 + 11}" },
|
||||
:article_id => Proc.new { |article| article.id } }
|
||||
end
|
||||
|
||||
|
||||
|
@ -37,9 +40,9 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|||
setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' }
|
||||
should_change('the number of versions', :by => 1) { Version.count }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'A new record' do
|
||||
setup { @widget = Widget.new }
|
||||
|
||||
|
@ -386,4 +389,60 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
context 'An item' do
|
||||
setup { @article = Article.new }
|
||||
|
||||
context 'which is created' do
|
||||
setup { @article.save }
|
||||
|
||||
should 'store fixed meta data' do
|
||||
assert_equal 42, @article.versions.last.answer
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which is independent of the item' do
|
||||
assert_equal '31 + 11 = 42', @article.versions.last.question
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which depends on the item' do
|
||||
assert_equal @article.id, @article.versions.last.article_id
|
||||
end
|
||||
|
||||
|
||||
context 'and updated' do
|
||||
setup { @article.update_attributes! :content => 'Better text.' }
|
||||
|
||||
should 'store fixed meta data' do
|
||||
assert_equal 42, @article.versions.last.answer
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which is independent of the item' do
|
||||
assert_equal '31 + 11 = 42', @article.versions.last.question
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which depends on the item' do
|
||||
assert_equal @article.id, @article.versions.last.article_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'and destroyd' do
|
||||
setup { @article.destroy }
|
||||
|
||||
should 'store fixed meta data' do
|
||||
assert_equal 42, @article.versions.last.answer
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which is independent of the item' do
|
||||
assert_equal '31 + 11 = 42', @article.versions.last.question
|
||||
end
|
||||
|
||||
should 'store dynamic meta data which depends on the item' do
|
||||
assert_equal @article.id, @article.versions.last.article_id
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -22,6 +22,11 @@ ActiveRecord::Schema.define(:version => 0) do
|
|||
t.string :whodunnit
|
||||
t.text :object
|
||||
t.datetime :created_at
|
||||
|
||||
# Metadata columns.
|
||||
t.integer :answer
|
||||
t.string :question
|
||||
t.integer :article_id
|
||||
end
|
||||
add_index :versions, [:item_type, :item_id]
|
||||
|
||||
|
|
Loading…
Reference in a new issue