paper_trail 1.2.14 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  test/debug.log
2
2
  test/paper_trail_plugin.sqlite3.db
3
+ coverage
data/README.md CHANGED
@@ -6,7 +6,7 @@ PaperTrail lets you track changes to your models' data. It's good for auditing
6
6
  ## Features
7
7
 
8
8
  * Stores every create, update and destroy.
9
- * Does not store updates which don't change anything.
9
+ * Does not store updates which don't change anything (or which only change attributes you are ignoring).
10
10
  * Allows you to get at every version, including the original, even once destroyed.
11
11
  * Allows you to get at every version even if the schema has since changed.
12
12
  * Automatically records who was responsible if your controller has a `current_user` method.
@@ -87,6 +87,25 @@ Here's a helpful table showing what PaperTrail stores:
87
87
  PaperTrail stores the values in the Model Before column. Most other auditing/versioning plugins store the After column.
88
88
 
89
89
 
90
+ ## Ignoring changes to certain attributes
91
+
92
+ You can ignore changes to certain attributes like this:
93
+
94
+ class Article < ActiveRecord::Base
95
+ has_paper_trail :ignore => [:title, :rating]
96
+ end
97
+
98
+ This means that changes to just the `title` or `rating` will not store another version of the article. It does not mean that the `title` and `rating` attributes will be ignored if some other change causes a new `Version` to be crated. For example:
99
+
100
+ >> a = Article.create
101
+ >> a.versions.length # 1
102
+ >> a.update_attributes :title => 'My Title', :rating => 3
103
+ >> a.versions.length # 1
104
+ >> a.update_attributes :content => 'Hello'
105
+ >> a.versions.length # 2
106
+ >> a.versions.last.reify.title # 'My Title'
107
+
108
+
90
109
  ## Reverting And Undeleting A Model
91
110
 
92
111
  PaperTrail makes reverting to a previous version easy:
@@ -160,7 +179,7 @@ And on again like this:
160
179
 
161
180
  ## Testing
162
181
 
