auditing 0.0.2 → 1.0.0

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