airblade-paper_trail 1.0.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # PaperTrail
2
+
3
+ Track changes to your models' data. Good for auditing or versioning.
4
+
5
+
6
+ ## Features
7
+
8
+ * Stores every create, update and destroy.
9
+ * Does not store updates which don't change anything.
10
+ * Allows you to get at every version, including the original, even once destroyed.
11
+ * Allows you to get at every version even if the schema has since changed.
12
+ * Automatically records who was responsible if your controller has a `current_user` method.
13
+ * Allows you to set who is responsible at model-level (useful for migrations).
14
+ * Can be turned off/on (useful for migrations).
15
+ * No configuration necessary.
16
+ * Stores everything in a single database table (generates migration for you).
17
+ * Thoroughly tested.
18
+
19
+
20
+ ## Rails Version
21
+
22
+ Known to work on Rails 2.3. Probably works on Rails 2.2 and 2.1.
23
+
24
+
25
+ ## Basic Usage
26
+
27
+ PaperTrail is simple to use. Just add 15 characters to a model to get a paper trail of every
28
+ `create`, `update`, and `destroy`.
29
+
30
+ class Widget < ActiveRecord::Base
31
+ has_paper_trail
32
+ end
33
+
34
+ This gives you a `versions` method which returns the paper trail of changes to your model.
35
+
36
+ >> widget = Widget.find 42
37
+ >> widget.versions # [<Version>, <Version>, ...]
38
+
39
+ Once you have a version, you can find out what happened:
40
+
41
+ >> v = widget.versions.last
42
+ >> v.event # 'update' (or 'create' or 'destroy')
43
+ >> v.whodunnit # '153' (if the update was via a controller and
44
+ # the controller has a current_user method,
45
+ # here returning the id of the current user)
46
+ >> v.created_at # when the update occurred
47
+ >> widget = v.reify # the widget as it was before the update;
48
+ # would be nil for a create event
49
+
50
+ PaperTrail stores the pre-change version of the model, unlike some other auditing/versioning
51
+ plugins, so you can retrieve the original version. This is useful when you start keeping a
52
+ paper trail for models that already have records in the database.
53
+
54
+ >> widget = Widget.find 153
55
+ >> widget.name # 'Doobly'
56
+ >> widget.versions # []
57
+ >> widget.update_attributes :name => 'Wotsit'
58
+ >> widget.versions.first.reify.name # 'Doobly'
59
+ >> widget.versions.first.event # 'update'
60
+
61
+ This also means that PaperTrail does not waste space storing a version of the object as it
62
+ currently stands. The `versions` method lets you get at previous versions only; after all,
63
+ you already know what the object currently looks like.
64
+
65
+ Here's a helpful table showing what PaperTrail stores:
66
+
67
+ <table>
68
+ <tr>
69
+ <th>Event</th>
70
+ <th>Model Before</th>
71
+ <th>Model After</th>
72
+ </tr>
73
+ <tr>
74
+ <td>create</td>
75
+ <td>nil</td>
76
+ <td>widget</td>
77
+ </tr>
78
+ <tr>
79
+ <td>update</td>
80
+ <td>widget</td>
81
+ <td>widget'</td>
82
+ <tr>
83
+ <td>destroy</td>
84
+ <td>widget</td>
85
+ <td>nil</td>
86
+ </tr>
87
+ </table>
88
+
89
+ PaperTrail stores the Before column. Most other auditing/versioning plugins store the After
90
+ column.
91
+
92
+
93
+ ## Finding Out Who Was Responsible For A Change
94
+
95
+ If your `ApplicationController` has a `current_user` method, PaperTrail will store the value it
96
+ returns in the `version`'s `whodunnit` column. Note that this column is a string so you will have
97
+ to convert it to an integer if it's an id and you want to look up the user later on:
98
+
99
+ >> last_change = Widget.versions.last
100
+ >> user_who_made_the_change = User.find last_change.whodunnit.to_i
101
+
102
+ In a migration or in `script/console` you can set who is responsible like this:
103
+
104
+ >> PaperTrail.whodunnit = 'Andy Stewart'
105
+ >> widget.update_attributes :name => 'Wibble'
106
+ >> widget.versions.last.whodunnit # Andy Stewart
107
+
108
+
109
+ ## Turning PaperTrail Off/On
110
+
111
+ Sometimes you don't want to store changes. Perhaps you are only interested in changes made
112
+ by your users and don't need to store changes you make yourself in, say, a migration.
113
+
114
+ If you are about change some widgets and you don't want a paper trail of your changes, you can
115
+ turn PaperTrail off like this:
116
+
117
+ >> Widget.paper_trail_off
118
+
119
+ And on again like this:
120
+
121
+ >> Widget.paper_trail_on
122
+
123
+
124
+ ## Installation
125
+
126
+ 1. Install PaperTrail either as a gem or as a plugin:
127
+
128
+ `config.gem 'airblade-paper_trail', :lib => 'paper_trail', :source => 'http://gems.github.com'`
129
+
130
+ `script/plugin install git://github.com/airblade/paper_trail.git`
131
+
132
+ 2. Generate a migration which wll add a `versions` table to your database.
133
+
134
+ `script/generate paper_trail`
135
+
136
+ 3. Run the migration.
137
+
138
+ `rake db:migrate`
139
+
140
+ 4. Add `has_paper_trail` to the models you want to track.
141
+
142
+
143
+ ## Testing
144
+
145
+ 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.
146
+
147
+
148
+ ## Inspirations
149
+
150
+ * [Simply Versioned](http://github.com/github/simply_versioned)
151
+ * [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
152
+
153
+
154
+ ## Intellectual Property
155
+
156
+ Copyright (c) 2009 Andy Stewart (boss@airbladesoftware.com).
157
+ Released under the MIT licence.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = 'paper_trail'
9
+ gemspec.summary = "Track changes to your models' data. Good for auditing or versioning."
10
+ gemspec.email = 'boss@airbladesoftware.com'
11
+ gemspec.homepage = 'http://github.com/airblade/paper_trail'
12
+ gemspec.authors = ['Andy Stewart']
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
17
+
18
+ desc 'Test the paper_trail plugin.'
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.libs << 'lib'
21
+ t.libs << 'test'
22
+ t.pattern = 'test/**/*_test.rb'
23
+ t.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ desc 'Generate documentation for the paper_trail plugin.'
40
+ Rake::RDocTask.new(:rdoc) do |rdoc|
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = 'PaperTrail'
43
+ rdoc.options << '--line-numbers' << '--inline-source'
44
+ rdoc.rdoc_files.include('README')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
47
+
48
+ desc 'Default: run unit tests.'
49
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,2 @@
1
+ Description:
2
+ Generates (but does not run) a migration to add a versions table.
@@ -0,0 +1,9 @@
1
+ class PaperTrailGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ m.migration_template 'create_versions.rb', 'db/migrate', :migration_file_name => 'create_versions'
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,16 @@
1
+ class CreateVersions < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :versions do |t|
4
+ t.string :item_type, :null => false
5
+ t.integer :item_id, :null => false
6
+ t.string :event, :null => false
7
+ t.string :whodunnit
8
+ t.text :object
9
+ t.datetime :created_at
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :versions
15
+ end
16
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ # Include hook code here
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+ require 'paper_trail/has_paper_trail'
3
+ require 'paper_trail/version'
4
+
5
+ module PaperTrail
6
+ @@whodunnit = nil
7
+
8
+ def self.included(base)
9
+ base.before_filter :set_whodunnit
10
+ end
11
+
12
+ def self.whodunnit
13
+ @@whodunnit.respond_to?(:call) ? @@whodunnit.call : @@whodunnit
14
+ end
15
+
16
+ def self.whodunnit=(value)
17
+ @@whodunnit = value
18
+ end
19
+
20
+ private
21
+
22
+ def set_whodunnit
23
+ @@whodunnit = lambda {
24
+ self.respond_to?(:current_user) ? self.current_user : nil
25
+ }
26
+ end
27
+ end
28
+
29
+ ActionController::Base.send :include, PaperTrail
@@ -0,0 +1,70 @@
1
+ module PaperTrail
2
+
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ end
6
+
7
+
8
+ module ClassMethods
9
+ def has_paper_trail
10
+ send :include, InstanceMethods
11
+
12
+ cattr_accessor :paper_trail_active
13
+ self.paper_trail_active = true
14
+
15
+ has_many :versions, :as => :item, :order => 'created_at ASC, id ASC'
16
+
17
+ after_create :record_create
18
+ before_update :record_update
19
+ after_destroy :record_destroy
20
+ end
21
+
22
+ def paper_trail_off
23
+ self.paper_trail_active = false
24
+ end
25
+
26
+ def paper_trail_on
27
+ self.paper_trail_active = true
28
+ end
29
+ end
30
+
31
+
32
+ module InstanceMethods
33
+ def record_create
34
+ versions.create(:event => 'create',
35
+ :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
36
+ end
37
+
38
+ def record_update
39
+ if changed? and self.class.paper_trail_active
40
+ versions.build :event => 'update',
41
+ :object => object_to_string(previous_version),
42
+ :whodunnit => PaperTrail.whodunnit
43
+ end
44
+ end
45
+
46
+ def record_destroy
47
+ versions.create(:event => 'destroy',
48
+ :object => object_to_string(previous_version),
49
+ :whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
50
+ end
51
+
52
+ private
53
+
54
+ def previous_version
55
+ previous = self.clone
56
+ previous.id = id
57
+ changes.each do |attr, ary|
58
+ previous.send "#{attr}=", ary.first
59
+ end
60
+ previous
61
+ end
62
+
63
+ def object_to_string(object)
64
+ object.attributes.to_yaml
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ ActiveRecord::Base.send :include, PaperTrail
@@ -0,0 +1,20 @@
1
+ class Version < ActiveRecord::Base
2
+ belongs_to :item, :polymorphic => true
3
+ validates_presence_of :event
4
+
5
+ def reify
6
+ unless object.nil?
7
+ # Using +item_type.constantize+ rather than +item.class+
8
+ # allows us to retrieve destroyed objects.
9
+ model = item_type.constantize.new
10
+ YAML::load(object).each do |k, v|
11
+ begin
12
+ model.send "#{k}=", v
13
+ rescue NoMethodError
14
+ RAILS_DEFAULT_LOGGER.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
15
+ end
16
+ end
17
+ model
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{paper_trail}
5
+ s.version = "1.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Andy Stewart"]
9
+ s.date = %q{2009-05-28}
10
+ s.email = %q{boss@airbladesoftware.com}
11
+ s.extra_rdoc_files = [
12
+ "README.md"
13
+ ]
14
+ s.files = [
15
+ "MIT-LICENSE",
16
+ "README.md",
17
+ "Rakefile",
18
+ "VERSION",
19
+ "generators/paper_trail/USAGE",
20
+ "generators/paper_trail/paper_trail_generator.rb",
21
+ "generators/paper_trail/templates/create_versions.rb",
22
+ "init.rb",
23
+ "install.rb",
24
+ "lib/paper_trail.rb",
25
+ "lib/paper_trail/has_paper_trail.rb",
26
+ "lib/paper_trail/version.rb",
27
+ "paper_trail.gemspec",
28
+ "rails/init.rb",
29
+ "tasks/paper_trail_tasks.rake",
30
+ "test/database.yml",
31
+ "test/paper_trail_controller_test.rb",
32
+ "test/paper_trail_model_test.rb",
33
+ "test/paper_trail_schema_test.rb",
34
+ "test/schema.rb",
35
+ "test/schema_change.rb",
36
+ "test/test_helper.rb",
37
+ "uninstall.rb"
38
+ ]
39
+ s.has_rdoc = true
40
+ s.homepage = %q{http://github.com/airblade/paper_trail}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.1}
44
+ s.summary = %q{Track changes to your models' data. Good for auditing or versioning.}
45
+ s.test_files = [
46
+ "test/paper_trail_controller_test.rb",
47
+ "test/paper_trail_model_test.rb",
48
+ "test/paper_trail_schema_test.rb",
49
+ "test/schema.rb",
50
+ "test/schema_change.rb",
51
+ "test/test_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 2
57
+
58
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
59
+ else
60
+ end
61
+ else
62
+ end
63
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'paper_trail'
File without changes
data/test/database.yml ADDED
@@ -0,0 +1,22 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite.db
4
+
5
+ sqlite3:
6
+ :adapter: sqlite3
7
+ :dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite3.db
8
+
9
+ postgresql:
10
+ :adapter: postgresql
11
+ :username: postgres
12
+ :password: postgres
13
+ :database: paper_trail_plugin_test
14
+ :min_messages: ERROR
15
+
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: andy
20
+ :password:
21
+ :database: paper_trail_plugin_test
22
+ :socket: /tmp/mysql.sock
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+ require 'application_controller'
3
+ require 'action_controller/test_process'
4
+
5
+ class ApplicationController
6
+ def rescue_action(e)
7
+ raise e
8
+ end
9
+
10
+ # Returns id of hypothetical current user
11
+ def current_user
12
+ 153
13
+ end
14
+ end
15
+
16
+ class WidgetsController < ApplicationController
17
+ def create
18
+ @widget = Widget.create params[:widget]
19
+ head :ok
20
+ end
21
+
22
+ def update
23
+ @widget = Widget.find params[:id]
24
+ @widget.update_attributes params[:widget]
25
+ head :ok
26
+ end
27
+
28
+ def destroy
29
+ @widget = Widget.find params[:id]
30
+ @widget.destroy
31
+ head :ok
32
+ end
33
+ end
34
+
35
+
36
+ class PaperTrailControllerTest < ActionController::TestCase #Test::Unit::TestCase
37
+ def setup
38
+ @controller = WidgetsController.new
39
+ @request = ActionController::TestRequest.new
40
+ @response = ActionController::TestResponse.new
41
+
42
+ ActionController::Routing::Routes.draw do |map|
43
+ map.resources :widgets
44
+ end
45
+ end
46
+
47
+ test 'create' do
48
+ post :create, :widget => { :name => 'Flugel' }
49
+ widget = assigns(:widget)
50
+ assert_equal 1, widget.versions.length
51
+ assert_equal 153, widget.versions.last.whodunnit.to_i
52
+ end
53
+
54
+ test 'update' do
55
+ w = Widget.create :name => 'Duvel'
56
+ assert_equal 1, w.versions.length
57
+ put :update, :id => w.id, :widget => { :name => 'Bugle' }
58
+ widget = assigns(:widget)
59
+ assert_equal 2, widget.versions.length
60
+ assert_equal 153, widget.versions.last.whodunnit.to_i
61
+ end
62
+
63
+ test 'destroy' do
64
+ w = Widget.create :name => 'Roundel'
65
+ assert_equal 1, w.versions.length
66
+ delete :destroy, :id => w.id
67
+ widget = assigns(:widget)
68
+ assert_equal 2, widget.versions.length
69
+ assert_equal 153, widget.versions.last.whodunnit.to_i
70
+ end
71
+ end
72
+
@@ -0,0 +1,251 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class Widget < ActiveRecord::Base
4
+ has_paper_trail
5
+ end
6
+
7
+
8
+ class HasPaperTrailModelTest < Test::Unit::TestCase
9
+ load_schema
10
+
11
+ context 'A new record' do
12
+ setup { @widget = Widget.new }
13
+
14
+ should 'not have any previous versions' do
15
+ assert_equal [], @widget.versions
16
+ end
17
+
18
+
19
+ context 'which is then created' do
20
+ setup { @widget.update_attributes :name => 'Henry' }
21
+
22
+ should 'have one previous version' do
23
+ assert_equal 1, @widget.versions.length
24
+ end
25
+
26
+ should 'be nil in its previous version' do
27
+ assert_nil @widget.versions.first.object
28
+ assert_nil @widget.versions.first.reify
29
+ end
30
+
31
+ should 'record the correct event' do
32
+ assert_match /create/i, @widget.versions.first.event
33
+ end
34
+
35
+
36
+ context 'and then updated without any changes' do
37
+ setup { @widget.save }
38
+
39
+ should 'not have a new version' do
40
+ assert_equal 1, @widget.versions.length
41
+ end
42
+ end
43
+
44
+
45
+ context 'and then updated with changes' do
46
+ setup { @widget.update_attributes :name => 'Harry' }
47
+
48
+ should 'have two previous versions' do
49
+ assert_equal 2, @widget.versions.length
50
+ end
51
+
52
+ should 'be available in its previous version' do
53
+ assert_equal 'Harry', @widget.name
54
+ assert_not_nil @widget.versions.last.object
55
+ widget = @widget.versions.last.reify
56
+ assert_equal 'Henry', widget.name
57
+ assert_equal 'Harry', @widget.name
58
+ end
59
+
60
+ should 'have the same ID in its previous version' do
61
+ assert_equal @widget.id, @widget.versions.last.reify.id
62
+ end
63
+
64
+ should 'record the correct event' do
65
+ assert_match /update/i, @widget.versions.last.event
66
+ end
67
+
68
+
69
+ context 'and then destroyed' do
70
+ setup { @widget.destroy }
71
+
72
+ should 'have three previous versions' do
73
+ assert_equal 3, @widget.versions.length
74
+ end
75
+
76
+ should 'be available in its previous version' do
77
+ widget = @widget.versions.last.reify
78
+ assert_equal @widget.id, widget.id
79
+ assert_equal @widget.attributes, widget.attributes
80
+ end
81
+
82
+ should 'record the correct event' do
83
+ assert_match /destroy/i, @widget.versions.last.event
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ # Test the serialisation and unserialisation.
92
+ # TODO: binary
93
+ context "A record's papertrail" do
94
+ setup do
95
+ @date_time = DateTime.now.utc
96
+ @time = Time.now
97
+ @date = Date.new 2009, 5, 29
98
+ @widget = Widget.create :name => 'Warble',
99
+ :a_text => 'The quick brown fox',
100
+ :an_integer => 42,
101
+ :a_float => 153.01,
102
+ :a_decimal => 2.71828,
103
+ :a_datetime => @date_time,
104
+ :a_time => @time,
105
+ :a_date => @date,
106
+ :a_boolean => true
107
+
108
+ @widget.update_attributes :name => nil,
109
+ :a_text => nil,
110
+ :an_integer => nil,
111
+ :a_float => nil,
112
+ :a_decimal => nil,
113
+ :a_datetime => nil,
114
+ :a_time => nil,
115
+ :a_date => nil,
116
+ :a_boolean => false
117
+ @previous = @widget.versions.last.reify
118
+ end
119
+
120
+ should 'handle strings' do
121
+ assert_equal 'Warble', @previous.name
122
+ end
123
+
124
+ should 'handle text' do
125
+ assert_equal 'The quick brown fox', @previous.a_text
126
+ end
127
+
128
+ should 'handle integers' do
129
+ assert_equal 42, @previous.an_integer
130
+ end
131
+
132
+ should 'handle floats' do
133
+ assert_in_delta 153.01, @previous.a_float, 0.001
134
+ end
135
+
136
+ should 'handle decimals' do
137
+ assert_in_delta 2.71828, @previous.a_decimal, 0.00001
138
+ end
139
+
140
+ should 'handle datetimes' do
141
+ # Is there a better way to test equality of two datetimes?
142
+ format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
143
+ assert_equal @date_time.strftime(format), @previous.a_datetime.strftime(format)
144
+ end
145
+
146
+ should 'handle times' do
147
+ assert_equal @time, @previous.a_time
148
+ end
149
+
150
+ should 'handle dates' do
151
+ assert_equal @date, @previous.a_date
152
+ end
153
+
154
+ should 'handle booleans' do
155
+ assert @previous.a_boolean
156
+ end
157
+
158
+
159
+ context "after a column is removed from the record's schema" do
160
+ setup do
161
+ change_schema
162
+ Widget.reset_column_information
163
+ assert_raise(NoMethodError) { Widget.new.sacrificial_column }
164
+ @last = @widget.versions.last
165
+ end
166
+
167
+ should 'reify previous version' do
168
+ assert_kind_of Widget, @last.reify
169
+ end
170
+
171
+ should 'restore all forward-compatible attributes' do
172
+ format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
173
+ assert_equal 'Warble', @last.reify.name
174
+ assert_equal 'The quick brown fox', @last.reify.a_text
175
+ assert_equal 42, @last.reify.an_integer
176
+ assert_in_delta 153.01, @last.reify.a_float, 0.001
177
+ assert_in_delta 2.71828, @last.reify.a_decimal, 0.00001
178
+ assert_equal @date_time.strftime(format), @last.reify.a_datetime.strftime(format)
179
+ assert_equal @time, @last.reify.a_time
180
+ assert_equal @date, @last.reify.a_date
181
+ assert @last.reify.a_boolean
182
+ end
183
+ end
184
+ end
185
+
186
+
187
+ context 'A record' do
188
+ setup { @widget = Widget.create :name => 'Zaphod' }
189
+
190
+ context 'with its paper trail turned off' do
191
+ setup do
192
+ Widget.paper_trail_off
193
+ @count = @widget.versions.length
194
+ end
195
+
196
+ teardown { Widget.paper_trail_on }
197
+
198
+ context 'when updated' do
199
+ setup { @widget.update_attributes :name => 'Beeblebrox' }
200
+
201
+ should 'not add to its trail' do
202
+ assert_equal @count, @widget.versions.length
203
+ end
204
+ end
205
+
206
+ context 'and then its paper trail turned on' do
207
+ setup { Widget.paper_trail_on }
208
+
209
+ context 'when updated' do
210
+ setup { @widget.update_attributes :name => 'Ford' }
211
+
212
+ should 'add to its trail' do
213
+ assert_equal @count + 1, @widget.versions.length
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+
221
+ context 'A papertrail with somebody making changes' do
222
+ setup do
223
+ PaperTrail.whodunnit = 'Colonel Mustard'
224
+ @widget = Widget.new :name => 'Fidget'
225
+ end
226
+
227
+ context 'when a record is created' do
228
+ setup { @widget.save }
229
+
230
+ should 'track who made the change' do
231
+ assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
232
+ end
233
+
234
+ context 'when a record is updated' do
235
+ setup { @widget.update_attributes :name => 'Rivet' }
236
+
237
+ should 'track who made the change' do
238
+ assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
239
+ end
240
+
241
+ context 'when a record is destroyed' do
242
+ setup { @widget.destroy }
243
+
244
+ should 'track who made the change' do
245
+ assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class PaperTrailTest < ActiveSupport::TestCase
4
+ def setup
5
+ load_schema
6
+ end
7
+
8
+ def test_schema_has_loaded_correctly
9
+ assert_equal [], Widget.all
10
+ assert_equal [], Version.all
11
+ assert_equal [], User.all
12
+ end
13
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,31 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+
3
+ create_table :widgets, :force => true do |t|
4
+ t.string :name
5
+ t.text :a_text
6
+ t.integer :an_integer
7
+ t.float :a_float
8
+ t.decimal :a_decimal
9
+ t.datetime :a_datetime
10
+ t.time :a_time
11
+ t.date :a_date
12
+ t.boolean :a_boolean
13
+ t.datetime :created_at, :updated_at
14
+ t.string :sacrificial_column
15
+ end
16
+
17
+ create_table :users, :force => true do |t|
18
+ t.string :name
19
+ t.datetime :created_at, :updated_at
20
+ end
21
+
22
+ create_table :versions, :force => true do |t|
23
+ t.string :item_type, :null => false
24
+ t.integer :item_id, :null => false
25
+ t.string :event, :null => false
26
+ t.string :whodunnit
27
+ t.text :object
28
+ t.datetime :created_at
29
+ end
30
+
31
+ end
@@ -0,0 +1,3 @@
1
+ ActiveRecord::Schema.define do
2
+ remove_column :widgets, :sacrificial_column
3
+ end
@@ -0,0 +1,47 @@
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
+ require 'test/unit'
10
+ require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
11
+
12
+ def connect_to_database
13
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
14
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
15
+
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
31
+
32
+ if db_adapter.nil?
33
+ raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
34
+ end
35
+
36
+ ActiveRecord::Base.establish_connection(config[db_adapter])
37
+ end
38
+
39
+ def load_schema
40
+ connect_to_database
41
+ load(File.dirname(__FILE__) + "/schema.rb")
42
+ require File.dirname(__FILE__) + '/../rails/init.rb'
43
+ end
44
+
45
+ def change_schema
46
+ load(File.dirname(__FILE__) + "/schema_change.rb")
47
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: airblade-paper_trail
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andy Stewart
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: boss@airbladesoftware.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - VERSION
29
+ - generators/paper_trail/USAGE
30
+ - generators/paper_trail/paper_trail_generator.rb
31
+ - generators/paper_trail/templates/create_versions.rb
32
+ - init.rb
33
+ - install.rb
34
+ - lib/paper_trail.rb
35
+ - lib/paper_trail/has_paper_trail.rb
36
+ - lib/paper_trail/version.rb
37
+ - paper_trail.gemspec
38
+ - rails/init.rb
39
+ - tasks/paper_trail_tasks.rake
40
+ - test/database.yml
41
+ - test/paper_trail_controller_test.rb
42
+ - test/paper_trail_model_test.rb
43
+ - test/paper_trail_schema_test.rb
44
+ - test/schema.rb
45
+ - test/schema_change.rb
46
+ - test/test_helper.rb
47
+ - uninstall.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/airblade/paper_trail
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Track changes to your models' data. Good for auditing or versioning.
74
+ test_files:
75
+ - test/paper_trail_controller_test.rb
76
+ - test/paper_trail_model_test.rb
77
+ - test/paper_trail_schema_test.rb
78
+ - test/schema.rb
79
+ - test/schema_change.rb
80
+ - test/test_helper.rb