cia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []