simple_audit 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ # Version 0.1.0 (Sept 29, 2010)
2
+ * Rails 3 compatibility
3
+ * minor API changes break backwards compatibility
4
+ * documented code
5
+
6
+ # Version 0.0.3 (June 6, 2010)
7
+ * unit tests
8
+
9
+ # Version 0.0.2 (June 3, 2010)
10
+ * added migration generator
11
+
12
+ # Version 0.0.1 (June 1, 2010)
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 [Gabriel Târnovan]
1
+ Copyright (c) 2010 [Gabriel-Ștefan Târnovan, Mihai-Longin Târnovan]
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/Rakefile CHANGED
@@ -27,24 +27,27 @@ end
27
27
  #
28
28
  begin
29
29
  require 'jeweler'
30
-
30
+ test_files = FileList['test/**/*'].to_a
31
31
  gem_files = FileList[
32
32
  '[a-zA-Z]*',
33
33
  'lib/**/*',
34
+ 'generators/**/*',
34
35
  'rails/**/*',
35
- 'tasks/**/*',
36
- 'test/**/*'
37
- ]
36
+ ].to_a + test_files
38
37
 
39
38
  Jeweler::Tasks.new do |gemspec|
40
39
  gemspec.name = "simple_audit"
41
40
  gemspec.summary = "Simple auditing solution for ActiveRecord models"
42
- #gemspec.description = ""
43
- gemspec.email = "gabriel.tarnovan@cubus.ro"
41
+ gemspec.description = <<-EOD
42
+ Provides a straightforward way for auditing changes on active record models, especially for composite entities.
43
+ Also provides helper methods for easily rendering an audit trail in Ruby on Rails views.
44
+ EOD
45
+ gemspec.email = ["gabriel.tarnovan@cubus.ro", "mihai.tarnovan@cubus.ro"]
44
46
  gemspec.homepage = "http://github.com/gtarnovan/simple_audit"
45
- gemspec.authors = ["Gabriel Tarnovan"]
46
- gemspec.version = "0.0.3"
47
- gemspec.files = gem_files.to_a
47
+ gemspec.authors = ["Gabriel Tarnovan", "Mihai Tarnovan"]
48
+ gemspec.version = "0.1.0"
49
+ gemspec.files = gem_files
50
+ gemspec.test_files = test_files
48
51
 
49
52
  gemspec.rubyforge_project = 'simple_audit'
50
53
  end
data/TODO CHANGED
@@ -1,3 +1,12 @@
1
- * migration generator
2
- * customizable method for getting the current user
3
- * tests
1
+ # Architecture
2
+ * support for ActiveModel
3
+ * ORM agnosticism
4
+
5
+ # Funtionality
6
+ * provide some sort of UUID for audited models that identify these independently from the database provided ID.
7
+ in case of deletion and creation of a new entity with the same UUID, the audit trails of both entities could be bound.
8
+ e.g. for an invoice the UUID could be its number/series combination. when someone deletes an invoice and creates a new one
9
+ with the same UUID, the audit trail of both instances should be bound, such that the deletion event is visible during the audit (i.e. in the view helper)
10
+
11
+ # Minor
12
+ * provide a sample css file for audit trail rendering
@@ -0,0 +1,2 @@
1
+ Description:
2
+ Generates migration for Audit model
@@ -0,0 +1,9 @@
1
+ class SimpleAuditMigrationGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ m.migration_template 'migration.rb', "db/migrate", :migration_file_name => 'simple_audit_migration'
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,22 @@
1
+ class SimpleAuditMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits do |t|
4
+ t.belongs_to :auditable, :polymorphic => true
5
+ t.belongs_to :user, :polymorphic => true
6
+
7
+ t.string :username
8
+ t.string :action
9
+ t.text :change_log
10
+ t.timestamps
11
+
12
+ end
13
+
14
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
15
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
16
+ add_index :audits, :created_at
17
+ end
18
+
19
+ def self.down
20
+ drop_table :audits
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module SimpleAudit
4
+ class MigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ desc "Generates migration for Audit model"
8
+
9
+ def self.orm
10
+ Rails::Generators.options[:rails][:orm]
11
+ end
12
+
13
+ def self.source_root
14
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
15
+ end
16
+
17
+ def self.orm_has_migration?
18
+ [:active_record].include? orm
19
+ end
20
+
21
+ def self.next_migration_number(path)
22
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
23
+ end
24
+
25
+ def create_migration_file
26
+ if self.class.orm_has_migration?
27
+ migration_template 'migration.rb', 'db/migrate/simple_audit_migration'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ class SimpleAuditMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits do |t|
4
+ t.belongs_to :auditable, :polymorphic => true
5
+ t.belongs_to :user, :polymorphic => true
6
+
7
+ t.string :username
8
+ t.string :action
9
+ t.text :change_log
10
+ t.timestamps
11
+
12
+ end
13
+
14
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
15
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
16
+ add_index :audits, :created_at
17
+ end
18
+
19
+ def self.down
20
+ drop_table :audits
21
+ end
22
+ end
@@ -1,48 +1,14 @@
1
- module Cubus
2
- module SimpleAudit
3
-
4
- def self.included(base)
5
- base.send :extend, ClassMethods
6
- end
7
-
8
- module ClassMethods
9
-
10
- # create a new Audit record
11
- # if no user is specified, try to use sentient_user's User.current
1
+ require 'active_record'
2
+ require 'action_view'
12
3
 
