airblade-paper_trail 1.0.1

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