First commit.
This commit is contained in:
commit
e9a8648c22
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2009 [name of plugin creator]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,152 @@
|
|||
# PaperTrail
|
||||
|
||||
Track changes to your models' data. Good for auditing or versioning.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Stores every create, update and destroy.
|
||||
* Does not store updates which don't change anything.
|
||||
* Allows you to get at every version, including the original, even once destroyed.
|
||||
* Allows you to get at every version even if the schema has since changed.
|
||||
* Automatically records who was responsible if your controller has a `current_user` method.
|
||||
* Allows you to set who is responsible at model-level (useful for migrations).
|
||||
* Can be turned off/on (useful for migrations).
|
||||
* No configuration necessary.
|
||||
* Stores everything in a single database table (generates migration for you).
|
||||
* Thoroughly tested.
|
||||
|
||||
|
||||
## Rails Version
|
||||
|
||||
Known to work on Rails 2.3. Probably works on Rails 2.2 and 2.1.
|
||||
|
||||
|
||||
## Basic Usage
|
||||
|
||||
PaperTrail is simple to use. Just add 15 characters to a model to get a paper trail of every
|
||||
`create`, `update`, and `destroy`.
|
||||
|
||||
class Widget < ActiveRecord::Base
|
||||
has_paper_trail
|
||||
end
|
||||
|
||||
This gives you a `versions` method which returns the paper trail of changes to your model.
|
||||
|
||||
>> widget = Widget.find 42
|
||||
>> widget.versions # [<Version>, <Version>, ...]
|
||||
|
||||
Once you have a version, you can find out what happened:
|
||||
|
||||
>> v = widget.versions.last
|
||||
>> v.event # 'update' (or 'create' or 'destroy')
|
||||
>> v.whodunnit # '153' (if the update was via a controller and
|
||||
# the controller has a current_user method,
|
||||
# here returning the id of the current user)
|
||||
>> v.created_at # when the update occurred
|
||||
>> widget = v.reify # the widget as it was before the update;
|
||||
# would be nil for a create event
|
||||
|
||||
PaperTrail stores the pre-change version of the model, unlike some other auditing/versioning
|
||||
plugins, so you can retrieve the original version. This is useful when you start keeping a
|
||||
paper trail for models that already have records in the database.
|
||||
|
||||
>> widget = Widget.find 153
|
||||
>> widget.name # 'Doobly'
|
||||
>> widget.versions # []
|
||||
>> widget.update_attributes :name => 'Wotsit'
|
||||
>> widget.versions.first.reify.name # 'Doobly'
|
||||
>> widget.versions.first.event # 'update'
|
||||
|
||||
This also means that PaperTrail does not waste space storing a version of the object as it
|
||||
currently stands. The `versions` method lets you get at previous versions only; after all,
|
||||
you already know what the object currently looks like.
|
||||
|
||||
Here's a helpful table showing what PaperTrail stores:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Model Before</th>
|
||||
<th>Model After</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>create</td>
|
||||
<td>nil</td>
|
||||
<td>widget</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>update</td>
|
||||
<td>widget</td>
|
||||
<td>widget'</td>
|
||||
<tr>
|
||||
<td>destroy</td>
|
||||
<td>widget</td>
|
||||
<td>nil</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
PaperTrail stores the Before column. Most other auditing/versioning plugins store the After
|
||||
column.
|
||||
|
||||
|
||||
## Finding Out Who Was Responsible For A Change
|
||||
|
||||
If your `ApplicationController` has a `current_user` method, PaperTrail will store the value it
|
||||
returns in the `version`'s `whodunnit` column. Note that this column is a string so you will have
|
||||
to convert it to an integer if it's an id and you want to look up the user later on:
|
||||
|
||||
>> last_change = Widget.versions.last
|
||||
>> user_who_made_the_change = User.find last_change.whodunnit.to_i
|
||||
|
||||
In a migration or in `script/console` you can set who is responsible like this:
|
||||
|
||||
>> PaperTrail.whodunnit = 'Andy Stewart'
|
||||
>> widget.update_attributes :name => 'Wibble'
|
||||
>> widget.versions.last.whodunnit # Andy Stewart
|
||||
|
||||
|
||||
## Turning PaperTrail Off/On
|
||||
|
||||
Sometimes you don't want to store changes. Perhaps you are only interested in changes made
|
||||
by your users and don't need to store changes you make yourself in, say, a migration.
|
||||
|
||||
If you are about change some widgets and you don't want a paper trail of your changes, you can
|
||||
turn PaperTrail off like this:
|
||||
|
||||
>> Widget.paper_trail_off
|
||||
|
||||
And on again like this:
|
||||
|
||||
>> Widget.paper_trail_on
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install PaperTrail either as a gem or as a plugin:
|
||||
|
||||
config.gem 'airblade-paper_trail', :lib => 'paper_trail', :source => 'http://gems.github.com'
|
||||
|
||||
script/plugin install git://github.com/airblade/paper_trail.git
|
||||
|
||||
2. Generate a migration which wll add a `versions` table to your database.
|
||||
|
||||
script/generate paper_trail
|
||||
|
||||
3. Run the migration.
|
||||
|
||||
rake db:migrate
|
||||
|
||||
4. Add `has_paper_trail` to the models you want to track.
|
||||
|
||||
|
||||
## Inspirations
|
||||
|
||||
* [Simply Versioned](http://github.com/github/simply_versioned)
|
||||
* [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
|
||||
|
||||
|
||||
## Intellectual Property
|
||||
|
||||
Copyright (c) 2009 Andy Stewart (boss@airbladesoftware.com).
|
||||
Released under the MIT licence.
|
|
@ -0,0 +1,23 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the paper_trail plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the paper_trail plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'PaperTrail'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
Description:
|
||||
Generates (but does not run) a migration to add a versions table.
|
|
@ -0,0 +1,9 @@
|
|||
class PaperTrailGenerator < Rails::Generator::Base
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
m.migration_template 'create_versions.rb', 'db/migrate', :migration_file_name => 'create_versions'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class CreateVersions < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :versions do |t|
|
||||
t.string :item_type, :null => false
|
||||
t.integer :item_id, :null => false
|
||||
t.string :event, :null => false
|
||||
t.string :whodunnit
|
||||
t.text :object
|
||||
t.datetime :created_at
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :versions
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
# Install hook code here
|
|
@ -0,0 +1,31 @@
|
|||
require 'yaml'
|
||||
require 'paper_trail/has_paper_trail'
|
||||
require 'paper_trail/version'
|
||||
|
||||
module PaperTrail
|
||||
VERSION = '1.0.0'
|
||||
|
||||
@@whodunnit = nil
|
||||
|
||||
def self.included(base)
|
||||
base.before_filter :set_whodunnit
|
||||
end
|
||||
|
||||
def self.whodunnit
|
||||
@@whodunnit.respond_to?(:call) ? @@whodunnit.call : @@whodunnit
|
||||
end
|
||||
|
||||
def self.whodunnit=(value)
|
||||
@@whodunnit = value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_whodunnit
|
||||
@@whodunnit = lambda {
|
||||
self.respond_to?(:current_user) ? self.current_user : nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Base.send :include, PaperTrail
|
|
@ -0,0 +1,70 @@
|
|||
module PaperTrail
|
||||
|
||||
def self.included(base)
|
||||
base.send :extend, ClassMethods
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
def has_paper_trail
|
||||
send :include, InstanceMethods
|
||||
|
||||
cattr_accessor :paper_trail_active
|
||||
self.paper_trail_active = true
|
||||
|
||||
has_many :versions, :as => :item, :order => 'created_at ASC, id ASC'
|
||||
|
||||
after_create :record_create
|
||||
before_update :record_update
|
||||
after_destroy :record_destroy
|
||||
end
|
||||
|
||||
def paper_trail_off
|
||||
self.paper_trail_active = false
|
||||
end
|
||||
|
||||
def paper_trail_on
|
||||
self.paper_trail_active = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
def record_create
|
||||
versions.create(:event => 'create',
|
||||
:whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
|
||||
end
|
||||
|
||||
def record_update
|
||||
if changed? and self.class.paper_trail_active
|
||||
versions.build :event => 'update',
|
||||
:object => object_to_string(previous_version),
|
||||
:whodunnit => PaperTrail.whodunnit
|
||||
end
|
||||
end
|
||||
|
||||
def record_destroy
|
||||
versions.create(:event => 'destroy',
|
||||
:object => object_to_string(previous_version),
|
||||
:whodunnit => PaperTrail.whodunnit) if self.class.paper_trail_active
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def previous_version
|
||||
previous = self.clone
|
||||
previous.id = id
|
||||
changes.each do |attr, ary|
|
||||
previous.send "#{attr}=", ary.first
|
||||
end
|
||||
previous
|
||||
end
|
||||
|
||||
def object_to_string(object)
|
||||
object.attributes.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
ActiveRecord::Base.send :include, PaperTrail
|
|
@ -0,0 +1,20 @@
|
|||
class Version < ActiveRecord::Base
|
||||
belongs_to :item, :polymorphic => true
|
||||
validates_presence_of :event
|
||||
|
||||
def reify
|
||||
unless object.nil?
|
||||
# Using +item_type.constantize+ rather than +item.class+
|
||||
# allows us to retrieve destroyed objects.
|
||||
model = item_type.constantize.new
|
||||
YAML::load(object).each do |k, v|
|
||||
begin
|
||||
model.send "#{k}=", v
|
||||
rescue NoMethodError
|
||||
RAILS_DEFAULT_LOGGER.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
|
||||
end
|
||||
end
|
||||
model
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
require 'paper_trail'
|
|
@ -0,0 +1,16 @@
|
|||
require 'rubygems'
|
||||
require 'rake'
|
||||
|
||||
begin
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |gemspec|
|
||||
gemspec.name = 'paper_trail'
|
||||
gemspec.summary = "Track changes to your models' data. Good for auditing or versioning."
|
||||
gemspec.email = 'boss@airbladesoftware.com'
|
||||
gemspec.homepage = 'http://github.com/airblade/paper_trail'
|
||||
gemspec.authors = ['Andy Stewart']
|
||||
end
|
||||
rescue LoadError
|
||||
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
||||
end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
sqlite:
|
||||
:adapter: sqlite
|
||||
:dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite.db
|
||||
|
||||
sqlite3:
|
||||
:adapter: sqlite3
|
||||
:dbfile: vendor/plugins/paper_trail/test/paper_trail_plugin.sqlite3.db
|
||||
|
||||
postgresql:
|
||||
:adapter: postgresql
|
||||
:username: postgres
|
||||
:password: postgres
|
||||
:database: paper_trail_plugin_test
|
||||
:min_messages: ERROR
|
||||
|
||||
mysql:
|
||||
:adapter: mysql
|
||||
:host: localhost
|
||||
:username: andy
|
||||
:password:
|
||||
:database: paper_trail_plugin_test
|
||||
:socket: /tmp/mysql.sock
|
|
@ -0,0 +1,72 @@
|
|||
require File.dirname(__FILE__) + '/test_helper.rb'
|
||||
require 'application_controller'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
class ApplicationController
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
end
|
||||
|
||||
# Returns id of hypothetical current user
|
||||
def current_user
|
||||
153
|
||||
end
|
||||
end
|
||||
|
||||
class WidgetsController < ApplicationController
|
||||
def create
|
||||
@widget = Widget.create params[:widget]
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@widget = Widget.find params[:id]
|
||||
@widget.update_attributes params[:widget]
|
||||
head :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
@widget = Widget.find params[:id]
|
||||
@widget.destroy
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class PaperTrailControllerTest < ActionController::TestCase #Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = WidgetsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.resources :widgets
|
||||
end
|
||||
end
|
||||
|
||||
test 'create' do
|
||||
post :create, :widget => { :name => 'Flugel' }
|
||||
widget = assigns(:widget)
|
||||
assert_equal 1, widget.versions.length
|
||||
assert_equal 153, widget.versions.last.whodunnit.to_i
|
||||
end
|
||||
|
||||
test 'update' do
|
||||
w = Widget.create :name => 'Duvel'
|
||||
assert_equal 1, w.versions.length
|
||||
put :update, :id => w.id, :widget => { :name => 'Bugle' }
|
||||
widget = assigns(:widget)
|
||||
assert_equal 2, widget.versions.length
|
||||
assert_equal 153, widget.versions.last.whodunnit.to_i
|
||||
end
|
||||
|
||||
test 'destroy' do
|
||||
w = Widget.create :name => 'Roundel'
|
||||
assert_equal 1, w.versions.length
|
||||
delete :destroy, :id => w.id
|
||||
widget = assigns(:widget)
|
||||
assert_equal 2, widget.versions.length
|
||||
assert_equal 153, widget.versions.last.whodunnit.to_i
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
require File.dirname(__FILE__) + '/test_helper.rb'
|
||||
|
||||
class Widget < ActiveRecord::Base
|
||||
has_paper_trail
|
||||
end
|
||||
|
||||
|
||||
class HasPaperTrailModelTest < Test::Unit::TestCase
|
||||
load_schema
|
||||
|
||||
context 'A new record' do
|
||||
setup { @widget = Widget.new }
|
||||
|
||||
should 'not have any previous versions' do
|
||||
assert_equal [], @widget.versions
|
||||
end
|
||||
|
||||
|
||||
context 'which is then created' do
|
||||
setup { @widget.update_attributes :name => 'Henry' }
|
||||
|
||||
should 'have one previous version' do
|
||||
assert_equal 1, @widget.versions.length
|
||||
end
|
||||
|
||||
should 'be nil in its previous version' do
|
||||
assert_nil @widget.versions.first.object
|
||||
assert_nil @widget.versions.first.reify
|
||||
end
|
||||
|
||||
should 'record the correct event' do
|
||||
assert_match /create/i, @widget.versions.first.event
|
||||
end
|
||||
|
||||
|
||||
context 'and then updated without any changes' do
|
||||
setup { @widget.save }
|
||||
|
||||
should 'not have a new version' do
|
||||
assert_equal 1, @widget.versions.length
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'and then updated with changes' do
|
||||
setup { @widget.update_attributes :name => 'Harry' }
|
||||
|
||||
should 'have two previous versions' do
|
||||
assert_equal 2, @widget.versions.length
|
||||
end
|
||||
|
||||
should 'be available in its previous version' do
|
||||
assert_equal 'Harry', @widget.name
|
||||
assert_not_nil @widget.versions.last.object
|
||||
widget = @widget.versions.last.reify
|
||||
assert_equal 'Henry', widget.name
|
||||
assert_equal 'Harry', @widget.name
|
||||
end
|
||||
|
||||
should 'have the same ID in its previous version' do
|
||||
assert_equal @widget.id, @widget.versions.last.reify.id
|
||||
end
|
||||
|
||||
should 'record the correct event' do
|
||||
assert_match /update/i, @widget.versions.last.event
|
||||
end
|
||||
|
||||
|
||||
context 'and then destroyed' do
|
||||
setup { @widget.destroy }
|
||||
|
||||
should 'have three previous versions' do
|
||||
assert_equal 3, @widget.versions.length
|
||||
end
|
||||
|
||||
should 'be available in its previous version' do
|
||||
widget = @widget.versions.last.reify
|
||||
assert_equal @widget.id, widget.id
|
||||
assert_equal @widget.attributes, widget.attributes
|
||||
end
|
||||
|
||||
should 'record the correct event' do
|
||||
assert_match /destroy/i, @widget.versions.last.event
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Test the serialisation and unserialisation.
|
||||
# TODO: binary
|
||||
context "A record's papertrail" do
|
||||
setup do
|
||||
@date_time = DateTime.now.utc
|
||||
@time = Time.now
|
||||
@date = Date.new 2009, 5, 29
|
||||
@widget = Widget.create :name => 'Warble',
|
||||
:a_text => 'The quick brown fox',
|
||||
:an_integer => 42,
|
||||
:a_float => 153.01,
|
||||
:a_decimal => 2.71828,
|
||||
:a_datetime => @date_time,
|
||||
:a_time => @time,
|
||||
:a_date => @date,
|
||||
:a_boolean => true
|
||||
|
||||
@widget.update_attributes :name => nil,
|
||||
:a_text => nil,
|
||||
:an_integer => nil,
|
||||
:a_float => nil,
|
||||
:a_decimal => nil,
|
||||
:a_datetime => nil,
|
||||
:a_time => nil,
|
||||
:a_date => nil,
|
||||
:a_boolean => false
|
||||
@previous = @widget.versions.last.reify
|
||||
end
|
||||
|
||||
should 'handle strings' do
|
||||
assert_equal 'Warble', @previous.name
|
||||
end
|
||||
|
||||
should 'handle text' do
|
||||
assert_equal 'The quick brown fox', @previous.a_text
|
||||
end
|
||||
|
||||
should 'handle integers' do
|
||||
assert_equal 42, @previous.an_integer
|
||||
end
|
||||
|
||||
should 'handle floats' do
|
||||
assert_in_delta 153.01, @previous.a_float, 0.001
|
||||
end
|
||||
|
||||
should 'handle decimals' do
|
||||
assert_in_delta 2.71828, @previous.a_decimal, 0.00001
|
||||
end
|
||||
|
||||
should 'handle datetimes' do
|
||||
# Is there a better way to test equality of two datetimes?
|
||||
format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
|
||||
assert_equal @date_time.strftime(format), @previous.a_datetime.strftime(format)
|
||||
end
|
||||
|
||||
should 'handle times' do
|
||||
assert_equal @time, @previous.a_time
|
||||
end
|
||||
|
||||
should 'handle dates' do
|
||||
assert_equal @date, @previous.a_date
|
||||
end
|
||||
|
||||
should 'handle booleans' do
|
||||
assert @previous.a_boolean
|
||||
end
|
||||
|
||||
|
||||
context "after a column is removed from the record's schema" do
|
||||
setup do
|
||||
change_schema
|
||||
Widget.reset_column_information
|
||||
assert_raise(NoMethodError) { Widget.new.sacrificial_column }
|
||||
@last = @widget.versions.last
|
||||
end
|
||||
|
||||
should 'reify previous version' do
|
||||
assert_kind_of Widget, @last.reify
|
||||
end
|
||||
|
||||
should 'restore all forward-compatible attributes' do
|
||||
format = '%a, %d %b %Y %H:%M:%S %z' # :rfc822
|
||||
assert_equal 'Warble', @last.reify.name
|
||||
assert_equal 'The quick brown fox', @last.reify.a_text
|
||||
assert_equal 42, @last.reify.an_integer
|
||||
assert_in_delta 153.01, @last.reify.a_float, 0.001
|
||||
assert_in_delta 2.71828, @last.reify.a_decimal, 0.00001
|
||||
assert_equal @date_time.strftime(format), @last.reify.a_datetime.strftime(format)
|
||||
assert_equal @time, @last.reify.a_time
|
||||
assert_equal @date, @last.reify.a_date
|
||||
assert @last.reify.a_boolean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'A record' do
|
||||
setup { @widget = Widget.create :name => 'Zaphod' }
|
||||
|
||||
context 'with its paper trail turned off' do
|
||||
setup do
|
||||
Widget.paper_trail_off
|
||||
@count = @widget.versions.length
|
||||
end
|
||||
|
||||
teardown { Widget.paper_trail_on }
|
||||
|
||||
context 'when updated' do
|
||||
setup { @widget.update_attributes :name => 'Beeblebrox' }
|
||||
|
||||
should 'not add to its trail' do
|
||||
assert_equal @count, @widget.versions.length
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then its paper trail turned on' do
|
||||
setup { Widget.paper_trail_on }
|
||||
|
||||
context 'when updated' do
|
||||
setup { @widget.update_attributes :name => 'Ford' }
|
||||
|
||||
should 'add to its trail' do
|
||||
assert_equal @count + 1, @widget.versions.length
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'A papertrail with somebody making changes' do
|
||||
setup do
|
||||
PaperTrail.whodunnit = 'Colonel Mustard'
|
||||
@widget = Widget.new :name => 'Fidget'
|
||||
end
|
||||
|
||||
context 'when a record is created' do
|
||||
setup { @widget.save }
|
||||
|
||||
should 'track who made the change' do
|
||||
assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
|
||||
end
|
||||
|
||||
context 'when a record is updated' do
|
||||
setup { @widget.update_attributes :name => 'Rivet' }
|
||||
|
||||
should 'track who made the change' do
|
||||
assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
|
||||
end
|
||||
|
||||
context 'when a record is destroyed' do
|
||||
setup { @widget.destroy }
|
||||
|
||||
should 'track who made the change' do
|
||||
assert_equal 'Colonel Mustard', @widget.versions.last.whodunnit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
require 'test_helper'
|
||||
|
||||
class PaperTrailTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
load_schema
|
||||
end
|
||||
|
||||
def test_schema_has_loaded_correctly
|
||||
assert_equal [], Widget.all
|
||||
assert_equal [], Version.all
|
||||
assert_equal [], User.all
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
ActiveRecord::Schema.define(:version => 0) do
|
||||
|
||||
create_table :widgets, :force => true do |t|
|
||||
t.string :name
|
||||
t.text :a_text
|
||||
t.integer :an_integer
|
||||
t.float :a_float
|
||||
t.decimal :a_decimal
|
||||
t.datetime :a_datetime
|
||||
t.time :a_time
|
||||
t.date :a_date
|
||||
t.boolean :a_boolean
|
||||
t.datetime :created_at, :updated_at
|
||||
t.string :sacrificial_column
|
||||
end
|
||||
|
||||
create_table :users, :force => true do |t|
|
||||
t.string :name
|
||||
t.datetime :created_at, :updated_at
|
||||
end
|
||||
|
||||
create_table :versions, :force => true do |t|
|
||||
t.string :item_type, :null => false
|
||||
t.integer :item_id, :null => false
|
||||
t.string :event, :null => false
|
||||
t.string :whodunnit
|
||||
t.text :object
|
||||
t.datetime :created_at
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
ActiveRecord::Schema.define do
|
||||
remove_column :widgets, :sacrificial_column
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'active_support/test_case'
|
||||
require 'shoulda'
|
||||
|
||||
ENV['RAILS_ENV'] = 'test'
|
||||
ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
|
||||
|
||||
require 'test/unit'
|
||||
require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
|
||||
|
||||
def connect_to_database
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
|
||||
db_adapter = ENV['DB']
|
||||
|
||||
# no db passed, try one of these fine config-free DBs before bombing.
|
||||
db_adapter ||=
|
||||
begin
|
||||
require 'rubygems'
|
||||
require 'sqlite'
|
||||
'sqlite'
|
||||
rescue MissingSourceFile
|
||||
begin
|
||||
require 'sqlite3'
|
||||
'sqlite3'
|
||||
rescue MissingSourceFile
|
||||
end
|
||||
end
|
||||
|
||||
if db_adapter.nil?
|
||||
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
|
||||
end
|
||||
|
||||
ActiveRecord::Base.establish_connection(config[db_adapter])
|
||||
end
|
||||
|
||||
def load_schema
|
||||
connect_to_database
|
||||
load(File.dirname(__FILE__) + "/schema.rb")
|
||||
require File.dirname(__FILE__) + '/../rails/init.rb'
|
||||
end
|
||||
|
||||
def change_schema
|
||||
load(File.dirname(__FILE__) + "/schema_change.rb")
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
# Uninstall hook code here
|
Loading…
Reference in New Issue