13
- def simple_audit(options = {})
14
- class_eval do
15
-
16
- cattr_accessor :username_method
17
- self.username_method = (options[:username_method] || :name).to_s
18
-
19
- has_many :audits, :as => :auditable
20
- after_create {|record| audit(record, :create)}
21
- after_update {|record| audit(record, :update)}
22
-
23
-
24
- end
25
- send :include, InstanceMethods
26
- end
27
-
28
- def audit record, action = :update, user = nil
29
- user ||= User.current if User.respond_to?(:current)
30
- record.audits.create(:user => user,
31
- :username => user.try(self.username_method),
32
- :action => action.to_s,
33
- :changes => record.audit_changes
34
- )
35
- end
36
-
37
- end
38
-
39
- module InstanceMethods
40
- def audit_changes
41
- self.attributes
42
- end
43
- end
44
-
45
- end
4
+ require 'simple_audit/simple_audit'
5
+ require 'simple_audit/audit'
6
+ require 'simple_audit/helper'
46
7
 
8
+ if defined?(ActiveRecord::Base)
9
+ ActiveRecord::Base.send :include, SimpleAudit
47
10
  end
48
11
 
12
+ if defined?(ActionView::Base)
13
+ ActionView::Base.send :include, SimpleAudit::Helper
14
+ end
@@ -0,0 +1,44 @@
1
+ module SimpleAudit #:nodoc:
2
+
3
+ # Changes of the audited models will be stored here.
4
+ class Audit < ActiveRecord::Base
5
+ belongs_to :auditable, :polymorphic => true
6
+ belongs_to :user, :polymorphic => true
7
+ serialize :change_log
8
+
9
+ # Computes the differences of the change logs between two audits.
10
+ #
11
+ # Returns a hash containing arrays of the form
12
+ # {
13
+ # :key_1 => [<value_in_other_audit>, <value_in_this_audit>],
14
+ # :key_2 => [<value_in_other_audit>, <value_in_this_audit>],
15
+ # }
16
+ def delta(other_audit)
17
+
18
+ return self.change_log if other_audit.nil?
19
+
20
+ returning({}) do |d|
21
+
22
+ # first for keys present only in this audit
23
+ (self.change_log.keys - other_audit.change_log.keys).each do |k|
24
+ d[k] = [nil, self.change_log[k]]
25
+ end
26
+
27
+ # .. then for keys present only in other audit
28
+ (other_audit.change_log.keys - self.change_log.keys).each do |k|
29
+ d[k] = [other_audit.change_log[k], nil]
30
+ end
31
+
32
+ # .. finally for keys present in both, but with different values
33
+ self.change_log.keys.each do |k|
34
+ if self.change_log[k] != other_audit.change_log[k]
35
+ d[k] = [other_audit.change_log[k], self.change_log[k]]
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,35 @@
1
+ module SimpleAudit #:nodoc:
2
+ module Helper
3
+
4
+ # Render the change log for the given audited model
5
+
6
+ def render_audits(audited_model)
7
+ return '' unless audited_model.respond_to?(:audits)
8
+ audits = (audited_model.audits || []).dup.sort{|a,b| b.created_at <=> a.created_at}
9
+ res = ''
10
+ audits.each_with_index do |audit, index|
11
+ older_audit = audits[index + 1]
12
+ res += content_tag(:div, :class => 'audit') do
13
+ content_tag(:div, audit.action, :class => "action #{audit.action}") +
14
+ content_tag(:div, audit.username, :class => "user") +
15
+ content_tag(:div, l(audit.created_at), :class => "timestamp") +
16
+ content_tag(:div, :class => 'changes') do
17
+ if older_audit.present?
18
+ audit.delta(older_audit).collect do |k, v|
19
+ "\n" +
20
+ audited_model.class.human_attribute_name(k) +
21
+ ":" +
22
+ content_tag(:span, v.last, :class => 'current') +
23
+ content_tag(:span, v.first, :class => 'previous')
24
+ end
25
+ else
26
+ audit.change_log.reject{|k, v| v.blank?}.collect {|k, v| "\n#{audited_model.class.human_attribute_name(k)}: #{v}"}
27
+ end
28
+ end
29
+ end
30
+ end
31
+ res
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,73 @@
1
+ # Use this macro if you want changes in your model to be saved in an audit table.
2
+ # The audits table must exist.
3
+ #
4
+ # class Booking
5
+ # simple_audit
6
+ # end
7
+ #
8
+ # See SimpleAudit::ClassMethods#simple_audit for configuration options
9
+
10
+ module SimpleAudit
11
+ def self.included(base) #:nodoc:
12
+ base.send :extend, ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ # == Configuration options
18
+ #
19
+ # * <tt>username_method => symbol</tt> - Call this method on the current user to get the name
20
+ #
21
+ # With no block, all the attributes of the audited model will be logged.
22
+ #
23
+ # class Booking
24
+ # # this is equivalent to passing no block
25
+ # simple_audit do |audited_record|
26
+ # audited_record.attributes
27
+ # end
28
+ # end
29
+ #
30
+ # If a block is given, the data returned by the block will be saved in the audit's change log.
31
+ #
32
+ # class Booking
33
+ # has_many :housing_units
34
+ # simple_audit do |audited_record|
35
+ # {
36
+ # :some_relevant_attribute => audited_record.some_relevant_attribute,
37
+ # :human_readable_serialization_of_aggregated_models => audited_record.housing_units.collect(&:to_s),
38
+ # ...
39
+ # }
40
+ # end
41
+ # end
42
+ #
43
+
44
+ def simple_audit(options = {}, &block)
45
+ class_eval do
46
+
47
+ write_inheritable_attribute :username_method, (options[:username_method] || :name).to_sym
48
+ class_inheritable_reader :username_method
49
+
50
+ audit_changes_proc = block_given? ? block.to_proc : Proc.new {|record| record.attributes}
51
+ write_inheritable_attribute :audit_changes, audit_changes_proc
52
+ class_inheritable_reader :audit_changes
53
+
54
+ has_many :audits, :as => :auditable, :class_name => '::SimpleAudit::Audit'
55
+
56
+ after_create {|record| record.class.audit(record, :create)}
57
+ after_update {|record| record.class.audit(record, :update)}
58
+
59
+ end
60
+ end
61
+
62
+ def audit(record, action = :update, user = nil) #:nodoc:
63
+ user ||= User.current if User.respond_to?(:current)
64
+ record.audits.create(:user => user,
65
+ :username => user.try(self.username_method),
66
+ :action => action.to_s,
67
+ :change_log => self.audit_changes.call(record)
68
+ )
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -1,11 +1,2 @@
1
+ # compatibility with Rails 2
1
2
  require 'simple_audit'
