close #334; Add small-scope 'whodunnit' method to PaperTrail::Model::InstanceMethods

This commit is contained in:
Ben Atkins 2014-03-11 17:55:44 -04:00
parent 52823d2fb2
commit 44a632eea6
5 changed files with 76 additions and 13 deletions

View File

@ -2,9 +2,10 @@
- [#340](https://github.com/airblade/paper_trail/issues/340) - Prevent potential error encountered when using the `InstallGenerator` - [#340](https://github.com/airblade/paper_trail/issues/340) - Prevent potential error encountered when using the `InstallGenerator`
with Rails `4.1.0.rc1`. with Rails `4.1.0.rc1`.
- [#334](https://github.com/airblade/paper_trail/pull/334) - Add small-scope `whodunnit` method to `PaperTrail::Model::InstanceMethods`.
- [#329](https://github.com/airblade/paper_trail/issues/329) - Add `touch_with_version` method to `PaperTrail::Model::InstanceMethods`, - [#329](https://github.com/airblade/paper_trail/issues/329) - Add `touch_with_version` method to `PaperTrail::Model::InstanceMethods`,
to allow for generating a version `touch`ing a model. to allow for generating a version `touch`ing a model.
- [#328](https://github.com/airblade/paper_trail/pull/328) / [#326](https://github.com/airblade/paper_trail/issues/326)/ - [#328](https://github.com/airblade/paper_trail/pull/328) / [#326](https://github.com/airblade/paper_trail/issues/326) /
[#307](https://github.com/airblade/paper_trail/issues/307) - `Model.paper_trail_enabled_for_model?` and [#307](https://github.com/airblade/paper_trail/issues/307) - `Model.paper_trail_enabled_for_model?` and
`model_instance.without_versioning` is now thread-safe. `model_instance.without_versioning` is now thread-safe.
- [#316](https://github.com/airblade/paper_trail/issues/316) - `user_for_paper_trail` should default to `current_user.try(:id)` - [#316](https://github.com/airblade/paper_trail/issues/316) - `user_for_paper_trail` should default to `current_user.try(:id)`

View File

@ -57,7 +57,7 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
### Sinatra ### Sinatra
In order to configure `PaperTrail` for usage with [Sinatra](http://www.sinatrarb.com), 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 [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 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: `Sinatra ActiveRecord Extension`, steps for setting up your app with `PaperTrail` will look something like this:
@ -480,15 +480,32 @@ In a console session you can manually set who is responsible like this:
You can avoid having to do this manually by setting your initializer to pick up the username of the current user from the OS, like this: You can avoid having to do this manually by setting your initializer to pick up the username of the current user from the OS, like this:
```ruby ```ruby
class PaperTrail::Version < ActiveRecord::Base # config/initializers/paper_trail.rb
if defined?(Rails::Console) module PaperTrail
PaperTrail.whodunnit = "#{`whoami`.strip}: console" class Version < ActiveRecord::Base
elsif File.basename($0) == "rake" if defined?(Rails::Console)
PaperTrail.whodunnit = "#{`whoami`.strip}: rake #{ARGV.join ' '}" PaperTrail.whodunnit = "#{`whoami`.strip}: console"
elsif File.basename($0) == "rake"
PaperTrail.whodunnit = "#{`whoami`.strip}: rake #{ARGV.join ' '}"
end
end end
end end
``` ```
Sometimes you want to define who is responsible for a change in a small scope without overwriting value of `PaperTrail.whodunnit`. It is possible to define the `whodunnit` value for an operation inside a block like this:
```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
>> widget.whodunnit('Ben Atkins') { |w| w.update_attributes :name => 'Beth' } # this syntax also works
>> widget.versions.last.whodunnit # Ben Atkins
```
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`. 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. 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.
@ -1062,6 +1079,7 @@ Many thanks to:
* [Vlad Bokov](https://github.com/razum2um) * [Vlad Bokov](https://github.com/razum2um)
* [Sean Marcia](https://github.com/SeanMarcia) * [Sean Marcia](https://github.com/SeanMarcia)
* [Chulki Lee](https://github.com/chulkilee) * [Chulki Lee](https://github.com/chulkilee)
* [Lucas Souza](https://github.com/lucasas)
## Inspirations ## Inspirations

View File

@ -14,11 +14,11 @@ module PaperTrail
# `:create`, `:update`, `:destroy` as desired. # `:create`, `:update`, `:destroy` as desired.
# :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`. # :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. # :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. # 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 # :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 # :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. # 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 # :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 # a new `Version`. In addition, these fields will not be included in the serialized versions
@ -216,6 +216,18 @@ module PaperTrail
self.class.paper_trail_on! if paper_trail_was_enabled self.class.paper_trail_on! if paper_trail_was_enabled
end end
# Temporarily overwrites the value of whodunnit and then executes the provided block.
def whodunnit(value)
raise ArgumentError, 'expected to receive a block' unless block_given?
current_whodunnit = PaperTrail.whodunnit
PaperTrail.whodunnit = value
begin
yield self
ensure
PaperTrail.whodunnit = current_whodunnit
end
end
# Mimicks behavior of `touch` method from `ActiveRecord::Persistence`, but generates a version # Mimicks behavior of `touch` method from `ActiveRecord::Persistence`, but generates a version
# #
# TODO: lookinto leveraging the `after_touch` callback from `ActiveRecord` to allow the # TODO: lookinto leveraging the `after_touch` callback from `ActiveRecord` to allow the

View File

@ -23,6 +23,39 @@ describe Widget do
describe "Methods" do describe "Methods" do
describe "Instance", :versioning => true do describe "Instance", :versioning => true do
describe :whodunnit do
it { should respond_to(:whodunnit) }
context "no block given" do
it "should raise an error" do
expect { widget.whodunnit('Ben') }.to raise_error(ArgumentError, 'expected to receive a block')
end
end
context "block given" do
let(:orig_name) { Faker::Name.name }
let(:new_name) { Faker::Name.name }
before do
PaperTrail.whodunnit = orig_name
widget.versions.last.whodunnit.should == orig_name # persist `widget`
end
it "should modify value of `PaperTrail.whodunnit` while executing the block" do
widget.whodunnit(new_name) do
PaperTrail.whodunnit.should == new_name
widget.update_attributes(:name => 'Elizabeth')
end
widget.versions.last.whodunnit.should == new_name
end
it "should revert the value of `PaperTrail.whodunnit` to it's previous value after executing the block" do
widget.whodunnit(new_name) { |w| w.update_attributes(:name => 'Elizabeth') }
PaperTrail.whodunnit.should == orig_name
end
end
end
describe :touch_with_version do describe :touch_with_version do
it { should respond_to(:touch_with_version) } it { should respond_to(:touch_with_version) }

View File

@ -771,7 +771,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
should 'store dynamic meta data based on a method of the item' do should 'store dynamic meta data based on a method of the item' do
assert_equal @article.action_data_provider_method, @article.versions.last.action assert_equal @article.action_data_provider_method, @article.versions.last.action
end end
should 'store dynamic meta data based on an attribute of the item prior to creation' do should 'store dynamic meta data based on an attribute of the item prior to creation' do
assert_equal nil, @article.versions.last.title assert_equal nil, @article.versions.last.title
end end
@ -793,7 +793,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
should 'store dynamic meta data which depends on the item' do should 'store dynamic meta data which depends on the item' do
assert_equal @article.id, @article.versions.last.article_id assert_equal @article.id, @article.versions.last.article_id
end end
should 'store dynamic meta data based on an attribute of the item prior to the update' do 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 assert_equal @initial_title, @article.versions.last.title
end end
@ -814,7 +814,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
should 'store dynamic meta data which depends on the item' do should 'store dynamic meta data which depends on the item' do
assert_equal @article.id, @article.versions.last.article_id assert_equal @article.id, @article.versions.last.article_id
end end
should 'store dynamic meta data based on an attribute of the item prior to the destruction' do 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 assert_equal @initial_title, @article.versions.last.title
end end
@ -837,7 +837,6 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
should 'return its previous self' do should 'return its previous self' do
assert_equal @widget.versions[-2].reify, @widget.previous_version assert_equal @widget.versions[-2].reify, @widget.previous_version
end end
end end