audit_record 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +150 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +53 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/audit_record.gemspec +100 -0
- data/lib/audit.rb +1 -0
- data/lib/auditable/audit_record.rb +29 -0
- data/lib/auditable/audit_record_sweeper.rb +7 -0
- data/lib/auditable/auditor.rb +84 -0
- data/lib/generators/auditable/templates/install.rb +15 -0
- data/lib/generators/install_generator.rb +28 -0
- data/test/audit_test.rb +32 -0
- data/test/helper.rb +58 -0
- metadata +325 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.1.1"
|
12
|
+
gem "jeweler", "~> 1.8.3"
|
13
|
+
gem "simplecov", ">= 0"
|
14
|
+
gem "rails"
|
15
|
+
gem 'sqlite3'
|
16
|
+
gem 'ruby-debug19', :platforms => :ruby_19, :require => 'ruby-debug' unless RUBY_VERSION > "1.9.2"
|
17
|
+
gem "spork", "> 0.9.0.rc"
|
18
|
+
gem "guard-spork"
|
19
|
+
gem 'guard'
|
20
|
+
gem 'rb-inotify', :require => false
|
21
|
+
gem 'rb-fsevent', :require => false
|
22
|
+
gem 'rb-fchange', :require => false
|
23
|
+
gem 'guard-test'
|
24
|
+
gem 'guard-bundler'
|
25
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionmailer (3.2.2)
|
5
|
+
actionpack (= 3.2.2)
|
6
|
+
mail (~> 2.4.0)
|
7
|
+
actionpack (3.2.2)
|
8
|
+
activemodel (= 3.2.2)
|
9
|
+
activesupport (= 3.2.2)
|
10
|
+
builder (~> 3.0.0)
|
11
|
+
erubis (~> 2.7.0)
|
12
|
+
journey (~> 1.0.1)
|
13
|
+
rack (~> 1.4.0)
|
14
|
+
rack-cache (~> 1.1)
|
15
|
+
rack-test (~> 0.6.1)
|
16
|
+
sprockets (~> 2.1.2)
|
17
|
+
activemodel (3.2.2)
|
18
|
+
activesupport (= 3.2.2)
|
19
|
+
builder (~> 3.0.0)
|
20
|
+
activerecord (3.2.2)
|
21
|
+
activemodel (= 3.2.2)
|
22
|
+
activesupport (= 3.2.2)
|
23
|
+
arel (~> 3.0.2)
|
24
|
+
tzinfo (~> 0.3.29)
|
25
|
+
activeresource (3.2.2)
|
26
|
+
activemodel (= 3.2.2)
|
27
|
+
activesupport (= 3.2.2)
|
28
|
+
activesupport (3.2.2)
|
29
|
+
i18n (~> 0.6)
|
30
|
+
multi_json (~> 1.0)
|
31
|
+
archive-tar-minitar (0.5.2)
|
32
|
+
arel (3.0.2)
|
33
|
+
builder (3.0.0)
|
34
|
+
columnize (0.3.6)
|
35
|
+
erubis (2.7.0)
|
36
|
+
ffi (1.0.11)
|
37
|
+
git (1.2.5)
|
38
|
+
guard (1.0.1)
|
39
|
+
ffi (>= 0.5.0)
|
40
|
+
thor (~> 0.14.6)
|
41
|
+
guard-bundler (0.1.3)
|
42
|
+
bundler (>= 1.0.0)
|
43
|
+
guard (>= 0.2.2)
|
44
|
+
guard-spork (0.5.2)
|
45
|
+
guard (>= 0.10.0)
|
46
|
+
spork (>= 0.8.4)
|
47
|
+
guard-test (0.4.3)
|
48
|
+
guard (>= 0.4)
|
49
|
+
test-unit (~> 2.2)
|
50
|
+
hike (1.2.1)
|
51
|
+
i18n (0.6.0)
|
52
|
+
jeweler (1.8.3)
|
53
|
+
bundler (~> 1.0)
|
54
|
+
git (>= 1.2.5)
|
55
|
+
rake
|
56
|
+
rdoc
|
57
|
+
journey (1.0.3)
|
58
|
+
json (1.6.6)
|
59
|
+
linecache19 (0.5.12)
|
60
|
+
ruby_core_source (>= 0.1.4)
|
61
|
+
mail (2.4.4)
|
62
|
+
i18n (>= 0.4.0)
|
63
|
+
mime-types (~> 1.16)
|
64
|
+
treetop (~> 1.4.8)
|
65
|
+
mime-types (1.17.2)
|
66
|
+
multi_json (1.2.0)
|
67
|
+
polyglot (0.3.3)
|
68
|
+
rack (1.4.1)
|
69
|
+
rack-cache (1.2)
|
70
|
+
rack (>= 0.4)
|
71
|
+
rack-ssl (1.3.2)
|
72
|
+
rack
|
73
|
+
rack-test (0.6.1)
|
74
|
+
rack (>= 1.0)
|
75
|
+
rails (3.2.2)
|
76
|
+
actionmailer (= 3.2.2)
|
77
|
+
actionpack (= 3.2.2)
|
78
|
+
activerecord (= 3.2.2)
|
79
|
+
activeresource (= 3.2.2)
|
80
|
+
activesupport (= 3.2.2)
|
81
|
+
bundler (~> 1.0)
|
82
|
+
railties (= 3.2.2)
|
83
|
+
railties (3.2.2)
|
84
|
+
actionpack (= 3.2.2)
|
85
|
+
activesupport (= 3.2.2)
|
86
|
+
rack-ssl (~> 1.3.2)
|
87
|
+
rake (>= 0.8.7)
|
88
|
+
rdoc (~> 3.4)
|
89
|
+
thor (~> 0.14.6)
|
90
|
+
rake (0.9.2.2)
|
91
|
+
rb-fchange (0.0.5)
|
92
|
+
ffi
|
93
|
+
rb-fsevent (0.9.0)
|
94
|
+
rb-inotify (0.8.8)
|
95
|
+
ffi (>= 0.5.0)
|
96
|
+
rdoc (3.12)
|
97
|
+
json (~> 1.4)
|
98
|
+
ruby-debug-base19 (0.11.25)
|
99
|
+
columnize (>= 0.3.1)
|
100
|
+
linecache19 (>= 0.5.11)
|
101
|
+
ruby_core_source (>= 0.1.4)
|
102
|
+
ruby-debug19 (0.11.6)
|
103
|
+
columnize (>= 0.3.1)
|
104
|
+
linecache19 (>= 0.5.11)
|
105
|
+
ruby-debug-base19 (>= 0.11.19)
|
106
|
+
ruby_core_source (0.1.5)
|
107
|
+
archive-tar-minitar (>= 0.5.2)
|
108
|
+
shoulda (3.0.1)
|
109
|
+
shoulda-context (~> 1.0.0)
|
110
|
+
shoulda-matchers (~> 1.0.0)
|
111
|
+
shoulda-context (1.0.0)
|
112
|
+
shoulda-matchers (1.0.0)
|
113
|
+
simplecov (0.6.1)
|
114
|
+
multi_json (~> 1.0)
|
115
|
+
simplecov-html (~> 0.5.3)
|
116
|
+
simplecov-html (0.5.3)
|
117
|
+
spork (1.0.0rc2)
|
118
|
+
sprockets (2.1.2)
|
119
|
+
hike (~> 1.2)
|
120
|
+
rack (~> 1.0)
|
121
|
+
tilt (~> 1.1, != 1.3.0)
|
122
|
+
sqlite3 (1.3.5)
|
123
|
+
test-unit (2.4.8)
|
124
|
+
thor (0.14.6)
|
125
|
+
tilt (1.3.3)
|
126
|
+
treetop (1.4.10)
|
127
|
+
polyglot
|
128
|
+
polyglot (>= 0.3.1)
|
129
|
+
tzinfo (0.3.32)
|
130
|
+
|
131
|
+
PLATFORMS
|
132
|
+
ruby
|
133
|
+
|
134
|
+
DEPENDENCIES
|
135
|
+
bundler (~> 1.1.1)
|
136
|
+
guard
|
137
|
+
guard-bundler
|
138
|
+
guard-spork
|
139
|
+
guard-test
|
140
|
+
jeweler (~> 1.8.3)
|
141
|
+
rails
|
142
|
+
rb-fchange
|
143
|
+
rb-fsevent
|
144
|
+
rb-inotify
|
145
|
+
rdoc (~> 3.12)
|
146
|
+
ruby-debug19
|
147
|
+
shoulda
|
148
|
+
simplecov
|
149
|
+
spork (> 0.9.0.rc)
|
150
|
+
sqlite3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Mark Daggett
|
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.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= Audit Record
|
2
|
+
|
3
|
+
A simple gem built for Rails 3+ which creates an audit record when users trigger events you flag. The events you can trigger audit on are:
|
4
|
+
|
5
|
+
1. When attributes changing in a model
|
6
|
+
2. When specific methods of a class are executed
|
7
|
+
3. When you trigger an AuditRecord through code as part of a manual audit process
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
In Gemfile:
|
12
|
+
|
13
|
+
gem "audit_record"
|
14
|
+
|
15
|
+
In your application root, run:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Generate the migration:
|
20
|
+
|
21
|
+
If installing without a previous version of acts_as_audited or you do not mind overwriting your audits table:
|
22
|
+
$ rails g audit:install
|
23
|
+
|
24
|
+
After running one of the generators:
|
25
|
+
$ rake db:migrate
|
26
|
+
|
27
|
+
== Usage
|
28
|
+
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
audit :attributes => [:name, :is_admin], :methods => [:unlock_account!, :destroy]
|
31
|
+
end
|
32
|
+
|
33
|
+
== Contributing to audit
|
34
|
+
|
35
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
36
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
37
|
+
* Fork the project.
|
38
|
+
* Start a feature/bugfix branch.
|
39
|
+
* Commit and push until you are happy with your contribution.
|
40
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
41
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
42
|
+
|
43
|
+
== Special Thanks
|
44
|
+
Much of Audit was inspired by acts_as_audited, which is a fine gem in its own right.
|
45
|
+
https://github.com/collectiveidea/acts_as_audited
|
46
|
+
|
47
|
+
However, my goal was to strip features down as much as possible and lock audits (where possible) to the model methods and attributes.
|
48
|
+
|
49
|
+
== Copyright
|
50
|
+
|
51
|
+
Copyright (c) 2012 Mark Daggett. See LICENSE.txt for
|
52
|
+
further details.
|
53
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "audit_record"
|
18
|
+
gem.homepage = "http://github.com/heavysixer/audit"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A gem that creates audit records around events you define}
|
21
|
+
gem.description = %Q{A simple gem built for Rails 3+ which creates an audit when events that you configure occur. The events you can audit are:
|
22
|
+
|
23
|
+
1. Attributes changing in a model
|
24
|
+
2. Methods called on a model
|
25
|
+
3. Methods called on some other class like a controller.
|
26
|
+
}
|
27
|
+
gem.email = "mark@humansized.com"
|
28
|
+
gem.authors = ["Mark Daggett"]
|
29
|
+
# dependencies defined in Gemfile
|
30
|
+
end
|
31
|
+
Jeweler::RubygemsDotOrgTasks.new
|
32
|
+
|
33
|
+
require 'rake/testtask'
|
34
|
+
Rake::TestTask.new(:test) do |test|
|
35
|
+
test.libs << 'lib' << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
require 'rdoc/task'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "audit #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "audit_record"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mark Daggett"]
|
12
|
+
s.date = "2012-03-29"
|
13
|
+
s.description = "A simple gem built for Rails 3+ which creates an audit when events that you configure occur. The events you can audit are:\n\n 1. Attributes changing in a model\n 2. Methods called on a model\n 3. Methods called on some other class like a controller.\n"
|
14
|
+
s.email = "mark@humansized.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"audit_record.gemspec",
|
28
|
+
"lib/audit.rb",
|
29
|
+
"lib/auditable/audit_record.rb",
|
30
|
+
"lib/auditable/audit_record_sweeper.rb",
|
31
|
+
"lib/auditable/auditor.rb",
|
32
|
+
"lib/generators/auditable/templates/install.rb",
|
33
|
+
"lib/generators/install_generator.rb",
|
34
|
+
"test/audit_test.rb",
|
35
|
+
"test/helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = "http://github.com/heavysixer/audit"
|
38
|
+
s.licenses = ["MIT"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = "1.8.21"
|
41
|
+
s.summary = "A gem that creates audit records around events you define"
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.1.1"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
51
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<rails>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<ruby-debug19>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<spork>, ["> 0.9.0.rc"])
|
56
|
+
s.add_development_dependency(%q<guard-spork>, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<rb-inotify>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<rb-fchange>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<guard-test>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<guard-bundler>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
65
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.1"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
68
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
69
|
+
s.add_dependency(%q<rails>, [">= 0"])
|
70
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
71
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
72
|
+
s.add_dependency(%q<spork>, ["> 0.9.0.rc"])
|
73
|
+
s.add_dependency(%q<guard-spork>, [">= 0"])
|
74
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
75
|
+
s.add_dependency(%q<rb-inotify>, [">= 0"])
|
76
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
77
|
+
s.add_dependency(%q<rb-fchange>, [">= 0"])
|
78
|
+
s.add_dependency(%q<guard-test>, [">= 0"])
|
79
|
+
s.add_dependency(%q<guard-bundler>, [">= 0"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
83
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
84
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.1"])
|
85
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
86
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
87
|
+
s.add_dependency(%q<rails>, [">= 0"])
|
88
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
89
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
90
|
+
s.add_dependency(%q<spork>, ["> 0.9.0.rc"])
|
91
|
+
s.add_dependency(%q<guard-spork>, [">= 0"])
|
92
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
93
|
+
s.add_dependency(%q<rb-inotify>, [">= 0"])
|
94
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
95
|
+
s.add_dependency(%q<rb-fchange>, [">= 0"])
|
96
|
+
s.add_dependency(%q<guard-test>, [">= 0"])
|
97
|
+
s.add_dependency(%q<guard-bundler>, [">= 0"])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
data/lib/audit.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/auditable/auditor"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: audit_records
|
4
|
+
#
|
5
|
+
# id :integer(4) not null, primary key
|
6
|
+
# user_id :integer(4)
|
7
|
+
# action :string(255)
|
8
|
+
# modifications :text
|
9
|
+
# remote_address :string(255)
|
10
|
+
# auditable_type :string(255)
|
11
|
+
# auditable_id :integer(4)
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
#
|
15
|
+
|
16
|
+
class AuditRecord < ActiveRecord::Base
|
17
|
+
serialize :modifications
|
18
|
+
belongs_to :user
|
19
|
+
class << self
|
20
|
+
def create_for(record)
|
21
|
+
unless record.audited_attribute_changes.empty?
|
22
|
+
create(:modifications => record.audited_attribute_changes, :auditable_type => record.class.to_s, :auditable_id => record.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def create_for_action(record, action)
|
26
|
+
create(:action => "#{record.class.to_s}.#{action} was called", :auditable_type => record.class.to_s, :auditable_id => record.id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/audit_record"
|
2
|
+
module Auditable
|
3
|
+
module Auditor
|
4
|
+
def self.included(base)
|
5
|
+
base.module_eval do
|
6
|
+
attr_accessor :audited_attribute_changes
|
7
|
+
end
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
module ClassMethods
|
11
|
+
def audit(opts = {})
|
12
|
+
options = HashWithIndifferentAccess.new({ :attributes => [], :methods => [] }).merge(opts)
|
13
|
+
add_auditable_actions(options[:methods])
|
14
|
+
add_auditable_attributes(options[:attributes])
|
15
|
+
has_many :audit_records, :as => :auditable
|
16
|
+
|
17
|
+
include Auditor::InstanceMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
def audited_attributes
|
21
|
+
if self.base_class == self
|
22
|
+
Array(@audited_attributes)
|
23
|
+
else
|
24
|
+
self.base_class.audited_attributes if @audited_attributes.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
private
|
28
|
+
def add_auditable_attributes(optional_attributes)
|
29
|
+
@audited_attributes = []
|
30
|
+
[optional_attributes].flatten.each { |attribute| @audited_attributes << attribute.to_sym }
|
31
|
+
unless @audited_attributes.empty?
|
32
|
+
after_save :audit_changes
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_auditable_actions(optional_actions)
|
37
|
+
[optional_actions].flatten.each do |a|
|
38
|
+
ending = ''
|
39
|
+
action = a.to_s
|
40
|
+
if ['!','?'].any?{ |x| action.last == x }
|
41
|
+
ending = action.slice!(/(.)$/)
|
42
|
+
end
|
43
|
+
send :define_method, "#{action}_with_action_audit#{ending}".to_sym do
|
44
|
+
AuditRecord.create_for_action(self, a)
|
45
|
+
self.send("#{action}_without_action_audit#{ending}")
|
46
|
+
end
|
47
|
+
alias_method_chain a, :action_audit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
module InstanceMethods
|
52
|
+
|
53
|
+
private
|
54
|
+
def audit_changes(*args)
|
55
|
+
|
56
|
+
self.audited_attribute_changes = changes.dup
|
57
|
+
|
58
|
+
# Delete keys we don't want to moderate anyway.
|
59
|
+
self.audited_attribute_changes.delete_if{ |k,v| [:id, :updated_at, :created_at].include?(k.to_sym) }
|
60
|
+
|
61
|
+
# Delete changes that were nil but are now blank.
|
62
|
+
# This is useful for when optional fields of new records are saved wtih no content.
|
63
|
+
self.audited_attribute_changes.delete_if{ |k,v| v == [nil,'']}
|
64
|
+
|
65
|
+
# If no attributes are supplied then the entire record is moderated otherwise moderate only the supplied columns.
|
66
|
+
unless self.class.base_class.audited_attributes.empty?
|
67
|
+
self.audited_attribute_changes.delete_if{ |k,v| !self.class.base_class.audited_attributes.include?(k.to_sym) }
|
68
|
+
end
|
69
|
+
AuditRecord.create_for(self)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
75
|
+
ActiveRecord::Base.class_eval { include Auditable::Auditor }
|
76
|
+
end
|
77
|
+
|
78
|
+
if defined?(ActionController) and defined?(ActionController::Base)
|
79
|
+
require "#{File.dirname(__FILE__)}/audit_record_sweeper"
|
80
|
+
ActionController::Base.class_eval do
|
81
|
+
cache_sweeper :audit_record_sweeper
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :audit_records do |t|
|
4
|
+
t.integer :user_id, :allow_nil => false
|
5
|
+
t.string :action
|
6
|
+
t.text :modifications
|
7
|
+
t.string :remote_address
|
8
|
+
t.string :auditable_type
|
9
|
+
t.integer :auditable_id
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
add_index :audit_records, :user_id
|
13
|
+
add_index :audit_records, [:auditable_id, :auditable_type]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
require 'active_record'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
|
6
|
+
module Auditable
|
7
|
+
module Generators
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
12
|
+
|
13
|
+
# Implement the required interface for Rails::Generators::Migration.
|
14
|
+
def self.next_migration_number(dirname) #:nodoc:
|
15
|
+
next_migration_number = current_migration_number(dirname) + 1
|
16
|
+
if ActiveRecord::Base.timestamped_migrations
|
17
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
18
|
+
else
|
19
|
+
"%.3d" % next_migration_number
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def copy_migration
|
24
|
+
migration_template 'install.rb', 'db/migrate/install_audit.rb'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/audit_test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class AuditTest < ActiveSupport::TestCase
|
3
|
+
self.use_transactional_fixtures = false
|
4
|
+
setup do
|
5
|
+
AuditRecord.delete_all
|
6
|
+
User.delete_all
|
7
|
+
end
|
8
|
+
|
9
|
+
should "create an audit record only when an auditable attribute is updated" do
|
10
|
+
assert_no_difference(AuditRecord, :count) do
|
11
|
+
@user = User.create(:name => "foo")
|
12
|
+
end
|
13
|
+
assert_difference(AuditRecord, :count) do
|
14
|
+
@user.update_attribute(:is_admin, true)
|
15
|
+
end
|
16
|
+
@a = AuditRecord.last
|
17
|
+
assert_equal "User", @a.auditable_type
|
18
|
+
assert_equal( [false,true], @a.modifications["is_admin"])
|
19
|
+
end
|
20
|
+
should "create an audit record when observering a specific action" do
|
21
|
+
assert_no_difference(AuditRecord, :count) do
|
22
|
+
@user = User.create(:name => "foo")
|
23
|
+
end
|
24
|
+
assert_difference(AuditRecord, :count) do
|
25
|
+
@user.destroy
|
26
|
+
end
|
27
|
+
@a = AuditRecord.last
|
28
|
+
assert_equal(@user.class.to_s,@a.auditable_type)
|
29
|
+
assert_equal(@user.id, @a.auditable_id)
|
30
|
+
assert_equal("User.destroy was called",@a.action)
|
31
|
+
end
|
32
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'rails'
|
4
|
+
require 'active_record'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rails/test_help'
|
13
|
+
require 'shoulda'
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
17
|
+
require 'audit'
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
20
|
+
|
21
|
+
ActiveRecord::Schema.define(:version => 1) do
|
22
|
+
create_table :users do |t|
|
23
|
+
t.string :name
|
24
|
+
t.boolean :is_admin, :default => false
|
25
|
+
end
|
26
|
+
create_table "audit_records", :force => true do |t|
|
27
|
+
t.integer "user_id"
|
28
|
+
t.string "action"
|
29
|
+
t.text "modifications"
|
30
|
+
t.string "remote_address"
|
31
|
+
t.string "auditable_type"
|
32
|
+
t.integer "auditable_id"
|
33
|
+
t.datetime "created_at", :null => false
|
34
|
+
t.datetime "updated_at", :null => false
|
35
|
+
end
|
36
|
+
|
37
|
+
add_index "audit_records", ["auditable_id", "auditable_type"], :name => "index_audit_records_on_auditable_id_and_auditable_type"
|
38
|
+
add_index "audit_records", ["user_id"], :name => "index_audit_records_on_user_id"
|
39
|
+
end
|
40
|
+
|
41
|
+
class User < ActiveRecord::Base
|
42
|
+
audit :methods => [:destroy], :attributes => [:is_admin]
|
43
|
+
end
|
44
|
+
class ActiveSupport::TestCase
|
45
|
+
fixtures :all
|
46
|
+
self.use_transactional_fixtures = true
|
47
|
+
self.use_instantiated_fixtures = false
|
48
|
+
|
49
|
+
def assert_difference(object, method = nil, difference = 1)
|
50
|
+
initial_value = object.send(method)
|
51
|
+
yield
|
52
|
+
assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_no_difference(object, method, &block)
|
56
|
+
assert_difference object, method, 0, &block
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,325 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: audit_record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Daggett
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: shoulda
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.12'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.12'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.1.1
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.1
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: jeweler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.3
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.8.3
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: simplecov
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rails
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sqlite3
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: ruby-debug19
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: spork
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>'
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.9.0.rc
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>'
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.9.0.rc
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: guard-spork
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: guard
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: rb-inotify
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: rb-fsevent
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ! '>='
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
type: :development
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: rb-fchange
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
none: false
|
226
|
+
requirements:
|
227
|
+
- - ! '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ! '>='
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: guard-test
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
none: false
|
242
|
+
requirements:
|
243
|
+
- - ! '>='
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
246
|
+
type: :development
|
247
|
+
prerelease: false
|
248
|
+
version_requirements: !ruby/object:Gem::Requirement
|
249
|
+
none: false
|
250
|
+
requirements:
|
251
|
+
- - ! '>='
|
252
|
+
- !ruby/object:Gem::Version
|
253
|
+
version: '0'
|
254
|
+
- !ruby/object:Gem::Dependency
|
255
|
+
name: guard-bundler
|
256
|
+
requirement: !ruby/object:Gem::Requirement
|
257
|
+
none: false
|
258
|
+
requirements:
|
259
|
+
- - ! '>='
|
260
|
+
- !ruby/object:Gem::Version
|
261
|
+
version: '0'
|
262
|
+
type: :development
|
263
|
+
prerelease: false
|
264
|
+
version_requirements: !ruby/object:Gem::Requirement
|
265
|
+
none: false
|
266
|
+
requirements:
|
267
|
+
- - ! '>='
|
268
|
+
- !ruby/object:Gem::Version
|
269
|
+
version: '0'
|
270
|
+
description: ! "A simple gem built for Rails 3+ which creates an audit when events
|
271
|
+
that you configure occur. The events you can audit are:\n\n 1. Attributes changing
|
272
|
+
in a model\n 2. Methods called on a model\n 3. Methods called on some other class
|
273
|
+
like a controller.\n"
|
274
|
+
email: mark@humansized.com
|
275
|
+
executables: []
|
276
|
+
extensions: []
|
277
|
+
extra_rdoc_files:
|
278
|
+
- LICENSE.txt
|
279
|
+
- README.rdoc
|
280
|
+
files:
|
281
|
+
- .document
|
282
|
+
- Gemfile
|
283
|
+
- Gemfile.lock
|
284
|
+
- LICENSE.txt
|
285
|
+
- README.rdoc
|
286
|
+
- Rakefile
|
287
|
+
- VERSION
|
288
|
+
- audit_record.gemspec
|
289
|
+
- lib/audit.rb
|
290
|
+
- lib/auditable/audit_record.rb
|
291
|
+
- lib/auditable/audit_record_sweeper.rb
|
292
|
+
- lib/auditable/auditor.rb
|
293
|
+
- lib/generators/auditable/templates/install.rb
|
294
|
+
- lib/generators/install_generator.rb
|
295
|
+
- test/audit_test.rb
|
296
|
+
- test/helper.rb
|
297
|
+
homepage: http://github.com/heavysixer/audit
|
298
|
+
licenses:
|
299
|
+
- MIT
|
300
|
+
post_install_message:
|
301
|
+
rdoc_options: []
|
302
|
+
require_paths:
|
303
|
+
- lib
|
304
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
305
|
+
none: false
|
306
|
+
requirements:
|
307
|
+
- - ! '>='
|
308
|
+
- !ruby/object:Gem::Version
|
309
|
+
version: '0'
|
310
|
+
segments:
|
311
|
+
- 0
|
312
|
+
hash: -1686998917339816790
|
313
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
314
|
+
none: false
|
315
|
+
requirements:
|
316
|
+
- - ! '>='
|
317
|
+
- !ruby/object:Gem::Version
|
318
|
+
version: '0'
|
319
|
+
requirements: []
|
320
|
+
rubyforge_project:
|
321
|
+
rubygems_version: 1.8.21
|
322
|
+
signing_key:
|
323
|
+
specification_version: 3
|
324
|
+
summary: A gem that creates audit records around events you define
|
325
|
+
test_files: []
|