2
-
3
- %w{ models helpers }.each do |dir|
4
- path = File.join(File.dirname(__FILE__), '..', 'lib', 'app', dir)
5
- $LOAD_PATH << path
6
- ActiveSupport::Dependencies.load_paths << path
7
- ActiveSupport::Dependencies.load_once_paths.delete(path)
8
- end
9
-
10
- ActiveRecord::Base.send :include, Cubus::SimpleAudit
11
- ActionController::Base.send :helper, :simple_audit
Binary file
@@ -5,41 +5,54 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{simple_audit}
8
- s.version = "0.0.3"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Gabriel Tarnovan"]
12
- s.date = %q{2010-06-07}
13
- s.email = %q{gabriel.tarnovan@cubus.ro}
11
+ s.authors = ["Gabriel Tarnovan", "Mihai Tarnovan"]
12
+ s.date = %q{2010-09-29}
13
+ s.description = %q{ Provides a straightforward way for auditing changes on active record models, especially for composite entities.
14
+ Also provides helper methods for easily rendering an audit trail in Ruby on Rails views.
15
+ }
16
+ s.email = ["gabriel.tarnovan@cubus.ro", "mihai.tarnovan@cubus.ro"]
14
17
  s.extra_rdoc_files = [
15
18
  "LICENSE",
16
- "README.markdown",
17
19
  "TODO"
18
20
  ]
