auditing 0.0.2 → 1.0.0

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Brad
1
+ Copyright (c) 2010 Brad Cantin
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,10 +1,10 @@
1
- = auditing (currently in development, no gem has been created yet)
1
+ = auditing (currently in development)
2
2
 
3
3
  Auditing is a simple way to track and rollback changes to a record.
4
4
 
5
5
  == Installation
6
6
 
7
- gem install auditing # NOTE: gem has not been created yet.
7
+ gem install auditing
8
8
 
9
9
  You will have to supply a model named Audit in your rails application with the following migration
10
10
 
@@ -37,18 +37,41 @@ TODO: more to follow about tracking the user
37
37
  To add auditing to all attributes on a model, simply add auditing to the model.
38
38
 
39
39
  class School < ActiveRecord::Base
40
- auditing
40
+ audit_enabled
41
41
  end
42
42
 
43
43
  If you want more control over which attributes are audited, you can define them
44
44
  by supplying a fields hash
45
45
 
46
46
  class School < ActiveRecord::Base
47
- auditing :fields => [:name, :established_on]
47
+ audit_enabled :fields => [:name, :established_on]
48
48
  end
49
49
 
50
50
  Auditing will not track "id", "created_at", or "updated_at" by default
51
51
 
52
+ == belongs_to relationships
53
+
54
+ Auditing a belongs_to relationship works by keeping track of the foreign_id attribute. You do
55
+ not need to add anything to the foreign model.
56
+
57
+ == has_many relationships
58
+
59
+ You may have the need to keep track of a has_many relationship. To accomplish this, we have
60
+ to enable a similar type of auditing on the other model(s)
61
+
62
+ class Company < ActiveRecord::Base
63
+ has_many :phone_numbers
64
+ audit_enabled
65
+ end
66
+ class PhoneNumber < ActiveRecord::Base
67
+ belongs_to :company
68
+ audit_relationship_enabled
69
+ end
70
+
71
+ As above, you can supply a :fields hash of which attributes will be logged.
72
+ If your relationship is polymorphic, you can supply an array of classes
73
+ that you :only want to log.
74
+
52
75
  == Note on Patches/Pull Requests
53
76
 
54
77
  * Fork the project.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.description = %Q{acts_as_versioned is good. This allows an attribute level rollback instead}
10
10
  gem.email = "brad.cantin@gmail.com"
11
11
  gem.homepage = "http://github.com/bcantin/auditing"
12
- gem.authors = ["Brad"]
12
+ gem.authors = ["Brad Cantin"]
13
13
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
14
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
15
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 1.0.0
data/auditing.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{auditing}
8
- s.version = "0.0.2"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Brad"]
12
- s.date = %q{2010-08-29}
11
+ s.authors = ["Brad Cantin"]
12
+ s.date = %q{2010-10-09}
13
13
  s.description = %q{acts_as_versioned is good. This allows an attribute level rollback instead}
14
14
  s.email = %q{brad.cantin@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -29,10 +29,12 @@ Gem::Specification.new do |s|
29
29
  "VERSION",
30
30
  "auditing.gemspec",
31
31
  "lib/auditing.rb",
32
- "lib/auditing/auditing.rb",
32
+ "lib/auditing/audit_relationship.rb",
33
33
  "lib/auditing/auditor.rb",
34
- "spec/auditing/auditing_spec.rb",
34
+ "lib/auditing/base.rb",
35
+ "spec/auditing/audit_relationship_spec.rb",
35
36
  "spec/auditing/auditor_spec.rb",
37
+ "spec/auditing/base_spec.rb",
36
38
  "spec/schema.rb",
37
39
  "spec/spec_helper.rb"
38
40
  ]
@@ -42,8 +44,9 @@ Gem::Specification.new do |s|
42
44
  s.rubygems_version = %q{1.3.7}
43
45
  s.summary = %q{A gem to keep track of audit hisory of a record}
44
46
  s.test_files = [
45
- "spec/auditing/auditing_spec.rb",
47
+ "spec/auditing/audit_relationship_spec.rb",
46
48
  "spec/auditing/auditor_spec.rb",
49
+ "spec/auditing/base_spec.rb",
47
50
  "spec/schema.rb",
48
51
  "spec/spec_helper.rb"
49
52
  ]
