paper_trail 1.2.14 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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