19
21
  s.files = [
20
- "LICENSE",
21
- "README.markdown",
22
+ "CHANGELOG",
23
+ "LICENSE",
22
24
  "Rakefile",
23
25
  "TODO",
24
- "lib/app/helpers/simple_audit_helper.rb",
25
- "lib/app/models/audit.rb",
26
+ "generators/simple_audit_migration/USAGE",
27
+ "generators/simple_audit_migration/simple_audit_migration_generator.rb",
28
+ "generators/simple_audit_migration/templates/migration.rb",
29
+ "lib/generators/simple_audit/migration/migration_generator.rb",
30
+ "lib/generators/simple_audit/migration/templates/active_record/migration.rb",
26
31
  "lib/simple_audit.rb",
32
+ "lib/simple_audit/audit.rb",
33
+ "lib/simple_audit/helper.rb",
34
+ "lib/simple_audit/simple_audit.rb",
27
35
  "rails/init.rb",
28
36
  "screenshot.png",
29
37
  "simple_audit.gemspec",
30
- "tasks/simple_audit_tasks.rake",
31
- "test/simple_audit_test.rb",
32
- "test/test_helper.rb"
38
+ "test/fixtures/addresses.yml",
39
+ "test/fixtures/people.yml",
40
+ "test/test_helper.rb",
41
+ "test/unit/simple_audit_test.rb"
33
42
  ]
