diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdee792..5b4911a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ - [#340](https://github.com/airblade/paper_trail/issues/340) - Prevent potential error encountered when using the `InstallGenerator` 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`, 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 `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)` diff --git a/README.md b/README.md index 50aaddf7..e70c07c5 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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: ```ruby -class PaperTrail::Version < ActiveRecord::Base - if defined?(Rails::Console) - PaperTrail.whodunnit = "#{`whoami`.strip}: console" - elsif File.basename($0) == "rake" - PaperTrail.whodunnit = "#{`whoami`.strip}: rake #{ARGV.join ' '}" +# config/initializers/paper_trail.rb +module PaperTrail + class Version < ActiveRecord::Base + if defined?(Rails::Console) + PaperTrail.whodunnit = "#{`whoami`.strip}: console" + elsif File.basename($0) == "rake" + PaperTrail.whodunnit = "#{`whoami`.strip}: rake #{ARGV.join ' '}" + 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`. 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) * [Sean Marcia](https://github.com/SeanMarcia) * [Chulki Lee](https://github.com/chulkilee) +* [Lucas Souza](https://github.com/lucasas) ## Inspirations diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index e1103c73..2c0a210b 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -14,11 +14,11 @@ module PaperTrail # `: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 @@ -216,6 +216,18 @@ module PaperTrail self.class.paper_trail_on! if paper_trail_was_enabled 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 # # TODO: lookinto leveraging the `after_touch` callback from `ActiveRecord` to allow the diff --git a/spec/models/widget_spec.rb b/spec/models/widget_spec.rb index 0d951ba7..152d915e 100644 --- a/spec/models/widget_spec.rb +++ b/spec/models/widget_spec.rb @@ -23,6 +23,39 @@ describe Widget do describe "Methods" 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 it { should respond_to(:touch_with_version) } diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb index 27983f47..09ef0727 100644 --- a/test/unit/model_test.rb +++ b/test/unit/model_test.rb @@ -771,7 +771,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase 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 @@ -793,7 +793,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase 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 @@ -814,7 +814,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase 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 @@ -837,7 +837,6 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase should 'return its previous self' do assert_equal @widget.versions[-2].reify, @widget.previous_version end - end