audit_log 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
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,3 @@
1
+ = AuditLog
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'AuditLog'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
data/lib/audit_log.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "audit_log/version"
2
+ require "audit_log/mapping"
3
+ require "audit_log/observer"
4
+
5
+ module AuditLog
6
+
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'singleton'
2
+
3
+ module AuditLog
4
+
5
+ class Mapping
6
+ include Singleton
7
+
8
+ attr_reader :audit_mappings
9
+
10
+ def self.prepare(&block)
11
+ AuditLog::Mapping.instance.instance_eval(&block)
12
+ end
13
+
14
+ def initialize
15
+ @audit_mappings = {}
16
+ end
17
+
18
+ def audit(model_name, options = {})
19
+ @audit_mappings[model_name] = {
20
+ ignored_fields: options[:ignore] || [],
21
+ nested_audited_models: []
22
+ }
23
+
24
+ @current = @audit_mappings[model_name]
25
+ self
26
+ end
27
+
28
+ def join(*nested_audited_models)
29
+ @current[:nested_audited_models] = nested_audited_models
30
+ self
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,212 @@
1
+ #require 'active_support/inflector'
2
+ #require 'active_record'
3
+
4
+ class AuditedModelsObserver < ActiveRecord::Observer
5
+
6
+ def self.observed_classes
7
+ AuditLog::Mapping.instance.audit_mappings.keys.collect{|model_as_symbol| model_as_symbol.to_s.camelize.constantize}
8
+ end
9
+
10
+ def before_validation(model)
11
+ Thread.current[:auditing] ||= model
12
+ end
13
+
14
+ def after_create(model)
15
+ if Thread.current[:auditing] == model
16
+ logged_model = LoggedModel.new(
17
+ what: {id: model.id, event: :create},
18
+ model_name: model.class.name,
19
+ model_id: model.id
20
+ )
21
+ logged_model.save
22
+ end
23
+ end
24
+
25
+
26
+ def after_destroy(model)
27
+ if Thread.current[:auditing] == model
28
+ logged_model = LoggedModel.new(
29
+ what: {id: model.id, event: :destroy},
30
+ model_name: model.class.name,
31
+ model_id: model.id
32
+ )
33
+ logged_model.save
34
+ end
35
+ end
36
+
37
+ def after_update(model)
38
+ if Thread.current[:auditing] == model
39
+ changes = Thread.current[:audited_model_changes]
40
+
41
+ if changes
42
+ what = WhatBuilder.new(changes).build
43
+
44
+ logged_model = LoggedModel.new(
45
+ what: what,
46
+ model_name: model.class.name,
47
+ model_id: model.id
48
+ )
49
+ logged_model.save
50
+ end
51
+ end
52
+ end
53
+
54
+ # {
55
+ # model: model,
56
+ # fields_updates: {field_name: {from: "", to: ""}},
57
+ # has_many: {
58
+ # association_name: [
59
+ # {
60
+ # model: model,
61
+ # event: :event_name,
62
+ # fields_updates: {field_name: {from: "", to: ""}}
63
+ # }
64
+ # ]
65
+ # }
66
+ # }
67
+ def before_update(model)
68
+ if Thread.current[:auditing] == model
69
+ if (model.changed? && !(model.changed_attributes.keys.collect{|attr| attr.to_sym}.uniq.sort - ignored_fields(model).uniq.sort).empty?) ||
70
+ has_some_association_changed?(model)
71
+ changes = {model: model, fields_updates: {}, has_many: {}, has_one: {}}
72
+
73
+ # build main model changes
74
+ model.changed_attributes.
75
+ select{|attribute| attribute != "updated_at" && !ignored_fields(model).include?(attribute.to_sym)}.
76
+ each{|attribute, old_value|
77
+ changes[:fields_updates][attribute.to_sym] = {from: old_value, to: model.send(attribute.to_sym)}
78
+ }
79
+
80
+ # build has_many model creations
81
+ association_audit_loggers(model).select{|association| model.send(association).kind_of?(Array)}.each{|method_name|
82
+ changes[:has_many][method_name.to_sym] ||= []
83
+
84
+ model.send(method_name).select{|a| a.new_record?}.each{|nested|
85
+ changes[:has_many][method_name.to_sym] << {model: nested, event: :create}
86
+ }
87
+ }
88
+
89
+ # build has_many model updates
90
+ association_audit_loggers(model).select{|association| model.send(association).kind_of?(Array)}.each{|method_name|
91
+ changes[:has_many][method_name.to_sym] ||= []
92
+
93
+ model.send(method_name).select{|a| a.changed? && !a.new_record?}.each{|nested|
94
+ nested_changes = {model: nested, fields_updates: {}, event: :update}
95
+ changes[:has_many][method_name.to_sym] << nested_changes
96
+
97
+ nested.changed_attributes.
98
+ select{|attribute| attribute != "updated_at"}.
99
+ each{|attribute, old_value|
100
+ nested_changes[:fields_updates][attribute.to_sym] = {from: old_value, to: nested.send(attribute.to_sym)}
101
+ }
102
+ }
103
+ }
104
+
105
+ # build has_many model deletions
106
+ association_audit_loggers(model).select{|association| model.send(association).kind_of?(Array)}.each{|method_name|
107
+ changes[:has_many][method_name.to_sym] ||= []
108
+
109
+ model.send(method_name).select{|a| a.marked_for_destruction?}.each{|nested|
110
+ changes[:has_many][method_name.to_sym] << {model: nested, event: :destroy}
111
+ }
112
+ }
113
+
114
+
115
+ # build has_one model creations
116
+ association_audit_loggers(model).select{|association| !model.send(association).kind_of?(Array) && !model.send(association).nil?}.each{|method_name|
117
+ has_one_model = model.send(method_name)
118
+
119
+ if has_one_model.new_record?
120
+ changes[:has_one][method_name.to_sym] = {model: has_one_model, event: :create}
121
+ elsif has_one_model.changed?
122
+ has_one_changes = {model: has_one_model, event: :update, fields_updates: {}}
123
+ has_one_model.changed_attributes.
124
+ select{|attribute| attribute != "updated_at"}.
125
+ each{|attribute, old_value|
126
+ has_one_changes[:fields_updates][attribute.to_sym] = {from: old_value, to: has_one_model.send(attribute.to_sym)}
127
+ }
128
+
129
+ changes[:has_one][method_name.to_sym] = has_one_changes
130
+ elsif has_one_model.marked_for_destruction?
131
+ changes[:has_one][method_name.to_sym] = {model: has_one_model, event: :destroy}
132
+ end
133
+ }
134
+
135
+ Thread.current[:audited_model_changes] = changes
136
+ end
137
+ end
138
+ end
139
+
140
+ private
141
+
142
+ def has_some_association_changed?(model)
143
+ changed = false
144
+
145
+ association_audit_loggers(model).each{|method_name|
146
+ association = model.send(method_name)
147
+ changed = true if association_has_changed?(association)
148
+ }
149
+
150
+ changed
151
+ end
152
+
153
+ def association_has_changed?(association)
154
+ # has_many
155
+ if association.kind_of?(Array)
156
+ !association.select{|logger| logger.changed? && !logger.new_record?}.empty? ||
157
+ !association.select{|logger| logger.new_record?}.empty? ||
158
+ !association.select{|item| item.marked_for_destruction?}.empty?
159
+
160
+ # has_one
161
+ else
162
+ association && (association.changed? || association.marked_for_destruction?)
163
+ end
164
+ end
165
+
166
+ def ignored_fields(model)
167
+ AuditLog::Mapping.instance.audit_mappings[model.class.to_s.underscore.to_sym][:ignored_fields]
168
+ end
169
+
170
+ def association_audit_loggers(model)
171
+ AuditLog::Mapping.instance.audit_mappings[model.class.to_s.underscore.to_sym][:nested_audited_models]
172
+ end
173
+
174
+ class WhatBuilder
175
+ attr_accessor :changes
176
+
177
+ def initialize(changes)
178
+ self.changes = changes
179
+ end
180
+
181
+ def build
182
+ what = {id: changes[:model].id, event: :update}
183
+ changes[:fields_updates].each{|field_name, old_and_new_values|
184
+ what[field_name] = old_and_new_values
185
+ }
186
+
187
+ changes[:has_many].each{|association_name, changes_list|
188
+ what[association_name] = [] unless changes_list.empty?
189
+ changes_list.each{|nested_changes|
190
+ what_nested = {id: nested_changes[:model].id, event: nested_changes[:event]}
191
+ nested_changes[:fields_updates].each{|field_name, old_and_new_values|
192
+ what_nested[field_name] = old_and_new_values
193
+ } if nested_changes[:fields_updates]
194
+ what[association_name] << what_nested
195
+ }
196
+ }
197
+
198
+ changes[:has_one].each{|association_name, nested_changes|
199
+ if nested_changes[:model]
200
+ what_nested = {id: nested_changes[:model].id, event: nested_changes[:event]}
201
+ nested_changes[:fields_updates].each{|field_name, old_and_new_values|
202
+ what_nested[field_name] = old_and_new_values
203
+ } if nested_changes[:fields_updates]
204
+ what[association_name] = what_nested
205
+ end
206
+ }
207
+
208
+ what
209
+ end
210
+ end
211
+
212
+ end
@@ -0,0 +1,3 @@
1
+ module AuditLog
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :audit_log do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audit_log
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Connectere
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.6
30
+ - !ruby/object:Gem::Dependency
31
+ name: sqlite3
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
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: '0'
62
+ description:
63
+ email:
64
+ - ti@connectere.agr.br
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - lib/audit_log/version.rb
70
+ - lib/audit_log/observer.rb
71
+ - lib/audit_log/mapping.rb
72
+ - lib/audit_log.rb
73
+ - lib/tasks/audit_log_tasks.rake
74
+ - MIT-LICENSE
75
+ - Rakefile
76
+ - README.rdoc
77
+ homepage: http://www.connectere.agr.br/
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.24
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Logs of model changes, including nested attributesg.
101
+ test_files: []