163
- PaperTrail has a thorough suite of tests. However they only run when PaperTrail is sitting in a Rails app's `vendor/plugins` directory. If anyone can tell me how to get them to run outside of a Rails app, I'd love to hear it.
182
+ PaperTrail has a thorough suite of tests. Thanks to [Zachery Hostens](http://github.com/zacheryph) for making them able to run standalone, i.e. without needing PaperTrail to be sitting in a Rails app.
164
183
 
165
184
 
166
185
  ## Problems
@@ -168,6 +187,13 @@ PaperTrail has a thorough suite of tests. However they only run when PaperTrail
168
187
  Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
169
188
 
170
189
 
190
+ ## Contributors
191
+
192
+ Many thanks to:
193
+
194
+ * [Zachery Hostens](http://github.com/zacheryph)
195
+
196
+
171
197
  ## Inspirations
172
198
 
173
199
  * [Simply Versioned](http://github.com/github/simply_versioned)
data/Rakefile CHANGED
@@ -11,8 +11,9 @@ begin
11
11
  gemspec.homepage = 'http://github.com/airblade/paper_trail'
12
12
  gemspec.authors = ['Andy Stewart']
13
13
  end
14
+ Jeweler::GemcutterTasks.new
14
15
  rescue LoadError
15
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
17
  end
17
18
 
18
19
  desc 'Test the paper_trail plugin.'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.13
1
+ 1.3.1
@@ -7,10 +7,8 @@ class CreateVersions < ActiveRecord::Migration
7
7
  t.string :whodunnit
8
8
  t.text :object
9
9
  t.datetime :created_at
10
- t.integer :user_id, :null => true
11
10
  end
12
11
  add_index :versions, [:item_type, :item_id]
13
- add_index :versions, :user_id
14
12
  end
15
13
 
16
14
  def self.down
@@ -1,27 +1,14 @@
1
1
  require 'yaml'
2
- require 'paper_trail/config'
3
2
  require 'paper_trail/has_paper_trail'
4
3
  require 'paper_trail/version'
5
4
 
6
5
  module PaperTrail
7
6
  @@whodunnit = nil
8
7
 
9
- def self.config
10
- @@config ||= PaperTrail::Config.instance
11
- end
12
-
13
8
  def self.included(base)
14
9
  base.before_filter :set_whodunnit
15
10
  end
16
11
 
17
- def self.enabled=(value)
18
- PaperTrail.config.enabled = value
19
- end
20
-
21
- def self.enabled?
22
- !!PaperTrail.config.enabled
23
- end
24
-
25
12
  def self.whodunnit
26
13
  @@whodunnit.respond_to?(:call) ? @@whodunnit.call : @@whodunnit
27
14
  end
@@ -37,7 +24,6 @@ module PaperTrail
37
24
  self.send :current_user rescue nil
38
25
  }
39
26
  end
40
-
41
27
  end
42
28
 
43
29
  ActionController::Base.send :include, PaperTrail
@@ -6,9 +6,14 @@ module PaperTrail
6
6
 
7
7
 
8
8
  module ClassMethods
9
- def has_paper_trail
9
+ # Options:
10
+ # :ignore an array of attributes for which a new +Version+ will not be created if only they change.
11
+ def has_paper_trail(options = {})
10
12
  send :include, InstanceMethods
11
13
 
14
+ cattr_accessor :ignore
15
+ self.ignore = (options[:ignore] || []).map &:to_s
16
+
12
17
  cattr_accessor :paper_trail_active
13
18
  self.paper_trail_active = true
14
19
 
@@ -32,11 +37,11 @@ module PaperTrail
32
37
  module InstanceMethods
33
38
  def record_create
34
39
  versions.create(:event => 'create',
35
- :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active && PaperTrail.enabled?
40
+ :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
36
41
  end
37
42
 
38
43
  def record_update
39
- if changed? and self.class.paper_trail_active and PaperTrail.enabled?
44
+ if changed_and_we_care? and self.class.paper_trail_active
40
45
  versions.build :event => 'update',
41
46
  :object => object_to_string(previous_version),
42
47
  :whodunnit => PaperTrail.whodunnit
@@ -46,59 +51,9 @@ module PaperTrail
46
51
  def record_destroy
47
52
  versions.create(:event => 'destroy',
48
53
  :object => object_to_string(previous_version),
49
- :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active && PaperTrail.enabled?
54
+ :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
50
55
  end
51
56
 
52
- # Returns the object at the version that was valid at the given timestamp.
53
- def version_at timestamp
54
- # short-circuit if the current state is valid
55
- return self if self.updated_at < timestamp
56
-
57
- version = versions.first(
58
- :conditions => ['created_at < ?', timestamp],
59
- :order => 'created_at DESC')
60
- version.reify if version
61
- end
62
-
63
- # Walk the versions to construct an audit trail of the edits made
64
- # over time, and by whom.
65
- def audit_trail options={}
66
- options[:attributes_to_ignore] ||= %w(updated_at)
67
-
68
- audit_trail = []
69
-
70
- versions_desc = versions_including_current_in_descending_order
71
-
72
- versions_desc.each_with_index do |version, index|
73
- previous_version = versions_desc[index + 1]
74
- break if previous_version.nil?
75
-
76
- attributes_after = yaml_to_hash(version.object)
77
- attributes_before = yaml_to_hash(previous_version.object)
78
-
79
- # remove some attributes that we don't need to report
80
- [attributes_before, attributes_after].each do |hash|
81
- hash.reject! { |k,v| k.in? Array(options[:attributes_to_ignore]) }
82
- end
83
-
84
- audit_trail << {
85
- :event => previous_version.event,
86
- :changed_by => transform_whodunnit(previous_version.whodunnit),
87
- :changed_at => previous_version.created_at,
88
- :changes => differences(attributes_before, attributes_after)
89
- }
90
- end
91
-
92
- audit_trail
93
- end
94
-
95
- protected
96
-
97
- def transform_whodunnit(whodunnit)
98
- whodunnit
99
- end
100
-
101
-
102
57
  private
103
58
 
104
59
  def previous_version
@@ -114,30 +69,8 @@ module PaperTrail
114
69
  object.attributes.to_yaml
115
70
  end
116
71
 
117
- def yaml_to_hash(yaml)
118
- return {} if yaml.nil?
119
- YAML::load(yaml).to_hash
120
- end
121
-
122
- # Returns an array of hashes, where each hash specifies the +:attribute+,
123
- # value +:before+ the change, and value +:after+ the change.
124
- def differences(before, after)
125
- before.diff(after).keys.sort.inject([]) do |diffs, k|
126
- diff = { :attribute => k, :before => before[k], :after => after[k] }
127
- diffs << diff; diffs
128
- end
129
- end
130
-
131
- def versions_including_current_in_descending_order
132
- if self.new_record?
133
- v = Version.all(:order => 'created_at desc', :conditions => {:item_id => id, :item_type => self.class.name})
134
- else
135
- v = self.versions.dup
136
- end
137
- v << Version.new(:event => 'update',
138
- :object => object_to_string(self),
139
- :created_at => self.updated_at)
140
- v.reverse # newest first
72
+ def changed_and_we_care?
73
+ changed? and !(changed - self.class.ignore).empty?
141
74
  end
142
75
  end
143
76
 
@@ -1,19 +1,7 @@
1
1
  class Version < ActiveRecord::Base
2
2
  belongs_to :item, :polymorphic => true
3
- belongs_to :user
4
- before_save :set_user_id
5
3
  validates_presence_of :event
6
4
 
7
- named_scope :for_item_type, lambda { |item_types|
8
- { :conditions => { :item_type => item_types } }
9
- }
10
-
11
- named_scope :created_after, lambda { |time|
12
- { :conditions => ['versions.created_at > ?', time] }
13
- }
14
-
15
- named_scope :by_created_at_ascending, :order => 'versions.created_at asc'
16
-
17
5
  def reify
18
6
  unless object.nil?
19
7
  # Attributes
@@ -45,7 +33,7 @@ class Version < ActiveRecord::Base
45
33
  begin
46
34
  model.send "#{k}=", v
47
35
  rescue NoMethodError
48
- RAILS_DEFAULT_LOGGER.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
36
+ logger.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
49
37
  end
50
38
  end
51
39
 
@@ -68,10 +56,4 @@ class Version < ActiveRecord::Base
68
56
  :order => 'id ASC').index(self)
69
57
  end
70
58
 
71
- private
72
-
73
- def set_user_id
74
- self.user_id = self.whodunnit.id if self.whodunnit.is_a?(ActiveRecord::Base)
75
- return true
76
- end
77
59
  end
@@ -1,13 +1,16 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{paper_trail}
5
- s.version = "1.2.14"
8
+ s.version = "1.3.1"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Andy Stewart", "Jeremy Weiskotten", "Joe Lind"]
9
- s.date = %q{2009-10-02}
10
- s.email = %q{jeremy@weiskotten.com}
11
+ s.authors = ["Andy Stewart"]
12
+ s.date = %q{2009-11-24}
13
+ s.email = %q{boss@airbladesoftware.com}
11
14
  s.extra_rdoc_files = [
12
15
  "README.md"
13
16
  ]
@@ -23,7 +26,6 @@ Gem::Specification.new do |s|
23
26
  "init.rb",
24
27
  "install.rb",
25
28
  "lib/paper_trail.rb",
26
- "lib/paper_trail/config.rb",
27
29
  "lib/paper_trail/has_paper_trail.rb",
28
30
  "lib/paper_trail/version.rb",
29
31
  "paper_trail.gemspec",
@@ -38,8 +40,7 @@ Gem::Specification.new do |s|
38
40
  "test/test_helper.rb",
39
41
  "uninstall.rb"
40
42
  ]
41
- s.has_rdoc = true
42
- s.homepage = %q{http://github.com/jeremyw/paper_trail}
43
+ s.homepage = %q{http://github.com/airblade/paper_trail}
43
44
  s.rdoc_options = ["--charset=UTF-8"]
44
45
  s.require_paths = ["lib"]
45
46
  s.rubygems_version = %q{1.3.5}
@@ -53,13 +54,14 @@ Gem::Specification.new do |s|
53
54
  "test/test_helper.rb"
54
55
  ]
55
56
 
56
- # if s.respond_to? :specification_version then
57
- # current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
58
- # s.specification_version = 2
59
- #
60
- # if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
61
- # else
62
- # end
63
- # else
64
- # end
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
62
+ else
63
+ end
64
+ else
65
+ end
65
66
  end
67
+
@@ -1,22 +1,18 @@
1
- sqlite:
2
- :adapter: sqlite
3
- :dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite.db
4
-
5
1
  sqlite3:
6
- :adapter: sqlite3
7
- :dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite3.db
2
+ adapter: sqlite3
3
+ database: ":memory:"
8
4
 
9
5
  postgresql:
10
- :adapter: postgresql
11
- :username: postgres
12
- :password: postgres
13
- :database: paper_trail_plugin_test
14
- :min_messages: ERROR
6
+ adapter: postgresql
7
+ username: postgres
8
+ password: postgres
9
+ database: paper_trail_plugin_test
10
+ min_messages: ERROR
15
11
 
16
12
  mysql:
17
- :adapter: mysql
18
- :host: localhost
19
- :username: andy
20
- :password:
21
- :database: paper_trail_plugin_test
22
- :socket: /tmp/mysql.sock
13
+ adapter: mysql
14
+ host: localhost
15
+ username: andy
16
+ password:
17
+ database: paper_trail_plugin_test
18
+ socket: /tmp/mysql.sock
@@ -1,8 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
- require 'application_controller'
3
- require 'action_controller/test_process'
4
2
 
5
- class ApplicationController
3
+ class ApplicationController < ActionController::Base
6
4
  def rescue_action(e)
7
5
  raise e
8
6
  end
@@ -17,10 +17,29 @@ class Fluxor < ActiveRecord::Base
17
17
  belongs_to :widget
18
18
  end
19
19
 
20
+ class Article < ActiveRecord::Base
21
+ has_paper_trail :ignore => [:title]
22
+ end
23
+
20
24
 
21
25
  class HasPaperTrailModelTest < Test::Unit::TestCase
22
26
  load_schema
23
27
 
28
+ context 'A record' do
29
+ setup { @article = Article.create }
30
+
31
+ context 'which updates an ignored column' do
32
+ setup { @article.update_attributes :title => 'My first title' }
33
+ should_not_change('the number of versions') { Version.count }
34
+ end
35
+
36
+ context 'which updates an ignored column and a non-ignored column' do
37
+ setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' }
38
+ should_change('the number of versions', :by => 1) { Version.count }
39
+ end
40
+
41
+ end
42
+
24
43
  context 'A new record' do
25
44
  setup { @widget = Widget.new }
26
45
 
@@ -194,7 +213,7 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
194
213
  should 'handle datetimes' do
195
214
  # Is there a better way to test equality of two datetimes?
196
215
  format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
197
- assert_equal @date_time.strftime(format), @previous.a_datetime.strftime(format)
216
+ assert_equal @date_time.utc.strftime(format), @previous.a_datetime.utc.strftime(format)
198
217
  end
199
218
 
200
219
  should 'handle times' do
@@ -224,14 +243,14 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
224
243
 
225
244
  should 'restore all forward-compatible attributes' do
226
245
  format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
227
- assert_equal 'Warble', @last.reify.name
228
- assert_equal 'The quick brown fox', @last.reify.a_text
229
- assert_equal 42, @last.reify.an_integer
230
- assert_in_delta 153.01, @last.reify.a_float, 0.001
231
- assert_in_delta 2.71828, @last.reify.a_decimal, 0.00001
232
- assert_equal @date_time.strftime(format), @last.reify.a_datetime.strftime(format)
233
- assert_equal @time, @last.reify.a_time
234
- assert_equal @date, @last.reify.a_date
246
+ assert_equal 'Warble', @last.reify.name
247
+ assert_equal 'The quick brown fox', @last.reify.a_text
248
+ assert_equal 42, @last.reify.an_integer
249
+ assert_in_delta 153.01, @last.reify.a_float, 0.001
250
+ assert_in_delta 2.71828, @last.reify.a_decimal, 0.00001
251
+ assert_equal @date_time.utc.strftime(format), @last.reify.a_datetime.utc.strftime(format)
252
+ assert_equal @time, @last.reify.a_time
253
+ assert_equal @date, @last.reify.a_date
235
254
  assert @last.reify.a_boolean
236
255
  end
237
256
  end
@@ -10,5 +10,6 @@ class PaperTrailSchemaTest < ActiveSupport::TestCase
10
10
  assert_equal [], Version.all
11
11
  assert_equal [], Wotsit.all
12
12
  assert_equal [], Fluxor.all
13
+ assert_equal [], Article.all
13
14
  end
14
15
  end
@@ -35,4 +35,9 @@ ActiveRecord::Schema.define(:version => 0) do
35
35
  t.string :name
36
36
  end
37
37
 
38
+ create_table :articles, :force => true do |t|
39
+ t.integer :title
40
+ t.string :content
41
+ end
42
+
38
43
  end
@@ -1,33 +1,29 @@
1
- require 'rubygems'
2
- require 'active_support'
3
- require 'active_support/test_case'
4
- require 'shoulda'
5
-
6
- ENV['RAILS_ENV'] = 'test'
7
- ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
8
-
9
1
  require 'test/unit'
10
- require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
2
+ RAILS_ROOT = File.join(File.dirname(__FILE__), %w{.. .. .. ..})
3
+ $:.unshift(File.join(File.dirname(__FILE__), %w{.. lib}))
4
+
5
+ unless defined?(ActiveRecord)
6
+ if File.directory? RAILS_ROOT + 'config'
7
+ puts 'using config/boot.rb'
8
+ ENV['RAILS_ENV'] = 'test'
9
+ require File.join(RAILS_ROOT, 'config', 'boot.rb')
10
+ else
11
+ # simply use installed gems if available
12
+ puts 'using rubygems'
13
+ require 'rubygems'
14
+ gem 'actionpack'; gem 'activerecord'; gem 'activesupport'; gem 'rails'
15
+ end
16
+
17
+ %w(action_pack action_controller active_record active_support initializer).each {|f| require f}
18
+ end
19
+ require 'shoulda'
20
+ require 'paper_trail'
11
21
 
12
22
  def connect_to_database
13
23
  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
14
24
  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
15
25
 
16
- db_adapter = ENV['DB']
17
-
18
- # no db passed, try one of these fine config-free DBs before bombing.
19
- db_adapter ||=
20
- begin
21
- require 'rubygems'
22
- require 'sqlite'
23
- 'sqlite'
24
- rescue MissingSourceFile
25
- begin
26
- require 'sqlite3'
27
- 'sqlite3'
28
- rescue MissingSourceFile
29
- end
30
- end
26
+ db_adapter = ENV['DB'] || 'sqlite3'
31
27
 
32
28
  if db_adapter.nil?
33
29
  raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
metadata CHANGED
@@ -1,22 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.14
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
8
- - Jeremy Weiskotten
9
- - Joe Lind
10
8
  autorequire:
11
9
  bindir: bin
12
10
  cert_chain: []
13
11
 
14
- date: 2009-10-02 00:00:00 -04:00
12
+ date: 2009-11-24 00:00:00 +00:00
15
13
  default_executable:
16
14
  dependencies: []
17
15
 
18
16
  description:
19
- email: jeremy@weiskotten.com
17
+ email: boss@airbladesoftware.com
20
18
  executables: []
21
19
 
22
20
  extensions: []
@@ -35,7 +33,6 @@ files:
35
33
  - init.rb
36
34
  - install.rb
37
35
  - lib/paper_trail.rb
38
- - lib/paper_trail/config.rb
39
36
  - lib/paper_trail/has_paper_trail.rb
40
37
  - lib/paper_trail/version.rb
41
38
  - paper_trail.gemspec
@@ -50,7 +47,7 @@ files:
50
47
  - test/test_helper.rb
51
48
  - uninstall.rb
52
49
  has_rdoc: true
53
- homepage: http://github.com/jeremyw/paper_trail
50
+ homepage: http://github.com/airblade/paper_trail
54
51
  licenses: []
55
52
 
56
53
  post_install_message:
@@ -1,10 +0,0 @@
1
- module PaperTrail
2
- class Config
3
- include Singleton
4
- attr_accessor :enabled
5
-
6
- def initialize
7
- @enabled = true
8
- end
9
- end
10
- end