@@ -0,0 +1,127 @@
1
+ module Auditing
2
+ module AuditRelationship
3
+
4
+ # AuditRelationship creates audits for a has_many relationship.
5
+ #
6
+ # @examples
7
+ # class Company < ActiveRecord::Base
8
+ # has_many :phone_numbers
9
+ # audit_enabled
10
+ # end
11
+ # class PhoneNumber < ActiveRecord::Base
12
+ # belongs_to :company
13
+ # audit_relationship_enabled
14
+ # end
15
+ #
16
+ # valid options include:
17
+ # :only => [(array of models)]
18
+ # an array of models to only send an audit to
19
+ # :except => [(array of models)]
20
+ # an array of models to not send an audit to
21
+ # :fields => [(array of field names)]
22
+ # an array of field names. Each field name will be one audit item
23
+ def audit_relationship_enabled(opts={})
24
+ include InstanceMethods
25
+
26
+ class_inheritable_accessor :audit_enabled_models
27
+ class_inheritable_accessor :field_names
28
+
29
+ self.audit_enabled_models = gather_models(opts)
30
+ self.field_names = gather_fields_for_auditing(opts[:fields])
31
+
32
+ after_create :audit_relationship_create
33
+ before_update :audit_relationship_update
34
+ before_destroy :audit_relationship_destroy
35
+ end
36
+
37
+ def gather_fields_for_auditing(fields=nil)
38
+ poly_array = []
39
+ reflect_on_all_associations(:belongs_to).each do |assoc|
40
+ poly_array << assoc.name if assoc.options[:polymorphic]
41
+ end
42
+
43
+ unless fields
44
+ if poly_array.nil?
45
+ return default_columns
46
+ else
47
+ tmp_names = default_columns
48
+ poly_array.each do |poly|
49
+ tmp_names = tmp_names.reject {|column| column.match(/#{poly.to_s}*/)}
50
+ end
51
+ end
52
+ return tmp_names
53
+ else
54
+ fields.is_a?(Array) ? fields.map {|f| f.to_s} : [fields.to_s]
55
+ end
56
+ end
57
+
58
+ def default_columns
59
+ self.column_names - ["id", "created_at", "updated_at"]
60
+ end
61
+
62
+ def gather_models(opts={})
63
+ if opts[:only]
64
+ if opts[:only].is_a?(Array)
65
+ opts[:only].map {|c| c.to_s.underscore.to_sym}
66
+ else
67
+ [opts[:only].to_s.underscore.to_sym]
68
+ end
69
+ else
70
+ array,tmp = [], []
71
+ reflect_on_all_associations(:belongs_to).each do |assoc|
72
+ array << assoc.name
73
+ end
74
+
75
+ if opts[:except]
76
+ if opts[:except].is_a?(Array)
77
+ tmp = opts[:except].map {|c| c.to_s.underscore.to_sym}
78
+ else
79
+ tmp = [opts[:except].to_s.underscore.to_sym]
80
+ end
81
+ end
82
+ array - tmp
83
+ end
84
+ end
85
+
86
+ module InstanceMethods
87
+
88
+ def audit_relationship_create
89
+ audit_enabled_models.each do |model|
90
+ return unless send(model).respond_to?('log_association_create')
91
+ field_names.each do |field|
92
+ field_value = send(field)
93
+ next unless field_value
94
+ model_instance = send(model)
95
+ model_instance.log_association_create(self, {:field => field,
96
+ :value => field_value})
97
+ end
98
+ end
99
+ end
100
+
101
+ def audit_relationship_update
102
+ if changed?
103
+ audit_enabled_models.each do |model|
104
+ return unless send(model).respond_to?('log_association_update')
105
+ changes.each.select {|k,v| field_names.include?(k)}.each do |field, change|
106
+ field_value = send(field)
107
+ model_instance = send(model)
108
+ model_instance.log_association_update(self, {:field => field,
109
+ :old_value => change[0],
110
+ :new_value => change[1]})
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def audit_relationship_destroy
117
+ audit_enabled_models.each do |model|
118
+ return unless send(model).respond_to?('log_association_destroy')
119
+ model_instance = send(model)
120
+ model_instance.log_association_destroy(self)
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+ end
@@ -2,21 +2,28 @@ module Auditing
2
2
  module Auditor
3
3
 
4
4
  def reversable?
5
- %w[updated].include?(action)
5
+ undoable?
6
6
  end
7
7
 
8
8
  def show_action
9
9
  action
10
10
  end
11
11
 
12
+ def old_value
13
+ Marshal.load(read_attribute(:old_value))
14
+ end
15
+
16
+ def new_value
17
+ Marshal.load(read_attribute(:new_value))
18
+ end
19
+
12
20
  def rollback
13
21
  return unless reversable?
14
22
 
15
23
  if association.blank?
16
24
  auditable.update_attribute(field_name.to_sym, old_value)
17
25
  else
18
- # TODO
19
- # association.class.find(association_id).update_attribute(field_name.to_sym, old_value)
26
+ association.class.find(association_id).update_attribute(field_name.to_sym, old_value)
20
27
  end
21
28
 
22
29
  end
@@ -0,0 +1,74 @@
1
+ module Auditing
2
+ module Base
3
+
4
+ # Auditing creates audit objects for a record.
5
+ #
6
+ # @examples
7
+ # class School < ActiveRecord::Base
8
+ # audit_enabled
9
+ # end
10
+ # class School < ActiveRecord::Base
11
+ # audit_enabled :fields => [:name, :established_on]
12
+ # end
13
+ def audit_enabled(opts={})
14
+ include InstanceMethods
15
+ class_inheritable_accessor :auditing_fields
16
+
17
+ has_many :audits, :as => :auditable, :order => 'created_at DESC, id DESC'
18
+
19
+ self.auditing_fields = gather_fields_for_auditing(opts[:fields])
20
+
21
+ after_create :log_creation
22
+ after_update :log_update
23
+ end
24
+
25
+ def gather_fields_for_auditing(fields=nil)
26
+ return self.column_names - ["id", "created_at", "updated_at"] unless fields
27
+ fields.is_a?(Array) ? fields.map {|f| f.to_s} : [fields.to_s]
28
+ end
29
+
30
+ module InstanceMethods
31
+ def log_creation
32
+ add_audit(:action => 'created', :undoable => false)
33
+ end
34
+
35
+ def log_update
36
+ if changed?
37
+ changes.each.select {|k,v| auditing_fields.include?(k)}.each do |field, change|
38
+ next if change[0].to_s == change[1].to_s
39
+ add_audit(:action => 'updated',
40
+ :field_name => field,
41
+ :old_value => Marshal.dump(change[0]),
42
+ :new_value => Marshal.dump(change[1]) )
43
+ end
44
+ end
45
+ end
46
+
47
+ def log_association_create(child_object, hash)
48
+ add_audit(:action => 'added',
49
+ :association => child_object,
50
+ :field_name => hash[:field],
51
+ :new_value => Marshal.dump(hash[:value]) )
52
+ end
53
+
54
+ def log_association_update(child_object, hash)
55
+ add_audit(:action => 'updated',
56
+ :association => child_object,
57
+ :field_name => hash[:field],
58
+ :old_value => Marshal.dump(hash[:old_value]),
59
+ :new_value => Marshal.dump(hash[:new_value]) )
60
+ end
61
+
62
+ def log_association_destroy(item)
63
+ add_audit(:action => 'removed', :association => item, :undoable => false)
64
+ # TODO: update all of this items previous audits to be undoable
65
+ end
66
+
67
+ private
68
+ def add_audit(hash={})
69
+ Audit.create!({:auditable => self}.merge(hash))
70
+ end
71
+ end # Auditing::InstanceMethods
72
+
73
+ end
74
+ end
data/lib/auditing.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'auditing/auditing'
1
+ require 'auditing/base'
2
2
  require 'auditing/auditor'
3
+ require 'auditing/audit_relationship'
3
4
 
4
- ActiveRecord::Base.send(:extend, Auditing)
5
+ ActiveRecord::Base.send(:extend, Auditing::Base)
5
6
  ActiveRecord::Base.send(:extend, Auditing::Auditor)
7
+ ActiveRecord::Base.send(:extend, Auditing::AuditRelationship)
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AuditingRelationship" do
4
+
5
+ describe "adding audit_relationship_enabled on a model" do
6
+ before do
7
+ class Company < ActiveRecord::Base
8
+ has_many :phone_numbers, :as => :phoneable
9
+ audit_enabled
10
+ end
11
+ class PhoneNumber < ActiveRecord::Base
12
+ belongs_to :phoneable, :polymorphic => true
13
+ audit_relationship_enabled
14
+ end
15
+ end
16
+
17
+ it "should respond to audit_relationship_enabled when we add audit_relationship_enabled" do
18
+ PhoneNumber.should respond_to(:audit_relationship_enabled)
19
+ end
20
+
21
+ it "should build an array of audit_enabled_models" do
22
+ PhoneNumber.audit_enabled_models.should == [:phoneable]
23
+ end
24
+
25
+ it "should build an array of field_names" do
26
+ PhoneNumber.field_names.should =~ ['number', 'extension']
27
+ end
28
+
29
+ describe "creating a new belongs_to object" do
30
+ before do
31
+ @company = Company.create(:name => 'Apple')
32
+ end
33
+
34
+ it "should add an audit to the parent instance object (company) when we create a phone number" do
35
+ lambda {
36
+ @company.phone_numbers << PhoneNumber.new(:number => '1-800-orange')
37
+ }.should change{@company.audits.count}
38
+ end
39
+
40
+ it "should add an audit for each field" do
41
+ lambda {
42
+ @company.phone_numbers << PhoneNumber.new(:number => '1-800-orange', :extension => '1')
43
+ }.should change{@company.audits.count}.by(2)
44
+ end
45
+
46
+ describe "updating an existing belongs_to object" do
47
+ before do
48
+ @ph = PhoneNumber.new(:number => '1-800-orange', :extension => '1')
49
+ @company.phone_numbers << @ph
50
+ end
51
+
52
+ it "should add an audit to the parent instance when we update a phone number" do
53
+ lambda {@ph.update_attributes(:number => '1-800-apple')}.should change{@company.audits.count}
54
+ end
55
+
56
+ describe "deleting a child_object" do
57
+ it "should add an audit to the parent instance when we delete a phone number" do
58
+ lambda {PhoneNumber.destroy(@ph)}.should change{@company.audits.count}
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,22 +1,21 @@
1
1
  require 'spec_helper'
2
- # require 'timecop'
3
2
 
4
3
  describe "Auditor" do
5
- it 'adds the Auditable::Auditor module to the Audit class' do
4
+ it 'adds the Auditing::Auditor module to the Audit class' do
6
5
  Audit.new.should respond_to(:show_action)
7
6
  end
8
7
 
9
8
  describe "#rollback" do
10
9
  before do
11
10
  class School < ActiveRecord::Base
12
- auditing
11
+ audit_enabled
13
12
  end
14
13
  @school = School.create(:name => 'PS118')
15
14
  @school.update_attributes(:name => 'PS99')
16
15
  @audit = @school.audits.first
17
16
  end
18
17
 
19
- it "the first audit should be the audit we want to rollback" do
18
+ it "the latest audit should be the audit we want to rollback" do
20
19
  @audit.action.should == 'updated'
21
20
  @audit.new_value.should == 'PS99'
22
21
  @audit.old_value.should == 'PS118'
@@ -33,11 +32,96 @@ describe "Auditor" do
33
32
  lambda { @audit.rollback }.should change { Audit.count }.by(1)
34
33
  end
35
34
 
36
- it "the first audit after a rollback should contain the changed values" do
35
+ it "the latest audit after a rollback should contain the changed values" do
37
36
  @audit.rollback
38
37
  @school.reload
39
38
  @school.audits.first.old_value.should == 'PS99'
40
39
  @school.audits.first.new_value.should == 'PS118'
41
40
  end
42
41
  end
42
+
43
+ describe "#rollback belongs_to attribute" do
44
+ before do
45
+ class Car < ActiveRecord::Base
46
+ audit_enabled
47
+ belongs_to :auto_maker
48
+ end
49
+ class AutoMaker < ActiveRecord::Base
50
+ has_many :cars
51
+ end
52
+ @automaker = AutoMaker.create(:name => 'maker of fast cars')
53
+ @new_automaker = AutoMaker.create(:name => 'maker of safe cars')
54
+
55
+ @car = Car.create(:name => 'fast car', :auto_maker => @automaker)
56
+ @car.update_attributes(:auto_maker => @new_automaker)
57
+ @audit = @car.audits.first
58
+ end
59
+
60
+ it "the latest audit should be the audit we want to rollback" do
61
+ @audit.action.should == 'updated'
62
+ @audit.new_value.should == @new_automaker.id
63
+ @audit.old_value.should == @automaker.id
64
+ @audit.association.should == nil
65
+ @car.auto_maker.should == @new_automaker
66
+ end
67
+
68
+ it "performs the rollback" do
69
+ @audit.rollback
70
+ @car.reload
71
+ @car.auto_maker.should == @automaker
72
+ end
73
+
74
+ it "creates an audit when a rollback is performed" do
75
+ lambda { @audit.rollback }.should change { Audit.count }.by(1)
76
+ end
77
+
78
+ it "the latest audit after a rollback should contain the changed values" do
79
+ @audit.rollback
80
+ @car.reload
81
+ @car.audits.first.old_value.should == @new_automaker.id
82
+ @car.audits.first.new_value.should == @automaker.id
83
+ end
84
+ end
85
+
86
+ describe "rollback has_many attributes" do
87
+ before do
88
+ class Company < ActiveRecord::Base
89
+ has_many :phone_numbers, :as => :phoneable
90
+ audit_enabled
91
+ end
92
+ class PhoneNumber < ActiveRecord::Base
93
+ belongs_to :phoneable, :polymorphic => true
94
+ audit_relationship_enabled
95
+ end
96
+ @company = Company.create(:name => 'Apple')
97
+ @ph = PhoneNumber.new(:number => '1-800-orange')
98
+ @company.phone_numbers << @ph
99
+ @ph.update_attributes(:number => '1-800-call-apple')
100
+ @audit = @company.audits.first
101
+ end
102
+
103
+ it "the latest audit should be the audit we want to rollback" do
104
+ @audit.action.should == 'updated'
105
+ @audit.new_value.should == '1-800-call-apple'
106
+ @audit.old_value.should == '1-800-orange'
107
+ @audit.association.should == @ph
108
+ end
109
+
110
+ it "performs the rollback" do
111
+ @audit.rollback
112
+ @company.reload
113
+ @company.phone_numbers.first.number.should == '1-800-orange'
114
+ end
115
+
116
+ it "creates an audit when a rollback is performed" do
117
+ lambda { @audit.rollback }.should change { Audit.count }.by(1)
118
+ end
119
+
120
+ it "the latest audit after a rollback should contain the changed values" do
121
+ @audit.rollback
122
+ @company.reload
123
+ @company.audits.first.old_value.should == '1-800-call-apple'
124
+ @company.audits.first.new_value.should == '1-800-orange'
125
+ end
126
+ end
43
127
  end
@@ -5,12 +5,12 @@ describe "Auditing" do
5
5
  describe "auditing default values" do
6
6
  before do
7
7
  class School < ActiveRecord::Base
8
- auditing
8
+ audit_enabled
9
9
  end
10
10
  end
11
11
 
12
- it "responds to auditing when auditing is added to an AR object" do
13
- School.should respond_to(:auditing)
12
+ it "responds to audit_enabled when auditing is added to an AR object" do
13
+ School.should respond_to(:audit_enabled)
14
14
  end
15
15
 
16
16
  it "responds to @auditing_fields" do
@@ -23,31 +23,31 @@ describe "Auditing" do
23
23
  end
24
24
  end # auditing default values
25
25
 
26
- describe "auditing :fields => [:foo,:bar]" do
26
+ describe "audit_enabled :fields => [:foo,:bar]" do
27
27
  it "accepts a single valude as a symbol" do
28
28
  class School < ActiveRecord::Base
29
- auditing :fields => :name
29
+ audit_enabled :fields => :name
30
30
  end
31
31
  School.auditing_fields.should == ['name']
32
32
  end
33
33
 
34
34
  it "accepts a single value as a string" do
35
35
  class School < ActiveRecord::Base
36
- auditing :fields => 'name'
36
+ audit_enabled :fields => 'name'
37
37
  end
38
38
  School.auditing_fields.should == ['name']
39
39
  end
40
40
 
41
41
  it "accepts an array of symbols" do
42
42
  class School < ActiveRecord::Base
43
- auditing :fields => [:name, :established_on]
43
+ audit_enabled :fields => [:name, :established_on]
44
44
  end
45
45
  School.auditing_fields.should == ['name', 'established_on']
46
46
  end
47
47
 
48
48
  it "accepts an array of strings" do
49
49
  class School < ActiveRecord::Base
50
- auditing :fields => ['name', 'established_on']
50
+ audit_enabled :fields => ['name', 'established_on']
51
51
  end
52
52
  School.auditing_fields.should == ['name', 'established_on']
53
53
  end
@@ -56,7 +56,7 @@ describe "Auditing" do
56
56
  describe "creating a new instance" do
57
57
  before do
58
58
  class School < ActiveRecord::Base
59
- auditing
59
+ audit_enabled
60
60
  end
61
61
  end
62
62
 
@@ -83,7 +83,7 @@ describe "Auditing" do
83
83
  describe "updating an existing record" do
84
84
  before do
85
85
  class School < ActiveRecord::Base
86
- auditing :fields => 'name'
86
+ audit_enabled :fields => 'name'
87
87
  end
88
88
  @school = School.create(:name => 'PS118')
89
89
  end
@@ -128,4 +128,74 @@ describe "Auditing" do
128
128
  end
129
129
  end # updating an existing record
130
130
 
131
+ describe "belongs_to relationships" do
132
+ before do
133
+ class Car < ActiveRecord::Base
134
+ audit_enabled
135
+ belongs_to :auto_maker
136
+ end
137
+ class AutoMaker < ActiveRecord::Base
138
+ has_many :cars
139
+ end
140
+
141
+ @car = Car.create(:name => 'fast car')
142
+ @automaker = AutoMaker.create(:name => 'maker of fast cars')
143
+ end
144
+
145
+ it "creates an audit when we add a belongs_to relationship" do
146
+ lambda { @car.update_attributes(:auto_maker => @automaker) }.should change { Audit.count }.by(1)
147
+ end
148
+
149
+ describe "our latest audit" do
150
+ before do
151
+ @car.update_attributes(:auto_maker => @automaker)
152
+ end
153
+
154
+ it "the action should be 'updated" do
155
+ @car.audits.first.action.should == 'updated'
156
+ end
157
+
158
+ it "new_value should be an id" do
159
+ @car.audits.first.new_value.should == @automaker.id
160
+ end
161
+
162
+ it "should be reversable" do
163
+ @car.audits.first.reversable?.should == true
164
+ end
165
+ end
166
+
167
+ describe "updating a belongs_to" do
168
+ before do
169
+ @new_automaker = AutoMaker.create(:name => 'maker of safe car')
170
+ @car.update_attributes(:auto_maker => @automaker)
171
+ end
172
+
173
+ it "creates an audit when we change a belongs_to relationship" do
174
+ lambda { @car.update_attributes(:auto_maker => @new_automaker) }.should change { Audit.count }.by(1)
175
+ end
176
+
177
+ describe "our latest audit" do
178
+ before do
179
+ @car.update_attributes(:auto_maker => @new_automaker)
180
+ end
181
+
182
+ it "the action should be 'updated" do
183
+ @car.audits.first.action.should == 'updated'
184
+ end
185
+
186
+ it "new_value should be an id" do
187
+ @car.audits.first.new_value.should == @new_automaker.id
188
+ end
189
+
190
+ it "old_value should be an id" do
191
+ @car.audits.first.old_value.should == @automaker.id
192
+ end
193
+
194
+ it "should be reversable" do
195
+ @car.audits.first.reversable?.should == true
196
+ end
197
+ end
198
+ end
199
+ end # belongs_to relationships
200
+
131
201
  end
data/spec/schema.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  ActiveRecord::Schema.define(:version => 0) do
2
2
 
3
- create_table :audits, :force => true do |t|
3
+ create_table :audits do |t|
4
4
  t.string :action
5
5
  t.string :auditable_type
6
6
  t.integer :auditable_id
@@ -14,11 +14,41 @@ ActiveRecord::Schema.define(:version => 0) do
14
14
  t.timestamps
15
15
  end
16
16
 
17
-
18
- create_table :schools, :force => true do |t|
17
+ create_table :schools do |t|
19
18
  t.string :name
20
19
  t.datetime :established_on
21
20
  t.timestamps
22
21
  end
23
22
 
23
+ create_table :cars do |t|
24
+ t.string :name
25
+ t.integer :auto_maker_id
26
+ t.timestamps
27
+ end
28
+
29
+ create_table :auto_makers do |t|
30
+ t.string :name
31
+ t.timestamps
32
+ end
33
+
34
+ create_table :companies do |t|
35
+ t.string :name
36
+ t.timestamps
37
+ end
38
+
39
+ create_table :people do |t|
40
+ t.string :first_name
41
+ t.string :last_name
42
+ t.timestamps
43
+ end
44
+
45
+ create_table :phone_numbers do |t|
46
+ t.string :number
47
+ t.string :extension
48
+ t.string :phoneable_type
49
+ t.integer :phoneable_id
50
+ t.timestamps
51
+ end
52
+
53
+
24
54
  end
metadata CHANGED
@@ -3,18 +3,18 @@ name: auditing
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
+ - 1
6
7
  - 0
7
8
  - 0
8
- - 2
9
- version: 0.0.2
9
+ version: 1.0.0
10
10
  platform: ruby
11
11
  authors:
12
- - Brad
12
+ - Brad Cantin
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-29 00:00:00 -04:00
17
+ date: 2010-10-09 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -54,10 +54,12 @@ files:
54
54
  - VERSION
55
55
  - auditing.gemspec
56
56
  - lib/auditing.rb
57
- - lib/auditing/auditing.rb
57
+ - lib/auditing/audit_relationship.rb
58
58
  - lib/auditing/auditor.rb
59
- - spec/auditing/auditing_spec.rb
59
+ - lib/auditing/base.rb
60
+ - spec/auditing/audit_relationship_spec.rb
60
61
  - spec/auditing/auditor_spec.rb
62
+ - spec/auditing/base_spec.rb
61
63
  - spec/schema.rb
62
64
  - spec/spec_helper.rb
63
65
  has_rdoc: true
@@ -93,7 +95,8 @@ signing_key:
93
95
  specification_version: 3
94
96
  summary: A gem to keep track of audit hisory of a record
95
97
  test_files:
96
- - spec/auditing/auditing_spec.rb
98
+ - spec/auditing/audit_relationship_spec.rb
97
99
  - spec/auditing/auditor_spec.rb
100
+ - spec/auditing/base_spec.rb
98
101
  - spec/schema.rb
99
102
  - spec/spec_helper.rb
@@ -1,51 +0,0 @@
1
- module Auditing
2
-
3
- # Auditing creates audit objects for a record.
4
- #
5
- # @examples
6
- # class School < ActiveRecord::Base
7
- # auditing
8
- # end
9
- # class School < ActiveRecord::Base
10
- # auditing :fields => [:name, :established_on]
11
- # end
12
- def auditing(opts={})
13
- include InstanceMethods
14
- class_inheritable_accessor :auditing_fields
15
-
16
- has_many :audits, :as => :auditable, :order => 'created_at DESC, id DESC'
17
-
18
- self.auditing_fields = gather_fields_for_auditing(opts[:fields])
19
-
20
- after_create :log_creation
21
- after_update :log_update
22
-
23
- end
24
-
25
- def gather_fields_for_auditing(fields=nil)
26
- return self.column_names - ["id", "created_at", "updated_at"] unless fields
27
- fields.is_a?(Array) ? fields.map {|f| f.to_s} : [fields.to_s]
28
- end
29
-
30
- module InstanceMethods
31
- def log_creation
32
- add_audit(:action => 'created', :undoable => false)
33
- end
34
-
35
- def log_update
36
- if changed?
37
- changes.each.select {|k,v| auditing_fields.include?(k)}.each do |field, change|
38
- next if change[0].to_s == change[1].to_s
39
- add_audit(:action => 'updated', :field_name => field,
40
- :old_value => change[0], :new_value => change[1])
41
- end
42
- end
43
- end
44
-
45
- private
46
- def add_audit(hash={})
47
- Audit.create!({:auditable => self}.merge(hash))
48
- end
49
- end # Auditing::InstanceMethods
50
-
51
- end