34
43
  s.homepage = %q{http://github.com/gtarnovan/simple_audit}
35
44
  s.rdoc_options = ["--charset=UTF-8"]
36
45
  s.require_paths = ["lib"]
37
46
  s.rubyforge_project = %q{simple_audit}
38
- s.rubygems_version = %q{1.3.5}
47
+ s.rubygems_version = %q{1.3.6}
39
48
  s.summary = %q{Simple auditing solution for ActiveRecord models}
40
49
  s.test_files = [
41
- "test/test_helper.rb",
42
- "test/simple_audit_test.rb"
50
+ "test/fixtures",
51
+ "test/fixtures/addresses.yml",
52
+ "test/fixtures/people.yml",
53
+ "test/test_helper.rb",
54
+ "test/unit",
55
+ "test/unit/simple_audit_test.rb"
43
56
  ]
44
57
 
45
58
  if s.respond_to? :specification_version then
@@ -0,0 +1,6 @@
1
+ address_1:
2
+ line_1: M. Viteazu nr. 11 sc. C ap.32
3
+ person_id: 1
4
+ address_2:
5
+ line_1: Calea Lunga nr. 104 Sibiu 123500
6
+ person_id: 2
@@ -0,0 +1,8 @@
1
+ 1:
2
+ name: Mihai Tarnovan
3
+ email: mihai.tarnovan@cubus.ro
4
+ id: 1
5
+ 2:
6
+ name: Gabriel Tarnovan
7
+ email: gabriel.tarnovan@cubus.ro
8
+ id: 2
@@ -1,3 +1,76 @@
1
1
  require 'rubygems'
2
+ require 'test/unit'
2
3
  require 'active_support'
3
- require 'active_support/test_case'
4
+ require 'active_support/test_case'
5
+ require 'active_record'
6
+ require 'active_record/fixtures'
7
+ require 'action_controller'
8
+ require 'ruby-debug'
9
+ require 'ostruct'
10
+
11
+ require File.join(File.dirname(__FILE__), "..", "generators", "simple_audit_migration", "templates", "migration.rb")
12
+ require File.join(File.dirname(__FILE__), "..", "lib", "simple_audit")
13
+
14
+ ActiveRecord::Base.establish_connection({
15
+ :adapter => 'sqlite3',
16
+ :database => ':memory:'
17
+ })
18
+
19
+ ActiveRecord::Migration.suppress_messages {
20
+ ActiveRecord::Schema.define do
21
+ suppress_messages do
22
+ create_table "people", :force => true do |t|
23
+ t.column "name", :text
24
+ t.column "email", :text
25
+ end
26
+ create_table "addresses", :force => true do |t|
27
+ t.column "line_1", :text
28
+ t.column "zip", :text
29
+ t.column "type", :text
30
+ t.references :person
31
+ end
32
+ create_table "users", :force => true do |t|
33
+ t.column "name", :text
34
+ end
35
+ end
36
+ end
37
+ SimpleAuditMigration.migrate(:up)
38
+ }
39
+
40
+ class Person < ActiveRecord::Base
41
+ has_one :address
42
+ simple_audit do |record|
43
+ {
44
+ :name => record.name,
45
+ :address => { :line_1 => record.address.line_1, :zip => record.address.zip }
46
+ }
47
+ end
48
+ end
49
+
50
+ class Address < ActiveRecord::Base
51
+ belongs_to :person
52
+ simple_audit :username_method => :full_name
53
+ end
54
+
55
+ class HomeAddress < Address
56
+ simple_audit :username_method => :short_name
57
+ end
58
+
59
+ class User < ActiveRecord::Base
60
+ def self.current; User.first ; end
61
+
62
+ def name
63
+ "name"
64
+ end
65
+
66
+ def full_name
67
+ "full_name"
68
+ end
69
+
70
+ def short_name
71
+ "short_name"
72
+ end
73
+
74
+ end
75
+
76
+ User.create(:name => "some user")
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+
3
+ class SimpleAuditTest < ActiveSupport::TestCase
4
+
5
+ include SimpleAudit
6
+
7
+ test "should audit entity creation" do
8
+ assert_difference 'Audit.count', 4 do
9
+ assert_difference 'Person.count', 2 do
10
+ Person.create(:name => "Mihai Tarnovan", :email => "mihai.tarnovan@cubus.ro", :address => Address.new(:line_1 => "M. Viteazu nr. 11 sc. C ap.32"))
11
+ Person.create(:name => "Gabriel Tarnovan", :email => "gabriel.tarnovan@cubus.ro", :address => Address.new(:line_1 => "Calea Lunga nr. 104 Sibiu 123500"))
12
+ end
13
+ end
14
+ end
15
+
16
+ test "should set correct action" do
17
+ person = Person.create(:name => "Mihai Tarnovan", :email => "mihai.tarnovan@cubus.ro", :address => Address.new(:line_1 => "M. Viteazu nr. 11 sc. C ap.32"))
18
+ assert_equal Audit.last.action, "create"
19
+ person.name = "Mihai T."
20
+ person.save
21
+ assert_equal person.audits.last.action, "update"
22
+ end
23
+
24
+ test "should audit only given fields" do
25
+ person = Person.create(:name => "Mihai Tarnovan", :email => "mihai.tarnovan@cubus.ro", :address => Address.new(:line_1 => "M. Viteazu nr. 11 sc. C ap.32"))
26
+ create_audit = person.audits.last
27
+ assert_difference 'Audit.count', 1 do
28
+ person.email = "mihai.tarnovan@gmail.com"
29
+ person.save
30
+ end
31
+ update_audit = person.audits.last
32
+ assert_equal update_audit.delta(create_audit), {}
33
+ end
34
+
35
+ test "should audit associated entity changes" do
36
+ person = Person.create(:name => "Mihai Tarnovan", :email => "mihai.tarnovan@cubus.ro", :address => Address.new(:line_1 => "M. Viteazu nr. 11 sc. C ap.32", :zip => "550350"))
37
+ create_audit = person.audits.last
38
+ assert_difference 'person.audits.count', 1 do
39
+ person.address = Address.new(:line_1 => "Bdul. Victoriei nr. 51", :zip => "550150")
40
+ person.name = "Gigi Kent"
41
+ person.email = "gigi@kent.ro"
42
+ person.save
43
+ end
44
+ update_audit = person.audits.last
45
+ assert_equal update_audit.delta(create_audit), {
46
+ :name => ["Mihai Tarnovan", "Gigi Kent"],
47
+ :address => [
48
+ { :line_1 => "M. Viteazu nr. 11 sc. C ap.32", :zip => "550350" },
49
+ { :line_1 => "Bdul. Victoriei nr. 51", :zip => "550150" }
50
+ ]
51
+ }
52
+ end
53
+
54
+ test "should audit all attributes by default" do
55
+ address = Address.create
56
+ assert_equal Audit.last.change_log.keys.sort, Address.column_names.sort
57
+ end
58
+
59
+ test "should use proper username method" do
60
+ address = HomeAddress.create
61
+ assert_equal User.new.short_name, address.audits.last.username
62
+ address = Address.create
63
+ assert_equal User.new.full_name, address.audits.last.username
64
+ end
65
+
66
+ end
metadata CHANGED
@@ -1,42 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Gabriel Tarnovan
13
+ - Mihai Tarnovan
8
14
  autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-06-07 00:00:00 +03:00
18
+ date: 2010-09-29 00:00:00 +03:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
16
- description:
17
- email: gabriel.tarnovan@cubus.ro
22
+ description: " Provides a straightforward way for auditing changes on active record models, especially for composite entities. \n Also provides helper methods for easily rendering an audit trail in Ruby on Rails views.\n"
23
+ email:
24
+ - gabriel.tarnovan@cubus.ro
25
+ - mihai.tarnovan@cubus.ro
18
26
  executables: []
19
27
 
20
28
  extensions: []
21
29
 
22
30
  extra_rdoc_files:
23
31
  - LICENSE
24
- - README.markdown
25
32
  - TODO
26
33
  files:
34
+ - CHANGELOG
27
35
  - LICENSE
28
- - README.markdown
29
36
  - Rakefile
30
37
  - TODO
31
- - lib/app/helpers/simple_audit_helper.rb
32
- - lib/app/models/audit.rb
38
+ - generators/simple_audit_migration/USAGE
39
+ - generators/simple_audit_migration/simple_audit_migration_generator.rb
40
+ - generators/simple_audit_migration/templates/migration.rb
41
+ - lib/generators/simple_audit/migration/migration_generator.rb
42
+ - lib/generators/simple_audit/migration/templates/active_record/migration.rb
33
43
  - lib/simple_audit.rb
44
+ - lib/simple_audit/audit.rb
45
+ - lib/simple_audit/helper.rb
46
+ - lib/simple_audit/simple_audit.rb
34
47
  - rails/init.rb
35
48
  - screenshot.png
36
49
  - simple_audit.gemspec
37
- - tasks/simple_audit_tasks.rake
38
- - test/simple_audit_test.rb
50
+ - test/fixtures/addresses.yml
51
+ - test/fixtures/people.yml
39
52
  - test/test_helper.rb
53
+ - test/unit/simple_audit_test.rb
40
54
  has_rdoc: true
41
55
  homepage: http://github.com/gtarnovan/simple_audit
42
56
  licenses: []
@@ -50,21 +64,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - ">="
52
66
  - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
53
69
  version: "0"
54
- version:
55
70
  required_rubygems_version: !ruby/object:Gem::Requirement
56
71
  requirements:
57
72
  - - ">="
58
73
  - !ruby/object:Gem::Version
74
+ segments:
75
+ - 0
59
76
  version: "0"
60
- version:
61
77
  requirements: []
62
78
 
63
79
  rubyforge_project: simple_audit
64
- rubygems_version: 1.3.5
80
+ rubygems_version: 1.3.6
65
81
  signing_key:
66
82
  specification_version: 3
67
83
  summary: Simple auditing solution for ActiveRecord models
68
84
  test_files:
85
+ - test/fixtures/addresses.yml
86
+ - test/fixtures/people.yml
69
87
  - test/test_helper.rb
70
- - test/simple_audit_test.rb
88
+ - test/unit/simple_audit_test.rb
@@ -1,122 +0,0 @@
1
- # simple_audit
2
-
3
- Simple auditing solution for ActiveRecord models. Provides an easy way of creating audit logs for complex model associations.
4
- Instead of storing audits for all data aggregated by the audited model, you can specify a serializable representation of the model.
5
-
6
- * a helper method is provided to easily display the audit log
7
- * the Audit object provides a #delta method which computs the differences between two audits
8
-
9
- In a nutshell:
10
-
11
- class Booking < ActiveRecord::Base
12
- simple_audit
13
-
14
- # data to be audited
15
- def audit_changes
16
- {
17
- :price => self.price,
18
- :period => self.period,
19
- ...
20
- }
21
- end
22
- end
23
-
24
- # in your view
25
- <%= render_audits(@booking) %>
26
-
27
- # Why ?
28
-
29
- simple_audit is intended as a straightforward auditing solution for complex model associations.
30
- In a normalized data structure (which is usually the case when using SQL), a core business model will aggregate data from several associated entities.
31
- More often than not in such cases, only the core model is of interest from an auditing point of view.
32
-
33
- So instead of auditing every entity sepparately, with simple_audit you decide for each audited model what data needs to be audited.
34
- This data will be serialized on every model update and the available helper methods will render a clear audit trail.
35
-
36
- ![Screenshot of helper result](http://github.com/gtarnovan/simple_audit/raw/master/screenshot.png)s
37
-
38
- # Installation & Configuration
39
-
40
- ## As a gem
41
-
42
- gem install simple_audit
43
-
44
- and require it
45
-
46
- config.gem 'simple_audit'
47
-
48
- ## As a plugin
49
-
50
- ./script/plugin install http://github.com/gtarnovan/simple_audit
51
-
52
- ## Database
53
-
54
- Generate and run migration
55
-
56
- ./script/generate simple_audit_migration create_audits
57
- rake db:migrate
58
-
59
- # Usage
60
-
61
- Audit ActiveRecord models. Somewhere in your (backend) views show the audit logs.
62
-
63
- # in your model
64
- # app/models/booking.rb
65
-
66
- class Booking < ActiveRecord::Base
67
- simple_audit
68
- ...
69
- end
70
-
71
- # in your view
72
- # app/views/bookings/booking.html.erb
73
-
74
- ...
75
- <%= render_audits(@booking) %>
76
- ...
77
-
78
- # Assumptions and limitations
79
-
80
- * Your user model is called User and the current user User.current
81
- See [sentient_user](http://github.com/bokmann/sentient_user) for more information.
82
-
83
- * You have to write your own tests (fow now)
84
-
85
-
86
-
87
- ## Customize auditing
88
-
89
- By default after each save, all model's attributes are saved in the audits table.
90
- You can customize the data which is saved by overriding the audit_changes method. All relevant data for the audited model should be included here.
91
-
92
- # app/models/booking.rb
93
-
94
- class Booking < ActiveRecord::Base
95
- simple_audit
96
-
97
- def audit_changes
98
- {
99
- :state => self.state,
100
- :price => self.price.format,
101
- :period => self.period.to_s,
102
- :housing_units => housing_units.collect(&:name).join('; '),
103
- ...
104
- }
105
- end
106
- ...
107
- end
108
-
109
- You can also customize the attribute of the User model which will be stored in the audit.
110
-
111
- # default is :name
112
- simple_audit :username_method => :email
113
-
114
- ## Rendering audit
115
-
116
- A helper method for displaying a list of audits is provided. It will render a decorated list of the provided audits;
117
- only the differences between revisions will be shown, thus making the audit information easily readable.
118
-
119
- ![Screenshot of helper result](http://github.com/gtarnovan/simple_audit/raw/master/screenshot.png)
120
-
121
-
122
- Copyright (c) 2010 [Gabriel Târnovan, Cubus Arts](http://cubus.ro "Cubus Arts"), released under the MIT license
@@ -1,33 +0,0 @@
1
- module SimpleAuditHelper
2
-
3
- def render_audits(audited_model)
4
- return '' unless audited_model.respond_to?(:audits)
5
- audits = (audited_model.audits || []).dup.sort{|a,b| b.created_at <=> a.created_at}
6
- res = ''
7
- audits.each_with_index do |audit, index|
8
- older_audit = audits[index + 1]
9
- res += content_tag(:div, :class => 'audit') do
10
- content_tag(:div, audit.action, :class => "action #{audit.action}") +
11
- content_tag(:div, audit.username, :class => "user") +
12
- content_tag(:div, l(audit.created_at), :class => "timestamp") +
13
- content_tag(:div, :class => 'changes') do
14
- if older_audit.present?
15
- audit.delta(older_audit).collect do |k, v|
16
- "\n" +
17
- audited_model.class.human_attribute_name(k) +
18
- ":" +
19
- content_tag(:span, v.last, :class => 'current') +
20
- content_tag(:span, v.first, :class => 'previous')
21
- end
22
- else
23
- audit.changes.reject{|k, v| v.blank?}.collect {|k, v| "\n#{audited_model.class.human_attribute_name(k)}: #{v}"}
24
- end
25
- end
26
- end
27
- end
28
- res
29
- end
30
-
31
- end
32
-
33
-
@@ -1,39 +0,0 @@
1
- class Audit < ActiveRecord::Base
2
- belongs_to :auditable, :polymorphic => true
3
- belongs_to :user, :polymorphic => true
4
- serialize :changes
5
-
6
- #
7
- # returns the differences of the to audits
8
- # result is a hash containing arrays of the form [<value_in_other_audit>, <value_in_this_audit>]
9
- # the values in this audit take precedence
10
- #
11
- def delta(other)
12
-
13
- return self.changes if other.nil?
14
-
15
- d = {}
16
-
17
- (self.changes.keys - other.changes.keys).each do |k|
18
- d[k] = [nil, self.changes[k]]
19
- end
20
-
21
- (other.changes.keys - self.changes.keys).each do |k|
22
- d[k] = [other.changes[k], nil]
23
- end
24
-
25
- self.changes.keys.each do |k|
26
- if self.changes[k] != other.changes[k]
27
- d[k] = [other.changes[k], self.changes[k]]
28
- end
29
- end
30
-
31
- d
32
-
33
- end
34
-
35
- def changes
36
- self[:changes]
37
- end
38
-
39
- end
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :simple_audit do
3
- # # Task goes here
4
- # end
@@ -1,8 +0,0 @@
1
- require 'test_helper'
2
-
3
- class SimpleAuditTest < ActiveSupport::TestCase
4
- # Replace this with your real tests.
5
- test "the truth" do
6
- assert true
7
- end
8
- end