Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows the definition of a customized whodunnit in a small scope #334

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
### Sinatra

In order to configure `PaperTrail` for usage with [Sinatra](http://www.sinatrarb.com),
your `Sinatra` app must be using `ActiveRecord` 3 or `ActiveRecord` 4. It is also recommended to use the
your `Sinatra` app must be using `ActiveRecord` 3 or `ActiveRecord` 4. It is also recommended to use the
[Sinatra ActiveRecord Extension](https://github.com/janko-m/sinatra-activerecord) or something similar for managing
your applications `ActiveRecord` connection in a manner similar to the way `Rails` does. If using the aforementioned
`Sinatra ActiveRecord Extension`, steps for setting up your app with `PaperTrail` will look something like this:
Expand Down Expand Up @@ -489,6 +489,21 @@ class PaperTrail::Version < ActiveRecord::Base
end
```

Sometimes you want to define who is responsible in a small scope and don't using `PaperTrail.whodunnit`. In these cases it is possible to define executing your operation inside a block:

```ruby
PaperTrail.whodunnit = 'Andy Stewart'

widget.whodunnit 'Lucas Souza' do
widget.update_attributes :name => 'Wibble'
end

widget.versions.last.whodunnit # Lucas Souza

widget.update_attributes :name => 'Clair'
widget.versions.last.whodunnit # Andy Stewart
```

A version's `whodunnit` records who changed the object causing the `version` to be stored. Because a version stores the object as it looked before the change (see the table above), `whodunnit` returns who stopped the object looking like this -- not who made it look like this. Hence `whodunnit` is aliased as `terminator`.

To find out who made a version's object look that way, use `version.originator`. And to find out who made a "live" object look like it does, use `originator` on the object.
Expand Down
29 changes: 24 additions & 5 deletions lib/paper_trail/has_paper_trail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ module ClassMethods
# `:create`, `:update`, `:destroy` as desired.
# :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
# :ignore an array of attributes for which a new `Version` will not be created if only they change.
# it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
# it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
# which will only be ignored if the value is a `Proc` which returns truthily.
# :if, :unless Procs that allow to specify conditions when to save versions for an object
# :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
# it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
# it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
# which will only be counted if the value is a `Proc` which returns truthily.
# :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
# a new `Version`. In addition, these fields will not be included in the serialized versions
Expand Down Expand Up @@ -232,6 +232,11 @@ def touch_with_version(name = nil)
save!
end

def whodunnit(whodunnit)
@whodunnit = whodunnit
yield if block_given?
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this would make more sense instead of having whodunnit and _whodunnit

def whodunnit(whodunnit = PaperTrail.whodunnit)
  @whodunnit = whodunnit
  yield if block_given?
end

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @seanlinsley, what about the case where I don't want to use my_object.whodunnit?


private

def source_version
Expand All @@ -242,13 +247,16 @@ def record_create
if paper_trail_switched_on?
data = {
:event => paper_trail_event || 'create',
:whodunnit => PaperTrail.whodunnit
:whodunnit => _whodunnit
}

if changed_notably? and self.class.paper_trail_version_class.column_names.include?('object_changes')
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
PaperTrail.serializer.dump(changes_for_paper_trail)
end

clean_whodunnit

send(self.class.versions_association_name).create! merge_metadata(data)
end
end
Expand All @@ -259,12 +267,15 @@ def record_update
data = {
:event => paper_trail_event || 'update',
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
:whodunnit => PaperTrail.whodunnit
:whodunnit => _whodunnit
}
if self.class.paper_trail_version_class.column_names.include?('object_changes')
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
PaperTrail.serializer.dump(changes_for_paper_trail)
end

clean_whodunnit

send(self.class.versions_association_name).build merge_metadata(data)
end
end
Expand All @@ -283,13 +294,17 @@ def record_destroy
:item_type => self.class.base_class.name,
:event => paper_trail_event || 'destroy',
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
:whodunnit => PaperTrail.whodunnit
:whodunnit => _whodunnit
}
self.class.paper_trail_version_class.create merge_metadata(data)
send(self.class.versions_association_name).send :load_target
end
end

def clean_whodunnit
@whodunnit = nil
end

def merge_metadata(data)
# First we merge the model-level metadata in `meta`.
paper_trail_options[:meta].each do |k,v|
Expand Down Expand Up @@ -362,6 +377,10 @@ def save_version?
unless_condition = self.paper_trail_options[:unless]
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
end

def _whodunnit
@whodunnit || PaperTrail.whodunnit
end
end
end
end
66 changes: 63 additions & 3 deletions test/unit/model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,66 @@ def without(&block)
@widget = Widget.new :name => 'Fidget'
end

context 'defining whodunnit using a block' do
context "when a record is created" do
setup do
PaperTrail.whodunnit = 'Helena'

@widget.whodunnit 'Clair' do
@widget.save
end

@version = @widget.versions.last # only 1 version
end

should 'track who made the change' do
assert_equal 'Clair', @version.whodunnit
end

should 'ignore defined whodunnit using global whodunnit' do
@widget.update_attributes :name => 'Fernandes'
@version = @widget.versions.last

assert_equal 'Helena', @version.whodunnit
end

context "when a record is updated" do
setup do
@widget.whodunnit 'Rafaela' do
@widget.update_attributes :name => 'Fernandes'
end

@version = @widget.versions.last # only 1 version
end

should 'track who made the change' do
assert_equal 'Rafaela', @version.whodunnit
end

should 'ignore defined whodunnit using global whodunnit' do
@widget.update_attributes :name => 'Souza'
@version = @widget.versions.last

assert_equal 'Helena', @version.whodunnit
end
end

context 'when a record is destroyed' do
setup do
@widget.whodunnit 'Lucas' do
@widget.destroy
end

@version = PaperTrail::Version.last
end

should 'track who made the change' do
assert_equal 'Lucas', @version.whodunnit
end
end
end
end

context 'when a record is created' do
setup do
PaperTrail.whodunnit = 'Alice'
Expand Down Expand Up @@ -771,7 +831,7 @@ def without(&block)
should 'store dynamic meta data based on a method of the item' do
assert_equal @article.action_data_provider_method, @article.versions.last.action
end

should 'store dynamic meta data based on an attribute of the item prior to creation' do
assert_equal nil, @article.versions.last.title
end
Expand All @@ -793,7 +853,7 @@ def without(&block)
should 'store dynamic meta data which depends on the item' do
assert_equal @article.id, @article.versions.last.article_id
end

should 'store dynamic meta data based on an attribute of the item prior to the update' do
assert_equal @initial_title, @article.versions.last.title
end
Expand All @@ -814,7 +874,7 @@ def without(&block)
should 'store dynamic meta data which depends on the item' do
assert_equal @article.id, @article.versions.last.article_id
end

should 'store dynamic meta data based on an attribute of the item prior to the destruction' do
assert_equal @initial_title, @article.versions.last.title
end
Expand Down