Skip to content

Commit

Permalink
Merge branch '3.1.0-stable'
Browse files Browse the repository at this point in the history
Conflicts:
	CHANGELOG.md
	lib/paper_trail.rb
  • Loading branch information
batter committed Sep 2, 2014
2 parents 280b194 + a2f2e86 commit a683be8
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 22 deletions.
1 change: 0 additions & 1 deletion .rspec
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
--format progress
--color

2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ env:
- DB=postgres
- DB=sqlite

sudo: false

before_script:
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_test;'; fi"
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_bar; '; fi"
Expand Down
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
## 3.1.0 (Unreleased)

##### Breaking change: if you use a custom initializer for PaperTrail in conjunction with Rails, you will need to add this line of code to the beginning of it:
```ruby
PaperTrail::Rails::Engine.eager_load!
```

- [#399](https://github.com/airblade/paper_trail/pull/399) - Add `:dup` argument for options hash to `reify` which forces a
new model instance.
- [#394](https://github.com/airblade/paper_trail/pull/394) - Add RSpec matcher `have_a_version_with` for easier testing.
- [#375](https://github.com/airblade/paper_trail/pull/375) / [#374](https://github.com/airblade/paper_trail/issues/374) /
[#354](https://github.com/airblade/paper_trail/issues/354) / [#131](https://github.com/airblade/paper_trail/issues/131) -
Versions should be built with `after_` callbacks so the timestamp field for a version can be forced to match the
corresponding timestamp in the database for the state persistence of a change to the base (versioned) model.

## 3.0.5

- [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406)
`PaperTrail::Version` class is not loaded via a `Rails::Engine`, even when the gem is used with in Rails. This feature has
will be re-introduced in version `3.1.0`.
- [#398](https://github.com/airblade/paper_trail/pull/398) - Only require the `RSpec` helper if `RSpec::Core` is required.

## 3.0.3
*This version was yanked from RubyGems and has been replaced by version `3.0.5`, which is identical but does not eager load
*This version was yanked from RubyGems and has been replaced by version `3.0.5`, which is almost identical, but does not eager load
in the `PaperTrail::Version` class through a `Rails::Engine` when the gem is used on Rails since it was causing issues for some users.*

- [#386](https://github.com/airblade/paper_trail/issues/386) - Fix eager loading of `versions` association with custom class name
Expand Down
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ And a `PaperTrail::Version` instance has these methods:
# Returns the item restored from this version.
version.reify(options = {})

# Return a new item from this version
version.reify(dup: true)

# Returns who put the item into the state stored in this version.
version.originator

Expand Down Expand Up @@ -481,6 +484,10 @@ You can avoid having to do this manually by setting your initializer to pick up

```ruby
# config/initializers/paper_trail.rb

# the following line is required for PaperTrail >= 3.1.0 with Rails
PaperTrail::Rails::Engine.eager_load!

if defined?(::Rails::Console)
PaperTrail.whodunnit = "#{`whoami`.strip}: console"
elsif File.basename($0) == "rake"
Expand Down Expand Up @@ -556,6 +563,11 @@ If you only use custom version classes and don't use PaperTrail's built-in one,
- either declare the `PaperTrail::Version` class to be abstract like this (in an initializer):

```ruby
# config/initializers/paper_trail.rb

# the following line is required for PaperTrail >= 3.1.0 with Rails
PaperTrail::Rails::Engine.eager_load!

PaperTrail::Version.module_eval do
self.abstract_class = true
end
Expand Down Expand Up @@ -721,6 +733,10 @@ For example:

```ruby
# config/initializers/paper_trail.rb

# the following line is required for PaperTrail >= 3.1.0 with Rails
PaperTrail::Rails::Engine.eager_load!

module PaperTrail
class Version < ActiveRecord::Base
attr_accessible :author_id, :word_count, :answer
Expand Down Expand Up @@ -968,6 +984,25 @@ describe Widget do
end
```

It is also possible to do assertions on the versions using `have_a_version_with` matcher

```
describe '`have_a_version_with` matcher' do
before do
widget.update_attributes!(:name => 'Leonard', :an_integer => 1 )
widget.update_attributes!(:name => 'Tom')
widget.update_attributes!(:name => 'Bob')
end
it "is possible to do assertions on versions" do
widget.should have_a_version_with :name => 'Leonard', :an_integer => 1
widget.should have_a_version_with :an_integer => 1
widget.should have_a_version_with :name => 'Tom'
end
end
```

### Cucumber

PaperTrail provides a helper for [Cucumber](http://cukes.info) that works similar to the RSpec helper.
Expand Down Expand Up @@ -1023,9 +1058,9 @@ require 'paper_trail/frameworks/rspec'

## Testing PaperTrail

Paper Trail has facilities to test aganist Postgres, Mysql and SQLite. To switch between DB engines you will need to export the DB Variable for the engine you wish to test aganist.
Paper Trail has facilities to test aganist Postgres, Mysql and SQLite. To switch between DB engines you will need to export the DB Variable for the engine you wish to test aganist.

Though be aware we do not have the abilty to create the db's (except sqlite) for you. You can look at .travis.yml before_script for an example of how to create the db's needed.
Though be aware we do not have the abilty to create the db's (except sqlite) for you. You can look at .travis.yml before_script for an example of how to create the db's needed.

```
export DB=postgres
Expand Down
7 changes: 5 additions & 2 deletions lib/paper_trail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ def self.configure
end

# Require frameworks
require 'paper_trail/frameworks/active_record'
require 'paper_trail/frameworks/sinatra'
require 'paper_trail/frameworks/rails' if defined? Rails
if defined? Rails
require 'paper_trail/frameworks/rails'
else
require 'paper_trail/frameworks/active_record'
end
require 'paper_trail/frameworks/rspec' if defined? RSpec::Core
require 'paper_trail/frameworks/cucumber' if defined? World
1 change: 1 addition & 0 deletions lib/paper_trail/frameworks/rails.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'paper_trail/frameworks/rails/controller'
require 'paper_trail/frameworks/rails/engine'

module PaperTrail
module Rails
Expand Down
5 changes: 5 additions & 0 deletions lib/paper_trail/frameworks/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@
# check to see if the model has `has_paper_trail` declared on it
match { |actual| actual.kind_of?(::PaperTrail::Model::InstanceMethods) }
end

RSpec::Matchers.define :have_a_version_with do |attributes|
# check if the model has a version with the specified attributes
match { |actual| actual.versions.where_object(attributes).any? }
end
12 changes: 8 additions & 4 deletions lib/paper_trail/has_paper_trail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def has_paper_trail(options = {})
after_create :record_create, :if => :save_version? if options_on.empty? || options_on.include?(:create)
if options_on.empty? || options_on.include?(:update)
before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
before_update :record_update, :if => :save_version?
after_update :record_update, :if => :save_version?
after_update :clear_version_instance!
end
after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy)
Expand Down Expand Up @@ -259,7 +259,9 @@ def record_create
:event => paper_trail_event || 'create',
:whodunnit => PaperTrail.whodunnit
}

if respond_to?(:created_at)
data[PaperTrail.timestamp_field] = created_at
end
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)
Expand All @@ -276,12 +278,14 @@ def record_update
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
:whodunnit => PaperTrail.whodunnit
}

if respond_to?(:updated_at)
data[PaperTrail.timestamp_field] = updated_at
end
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
send(self.class.versions_association_name).build merge_metadata(data)
send(self.class.versions_association_name).create merge_metadata(data)
end
end

Expand Down
7 changes: 4 additions & 3 deletions lib/paper_trail/version_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ module VersionConcern
included do
belongs_to :item, :polymorphic => true
validates_presence_of :event
attr_accessible :item_type, :item_id, :event, :whodunnit, :object, :object_changes if PaperTrail.active_record_protected_attributes?

attr_accessible :item_type, :item_id, :event, :whodunnit, :object, :object_changes, :created_at if PaperTrail.active_record_protected_attributes?
after_create :enforce_version_limit!
end

Expand Down Expand Up @@ -111,6 +110,8 @@ def object_changes_col_is_json?
# :has_one set to `false` to opt out of has_one reification.
# set to a float to change the lookback time (check whether your db supports
# sub-second datetimes if you want them).
# :dup `false` default behavior
# `true` it always create a new object instance. It is useful for comparing two versions of the same object
def reify(options = {})
return nil if object.nil?

Expand All @@ -133,7 +134,7 @@ def reify(options = {})
# `item_type` will be the base class, not the actual subclass.
# If `type` is present but empty, the class is the base class.

if item
if item && options[:dup] != true
model = item
# Look for attributes that exist in the model and not in this version. These attributes should be set to nil.
(model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
Expand Down
53 changes: 49 additions & 4 deletions spec/models/widget_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@

let(:widget) { Widget.create! :name => 'Bob', :an_integer => 1 }

describe '`have_a_version_with` matcher', :versioning => true do
before do
widget.update_attributes!(:name => 'Leonard', :an_integer => 1 )
widget.update_attributes!(:name => 'Tom')
widget.update_attributes!(:name => 'Bob')
end

it "is possible to do assertions on versions" do
widget.should have_a_version_with :name => 'Leonard', :an_integer => 1
widget.should have_a_version_with :an_integer => 1
widget.should have_a_version_with :name => 'Tom'
end
end

describe "`versioning` option" do
context :enabled, :versioning => true do
it 'should enable versioning for models wrapped within a block' do
Expand Down Expand Up @@ -34,8 +48,16 @@
end
end

describe :after_create do
let(:widget) { Widget.create!(:name => 'Foobar', :created_at => Time.now - 1.week) }

it "corresponding version should use the widget's `created_at`" do
widget.versions.last.created_at.to_i.should == widget.created_at.to_i
end
end

describe :after_update do
before { widget.update_attributes!(:name => 'Foobar') }
before { widget.update_attributes!(:name => 'Foobar', :updated_at => Time.now + 1.week) }

subject { widget.versions.last.reify }

Expand All @@ -45,6 +67,10 @@
subject.save!
subject.should be_live
end

it "corresponding version should use the widget updated_at" do
widget.versions.last.created_at.to_i.should == widget.updated_at.to_i
end
end

describe :after_destroy do
Expand Down Expand Up @@ -96,10 +122,29 @@
PaperTrail.whodunnit = new_name
widget.update_attributes(:name => 'Elizabeth')
end
let(:reified_widget) { widget.versions[1].reify }

it "should return the appropriate originator" do
reified_widget.originator.should == orig_name
context "default behavior (no `options[:dup]` option passed in)" do
let(:reified_widget) { widget.versions[1].reify }

it "should return the appropriate originator" do
reified_widget.originator.should == orig_name
end

it "should not create a new model instance" do
reified_widget.should_not be_new_record
end
end

context "creating a new instance (`options[:dup] == true`)" do
let(:reified_widget) { widget.versions[1].reify(:dup => true) }

it "should return the appropriate originator" do
reified_widget.originator.should == orig_name
end

it "should not create a new model instance" do
reified_widget.should be_new_record
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each { |f| require f }

# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
Expand Down
2 changes: 1 addition & 1 deletion test/dummy/config/initializers/paper_trail.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module PaperTrail
class Version < ActiveRecord::Base
attr_accessible :created_at, :updated_at, :answer, :action, :question, :article_id, :ip, :user_agent, :title if ::PaperTrail.active_record_protected_attributes?
attr_accessible :answer, :action, :question, :article_id, :ip, :user_agent, :title if ::PaperTrail.active_record_protected_attributes?
end
end
9 changes: 6 additions & 3 deletions test/unit/model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,8 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
assert @widget.live?
end


context 'which is then created' do
setup { @widget.update_attributes :name => 'Henry' }
setup { @widget.update_attributes :name => 'Henry', :created_at => Time.now - 1.day }

should 'have one previous version' do
assert_equal 1, @widget.versions.length
Expand All @@ -212,6 +211,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
assert @widget.live?
end

should 'use the widget created_at' do
assert_equal @widget.created_at.to_i, @widget.versions.first.created_at.to_i
end

should 'have changes' do

#TODO Postgres does not appear to pass back ActiveSupport::TimeWithZone,
Expand Down Expand Up @@ -731,7 +734,7 @@ def without(&block)
should 'return versions in the time period' do
assert_equal ['Fidget'], @widget.versions_between(20.days.ago, 10.days.ago).map(&:name)
assert_equal ['Widget', 'Fidget'], @widget.versions_between(45.days.ago, 10.days.ago).map(&:name)
assert_equal ['Fidget', 'Digit'], @widget.versions_between(16.days.ago, 1.minute.ago).map(&:name)
assert_equal ['Fidget', 'Digit', 'Digit'], @widget.versions_between(16.days.ago, 1.minute.ago).map(&:name)
assert_equal [], @widget.versions_between(60.days.ago, 45.days.ago).map(&:name)
end
end
Expand Down

0 comments on commit a683be8

Please sign in to comment.