cia 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
@@ -0,0 +1,7 @@
1
+ appraise "rails2" do
2
+ gem 'activerecord', "2.3.14"
3
+ end
4
+
5
+ appraise "rails3" do
6
+ gem 'activerecord', "3.2.3"
7
+ end
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'rspec', '~>2'
6
+ gem 'appraisal'
7
+ gem 'activerecord'
8
+ gem 'sqlite3'
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cia (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (3.2.2)
10
+ activesupport (= 3.2.2)
11
+ builder (~> 3.0.0)
12
+ activerecord (3.2.2)
13
+ activemodel (= 3.2.2)
14
+ activesupport (= 3.2.2)
15
+ arel (~> 3.0.2)
16
+ tzinfo (~> 0.3.29)
17
+ activesupport (3.2.2)
18
+ i18n (~> 0.6)
19
+ multi_json (~> 1.0)
20
+ appraisal (0.4.1)
21
+ bundler
22
+ rake
23
+ arel (3.0.2)
24
+ builder (3.0.0)
25
+ diff-lcs (1.1.3)
26
+ i18n (0.6.0)
27
+ multi_json (1.3.4)
28
+ rake (0.9.2)
29
+ rspec (2.6.0)
30
+ rspec-core (~> 2.6.0)
31
+ rspec-expectations (~> 2.6.0)
32
+ rspec-mocks (~> 2.6.0)
33
+ rspec-core (2.6.4)
34
+ rspec-expectations (2.6.0)
35
+ diff-lcs (~> 1.1.2)
36
+ rspec-mocks (2.6.0)
37
+ sqlite3 (1.3.6)
38
+ tzinfo (0.3.33)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ activerecord
45
+ appraisal
46
+ cia!
47
+ rake
48
+ rspec (~> 2)
49
+ sqlite3
@@ -0,0 +1,27 @@
1
+ require 'appraisal'
2
+ require 'bundler/gem_tasks'
3
+
4
+ task :default do
5
+ sh "bundle exec rake appraisal:install && bundle exec rake appraisal spec"
6
+ end
7
+
8
+ task :spec do
9
+ sh "rspec spec/"
10
+ end
11
+
12
+ # extracted from https://github.com/grosser/project_template
13
+ rule /^version:bump:.*/ do |t|
14
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
15
+ index = ['major', 'minor','patch'].index(t.name.split(':').last)
16
+ file = 'lib/cia/version.rb'
17
+
18
+ version_file = File.read(file)
19
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
20
+ version_parts[index] = version_parts[index].to_i + 1
21
+ version_parts[2] = 0 if index < 2 # remove patch for minor
22
+ version_parts[1] = 0 if index < 1 # remove minor for major
23
+ new_version = version_parts * '.'
24
+ File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
25
+
26
+ sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
27
+ end
@@ -0,0 +1,50 @@
1
+ # Central Intelligent Auditing
2
+
3
+ Audit model events like update/create/delete + attribute changes.<br/>
4
+ - very normalized and queryable through table layout
5
+ ```
6
+ 1 Transaction (actor/ip/time/...)
7
+ -> has many events (updated subject + message)
8
+ -> has many attribute changes (changed password from foo to bar on subject)
9
+ ```
10
+ - actors and subjects are polymorphic
11
+ - events come in different types like `ActiveAuditing::UpdateEvent`
12
+ - transactions wrap multiple events, a nice place to add debugging info like source/action/ip
13
+ - works on ActiveRecord 2 and 3
14
+
15
+ Install
16
+ =======
17
+ gem install active_auditing
18
+ Or
19
+
20
+ rails plugin install git://github.com/grosser/cia.git
21
+
22
+
23
+ Usage
24
+ =====
25
+
26
+ ```Ruby
27
+ class User < ActiveRecord::Base
28
+ audited_attributes :email, :crypted_password
29
+ end
30
+
31
+ class ApplicationController < ActionController::Base
32
+ around_filter :scope_auditing
33
+
34
+ def scope_auditing
35
+ Auditing.audit :actor => current_user, :ip_address => request.remote_ip do
36
+ yield
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ ```
43
+
44
+
45
+ Author
46
+ ======
47
+ [Michael Grosser](http://grosser.it)<br/>
48
+ michael@grosser.it<br/>
49
+ License: MIT<br/>
50
+ [![Build Status](https://secure.travis-ci.org/grosser/cia.png)](http://travis-ci.org/grosser/cia)
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ name = "cia"
3
+ require "#{name}/version"
4
+
5
+ Gem::Specification.new name, CIA::VERSION do |s|
6
+ s.summary = "Audit model events like update/create/delete + attribute changes + grouped them by transaction, in normalized table layout for easy query access."
7
+ s.authors = ["Michael Grosser"]
8
+ s.email = "michael@grosser.it"
9
+ s.homepage = "http://github.com/grosser/#{name}"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.license = 'MIT'
12
+ end
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source :rubygems
4
+
5
+ gem "rake"
6
+ gem "rspec", "~>2"
7
+ gem "appraisal"
8
+ gem "sqlite3"
9
+ gem "activerecord", "2.3.14"
10
+
11
+ gemspec :path=>"../"
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: /Users/mgrosser/code/tools/cia
3
+ specs:
4
+ cia (0.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activerecord (2.3.14)
10
+ activesupport (= 2.3.14)
11
+ activesupport (2.3.14)
12
+ appraisal (0.4.1)
13
+ bundler
14
+ rake
15
+ diff-lcs (1.1.3)
16
+ rake (0.9.2.2)
17
+ rspec (2.10.0)
18
+ rspec-core (~> 2.10.0)
19
+ rspec-expectations (~> 2.10.0)
20
+ rspec-mocks (~> 2.10.0)
21
+ rspec-core (2.10.1)
22
+ rspec-expectations (2.10.0)
23
+ diff-lcs (~> 1.1.3)
24
+ rspec-mocks (2.10.1)
25
+ sqlite3 (1.3.6)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ activerecord (= 2.3.14)
32
+ appraisal
33
+ cia!
34
+ rake
35
+ rspec (~> 2)
36
+ sqlite3
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source :rubygems
4
+
5
+ gem "rake"
6
+ gem "rspec", "~>2"
7
+ gem "appraisal"
8
+ gem "sqlite3"
9
+ gem "activerecord", "3.2.3"
10
+
11
+ gemspec :path=>"../"
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: /Users/mgrosser/code/tools/cia
3
+ specs:
4
+ cia (0.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (3.2.3)
10
+ activesupport (= 3.2.3)
11
+ builder (~> 3.0.0)
12
+ activerecord (3.2.3)
13
+ activemodel (= 3.2.3)
14
+ activesupport (= 3.2.3)
15
+ arel (~> 3.0.2)
16
+ tzinfo (~> 0.3.29)
17
+ activesupport (3.2.3)
18
+ i18n (~> 0.6)
19
+ multi_json (~> 1.0)
20
+ appraisal (0.4.1)
21
+ bundler
22
+ rake
23
+ arel (3.0.2)
24
+ builder (3.0.0)
25
+ diff-lcs (1.1.3)
26
+ i18n (0.6.0)
27
+ multi_json (1.3.5)
28
+ rake (0.9.2.2)
29
+ rspec (2.10.0)
30
+ rspec-core (~> 2.10.0)
31
+ rspec-expectations (~> 2.10.0)
32
+ rspec-mocks (~> 2.10.0)
33
+ rspec-core (2.10.1)
34
+ rspec-expectations (2.10.0)
35
+ diff-lcs (~> 1.1.3)
36
+ rspec-mocks (2.10.1)
37
+ sqlite3 (1.3.6)
38
+ tzinfo (0.3.33)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ activerecord (= 3.2.3)
45
+ appraisal
46
+ cia!
47
+ rake
48
+ rspec (~> 2)
49
+ sqlite3
@@ -0,0 +1,30 @@
1
+ require 'active_record'
2
+ require 'cia/version'
3
+ require 'cia/auditable'
4
+ require 'cia/null_transaction'
5
+ require 'cia/transaction'
6
+ require 'cia/event'
7
+ require 'cia/create_event'
8
+ require 'cia/update_event'
9
+ require 'cia/delete_event'
10
+ require 'cia/attribute_change'
11
+
12
+ module CIA
13
+ def self.audit(options = {})
14
+ Thread.current[:audit_transaction] = Transaction.new(options)
15
+ yield
16
+ ensure
17
+ Thread.current[:audit_transaction] = nil
18
+ end
19
+
20
+ def self.current_transaction
21
+ Thread.current[:audit_transaction] || NullTransaction
22
+ end
23
+
24
+ def self.record_audit(event_type, object)
25
+ CIA.current_transaction.record(event_type, object)
26
+ rescue => e
27
+ Rails.logger.error("Failed to record audit: #{e}\n#{e.backtrace}")
28
+ raise e unless Rails.env.production?
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ module CIA
2
+ class AttributeChange < ActiveRecord::Base
3
+ self.table_name = "audit_attribute_changes"
4
+
5
+ belongs_to :event, :foreign_key => "audit_event_id"
6
+ belongs_to :source, :polymorphic => true
7
+
8
+ validates_presence_of :event, :attribute_name, :source
9
+
10
+ if ActiveRecord::VERSION::MAJOR > 2
11
+ scope :previous, :order => "id desc"
12
+ else
13
+ named_scope :previous, :order => "id desc"
14
+ end
15
+
16
+ delegate :created_at, :to => :event
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module CIA
2
+ module Auditable
3
+ def self.included(base)
4
+ base.class_attribute :audited_attributes
5
+ base.send :extend, ClassMethods
6
+ base.after_create {|record| CIA.record_audit(CIA::CreateEvent, record) }
7
+ base.after_update {|record| CIA.record_audit(CIA::UpdateEvent, record) }
8
+ base.after_destroy {|record| CIA.record_audit(CIA::DeleteEvent, record) }
9
+ end
10
+
11
+ module ClassMethods
12
+ def audit_attribute(*attributes)
13
+ self.audited_attributes = Set.new unless audited_attributes
14
+ self.audited_attributes += attributes.map(&:to_s)
15
+
16
+ has_many :audit_events, :class_name => "CIA::Event", :as => :source
17
+ has_many :audit_attribute_changes, :class_name => "CIA::AttributeChange", :as => :source
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ module CIA
2
+ class CreateEvent < Event
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module CIA
2
+ class DeleteEvent < Event
3
+ end
4
+ end
@@ -0,0 +1,24 @@
1
+ module CIA
2
+ class Event < ActiveRecord::Base
3
+ abstract_class
4
+ self.table_name = "audit_events"
5
+
6
+ belongs_to :source, :polymorphic => true
7
+ belongs_to :transaction, :foreign_key => :audit_transaction_id
8
+ has_many :attribute_changes, :foreign_key => :audit_event_id
9
+
10
+ validates_presence_of :transaction, :source, :type
11
+
12
+ # tested via transaction_test.rb
13
+ def record_attribute_changes!(changes)
14
+ changes.each do |attribute_name, (old_value, new_value)|
15
+ attribute_changes.create!(
16
+ :attribute_name => attribute_name,
17
+ :old_value => old_value,
18
+ :new_value => new_value,
19
+ :source => source
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module CIA
2
+ class NullTransaction
3
+ def self.record(*args)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ module CIA
2
+ class Transaction < ActiveRecord::Base
3
+ self.table_name = "audit_transactions"
4
+
5
+ belongs_to :actor, :polymorphic => true
6
+ has_many :events, :foreign_key => :audit_transaction_id
7
+
8
+ def record(event_type, source)
9
+ changes = source.changes.slice(*source.class.audited_attributes)
10
+ message = source.audit_message if source.respond_to?(:audit_message)
11
+
12
+ return if not message and changes.empty? and event_type == CIA::UpdateEvent
13
+
14
+ event = event_type.create!(
15
+ :source => source,
16
+ :transaction => self,
17
+ :message => message
18
+ )
19
+ event.record_attribute_changes!(changes)
20
+ event
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ module CIA
2
+ class UpdateEvent < Event
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module CIA
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe CIA::AttributeChange do
4
+ it "delegates create_at to event" do
5
+ t = Time.now
6
+ event = CIA::Event.new(:created_at => t)
7
+ change = CIA::AttributeChange.new(:event => event)
8
+ change.created_at.should == event.created_at
9
+ end
10
+
11
+ describe ".previous" do
12
+ it "finds by id desc" do
13
+ a = create_change
14
+ b = create_change
15
+ CIA::AttributeChange.previous.should == [b,a]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe CIA::Event do
4
+ it "has many attribute_changes" do
5
+ change = create_change
6
+ change.event.attribute_changes.should == [change]
7
+ end
8
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe CIA::Transaction do
4
+ it "has many events" do
5
+ event = create_event
6
+ event.transaction.events.should == [event]
7
+ end
8
+
9
+ context "#record" do
10
+ def parse_event_changes(event)
11
+ event.attribute_changes.map { |c| [c.attribute_name, c.old_value, c.new_value] }
12
+ end
13
+
14
+ let(:transaction){ CIA::Transaction.new(:actor => User.create!) }
15
+
16
+ it "records attribute creations" do
17
+ source = Car.create!
18
+ source.wheels = 4
19
+ event = transaction.record(CIA::UpdateEvent, source)
20
+
21
+ parse_event_changes(event).should == [["wheels", nil, "4"]]
22
+ end
23
+
24
+ it "records multiple attributes" do
25
+ source = CarWith3Attributes.create!
26
+ source.wheels = 4
27
+ source.drivers = 2
28
+ source.color = "red"
29
+ event = transaction.record(CIA::UpdateEvent, source)
30
+
31
+ parse_event_changes(event).should =~ [["wheels", nil, "4"], ["drivers", nil, "2"], ["color", nil, "red"]]
32
+ end
33
+
34
+ it "records attribute changes" do
35
+ source = Car.create!(:wheels => 2)
36
+ source.wheels = 4
37
+ event = transaction.record(CIA::UpdateEvent, source)
38
+ parse_event_changes(event).should == [["wheels", "2", "4"]]
39
+ end
40
+
41
+ it "records attribute deletions" do
42
+ source = Car.create!(:wheels => 2)
43
+ source.wheels = nil
44
+ event = transaction.record(CIA::UpdateEvent, source)
45
+ parse_event_changes(event).should == [["wheels", "2", nil]]
46
+ end
47
+
48
+ it "does not record unaudited attribute changes" do
49
+ source = Car.create!
50
+ source.drivers = 2
51
+ event = nil
52
+ expect{
53
+ event = transaction.record(CIA::UpdateEvent, source)
54
+ }.to_not change{ CIA::Event.count }
55
+
56
+ event.should == nil
57
+ end
58
+
59
+ it "records audit_message as message even if there are no changes" do
60
+ source = CarWithAMessage.create!
61
+ source.audit_message = "Foo"
62
+ event = transaction.record(CIA::UpdateEvent, source)
63
+
64
+ event.message.should == "Foo"
65
+ parse_event_changes(event).should == []
66
+ end
67
+
68
+ it "record non-updates even without changes" do
69
+ source = Car.create!
70
+ event = transaction.record(CIA::CreateEvent, source)
71
+
72
+ parse_event_changes(event).should == []
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe CIA do
4
+ it "has a VERSION" do
5
+ CIA::VERSION.should =~ /^[\.\da-z]+$/
6
+ end
7
+
8
+ describe ".audit" do
9
+ it "has no transaction when it starts" do
10
+ CIA.current_transaction.should == CIA::NullTransaction
11
+ end
12
+
13
+ it "starts a new transaction" do
14
+ result = 1
15
+ CIA.audit({}) do
16
+ result = CIA.current_transaction
17
+ end
18
+ result.class.should == CIA::Transaction
19
+ end
20
+
21
+ it "stops the transaction after the block" do
22
+ CIA.audit({}){}
23
+ CIA.current_transaction.should == CIA::NullTransaction
24
+ end
25
+
26
+ it "returns the block content" do
27
+ CIA.audit({}){ 1 }.should == 1
28
+ end
29
+
30
+ it "is threadsafe" do
31
+ Thread.new do
32
+ CIA.audit({}) do
33
+ sleep 0.04
34
+ end
35
+ end
36
+ sleep 0.01
37
+ CIA.current_transaction.should == CIA::NullTransaction
38
+ sleep 0.04 # so next tests dont fail
39
+ end
40
+ end
41
+
42
+ describe ".record_audit" do
43
+ let(:object) { Car.new }
44
+ let(:transaction) { CIA.current_transaction }
45
+
46
+ around do |example|
47
+ CIA.audit :actor => User.create! do
48
+ example.call
49
+ end
50
+ end
51
+
52
+ before do
53
+ Rails.stub(:logger).and_return(mock(:error => ""))
54
+ Rails.env.stub(:production?).and_return(true)
55
+ end
56
+
57
+ it "tracks create" do
58
+ expect{
59
+ object.save!
60
+ }.to change{ CIA::Event.count }.by(+1)
61
+ CIA::Event.last.class.should == CIA::CreateEvent
62
+ end
63
+
64
+ it "tracks delete" do
65
+ object.save!
66
+ expect{
67
+ object.destroy
68
+ }.to change{ CIA::Event.count }.by(+1)
69
+ CIA::Event.last.class.should == CIA::DeleteEvent
70
+ end
71
+
72
+ it "tracks update" do
73
+ object.save!
74
+ expect{
75
+ object.update_attributes(:wheels => 3)
76
+ }.to change{ CIA::Event.count }.by(+1)
77
+ CIA::Event.last.class.should == CIA::UpdateEvent
78
+ end
79
+
80
+ context "exceptions" do
81
+ before do
82
+ Rails.stub(:logger).and_return(mock(:error => ""))
83
+ Rails.env.stub(:production?).and_return(true)
84
+ transaction.stub(:record).and_raise(StandardError.new("foo"))
85
+ end
86
+
87
+ it "logs exceptions raised by the transaction" do
88
+ Rails.logger.should_receive(:error).with { |x| x =~ /Failed to record audit: foo/ }
89
+ object.save! rescue nil
90
+ end
91
+
92
+ it "re-raise exceptions when not in production" do
93
+ Rails.env.stub(:production?).and_return(false)
94
+
95
+ expect {
96
+ object.save!
97
+ }.to raise_error(StandardError)
98
+ end
99
+
100
+ it "does not re-raise exceptions when in production" do
101
+ Rails.env.stub(:production?).and_return(true)
102
+ object.save!
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,89 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'cia'
3
+
4
+ ActiveRecord::Base.establish_connection(
5
+ :adapter => "sqlite3",
6
+ :database => ":memory:"
7
+ )
8
+
9
+ ActiveRecord::Schema.define(:version => 1) do
10
+ create_table :audit_transactions do |t|
11
+ t.integer :actor_id, :null => false
12
+ t.string :actor_type, :null => false
13
+ t.string :ip_address
14
+ t.timestamp :created_at
15
+ end
16
+
17
+ create_table :audit_events do |t|
18
+ t.string :type, :source_type, :null => false
19
+ t.integer :audit_transaction_id, :source_id, :null => false
20
+ t.string :message
21
+ t.timestamp :created_at
22
+ end
23
+
24
+ create_table :audit_attribute_changes do |t|
25
+ t.integer :audit_event_id, :source_id, :null => false
26
+ t.string :attribute_name, :source_type, :null => false
27
+ t.string :old_value, :new_value
28
+ end
29
+
30
+ create_table :cars do |t|
31
+ t.integer :wheels
32
+ t.integer :drivers
33
+ t.string :color
34
+ end
35
+
36
+ create_table :users do |t|
37
+ t.string :email
38
+ end
39
+ end
40
+
41
+ class User < ActiveRecord::Base
42
+ end
43
+
44
+ class Car < ActiveRecord::Base
45
+ include CIA::Auditable
46
+ audit_attribute :wheels
47
+ end
48
+
49
+ class CarWithAMessage < ActiveRecord::Base
50
+ self.table_name = "cars"
51
+ include CIA::Auditable
52
+ audit_attribute :wheels
53
+ attr_accessor :audit_message
54
+ end
55
+
56
+ class CarWith3Attributes < ActiveRecord::Base
57
+ self.table_name = "cars"
58
+ include CIA::Auditable
59
+ audit_attribute :wheels, :color
60
+ audit_attribute :drivers
61
+ end
62
+
63
+ class CarWithoutObservers < ActiveRecord::Base
64
+ self.table_name = "cars"
65
+ end
66
+
67
+ class CarWithoutObservers2 < ActiveRecord::Base
68
+ self.table_name = "cars"
69
+ end
70
+
71
+ def create_event
72
+ transaction = CIA::Transaction.create!(:actor => User.create!)
73
+ CIA::UpdateEvent.create!(:source => Car.create!, :transaction => transaction)
74
+ end
75
+
76
+ def create_change
77
+ event = create_event
78
+ CIA::AttributeChange.create!(:event => event, :source => event.source, :attribute_name => "bar")
79
+ end
80
+
81
+ module Rails
82
+ def self.logger
83
+ raise "NOT STUBBED"
84
+ end
85
+
86
+ def self.env
87
+ @@env ||= "NOT STUBBED"
88
+ end
89
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cia
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Grosser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-21 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: michael@grosser.it
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .travis.yml
21
+ - Appraisals
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - Rakefile
25
+ - Readme.md
26
+ - cia.gemspec
27
+ - gemfiles/rails2.gemfile
28
+ - gemfiles/rails2.gemfile.lock
29
+ - gemfiles/rails3.gemfile
30
+ - gemfiles/rails3.gemfile.lock
31
+ - lib/cia.rb
32
+ - lib/cia/attribute_change.rb
33
+ - lib/cia/auditable.rb
34
+ - lib/cia/create_event.rb
35
+ - lib/cia/delete_event.rb
36
+ - lib/cia/event.rb
37
+ - lib/cia/null_transaction.rb
38
+ - lib/cia/transaction.rb
39
+ - lib/cia/update_event.rb
40
+ - lib/cia/version.rb
41
+ - spec/cia/attribute_change_spec.rb
42
+ - spec/cia/event_spec.rb
43
+ - spec/cia/transaction_spec.rb
44
+ - spec/cia_spec.rb
45
+ - spec/spec_helper.rb
46
+ homepage: http://github.com/grosser/cia
47
+ licenses:
48
+ - MIT
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ segments:
60
+ - 0
61
+ hash: -3376077843642118516
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ segments:
69
+ - 0
70
+ hash: -3376077843642118516
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.24
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Audit model events like update/create/delete + attribute changes + grouped
77
+ them by transaction, in normalized table layout for easy query access.
78
+ test_files: []