chrno_audit 0.2.4
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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +92 -0
- data/MIT-LICENSE +20 -0
- data/README.md +102 -0
- data/Rakefile +1 -0
- data/TODO +2 -0
- data/app/models/chrno_audit/audit_observer.rb +82 -0
- data/app/models/chrno_audit/audit_record.rb +45 -0
- data/chrno_audit-0.4.0.gem +0 -0
- data/chrno_audit.gemspec +20 -0
- data/lib/chrno_audit/action_controller_concern.rb +65 -0
- data/lib/chrno_audit/active_record_concern.rb +79 -0
- data/lib/chrno_audit/version.rb +5 -0
- data/lib/chrno_audit.rb +33 -0
- data/lib/generators/chrno_audit/install_generator.rb +39 -0
- data/lib/generators/chrno_audit/templates/README +18 -0
- data/lib/generators/chrno_audit/templates/initializer.rb +8 -0
- data/lib/generators/chrno_audit/templates/migration.rb +20 -0
- metadata +90 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: e0062dcbf09cede00ed1653833974379aec8b57d
|
|
4
|
+
data.tar.gz: 04895bc6278aec3259ed18d9612ddcb53a9d9e06
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e54826cdb9a5a549873c544f9325cbdb751ae9cfc8bd5b1ffb7cc9215ab6c0c6eab03fae334b5162981706b67067a0b42400ac8058fb8c7a7e8491761d0ba40f
|
|
7
|
+
data.tar.gz: 4a1c88092ac240d6052b201ef3ed7d0c3bfefdf63b08dde907f16e67210627f200e30ee09a5bc8ed0ec664037d2b5ed1a2f29ee0a8b77a6f0df0e190d3c8f764
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
chrno_audit (0.2.4)
|
|
5
|
+
activerecord (>= 3.1)
|
|
6
|
+
rails (>= 3.1)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: http://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
actionmailer (3.2.13)
|
|
12
|
+
actionpack (= 3.2.13)
|
|
13
|
+
mail (~> 2.5.3)
|
|
14
|
+
actionpack (3.2.13)
|
|
15
|
+
activemodel (= 3.2.13)
|
|
16
|
+
activesupport (= 3.2.13)
|
|
17
|
+
builder (~> 3.0.0)
|
|
18
|
+
erubis (~> 2.7.0)
|
|
19
|
+
journey (~> 1.0.4)
|
|
20
|
+
rack (~> 1.4.5)
|
|
21
|
+
rack-cache (~> 1.2)
|
|
22
|
+
rack-test (~> 0.6.1)
|
|
23
|
+
sprockets (~> 2.2.1)
|
|
24
|
+
activemodel (3.2.13)
|
|
25
|
+
activesupport (= 3.2.13)
|
|
26
|
+
builder (~> 3.0.0)
|
|
27
|
+
activerecord (3.2.13)
|
|
28
|
+
activemodel (= 3.2.13)
|
|
29
|
+
activesupport (= 3.2.13)
|
|
30
|
+
arel (~> 3.0.2)
|
|
31
|
+
tzinfo (~> 0.3.29)
|
|
32
|
+
activeresource (3.2.13)
|
|
33
|
+
activemodel (= 3.2.13)
|
|
34
|
+
activesupport (= 3.2.13)
|
|
35
|
+
activesupport (3.2.13)
|
|
36
|
+
i18n (= 0.6.1)
|
|
37
|
+
multi_json (~> 1.0)
|
|
38
|
+
arel (3.0.3)
|
|
39
|
+
builder (3.0.4)
|
|
40
|
+
erubis (2.7.0)
|
|
41
|
+
hike (1.2.3)
|
|
42
|
+
i18n (0.6.1)
|
|
43
|
+
journey (1.0.4)
|
|
44
|
+
json (1.8.1)
|
|
45
|
+
mail (2.5.4)
|
|
46
|
+
mime-types (~> 1.16)
|
|
47
|
+
treetop (~> 1.4.8)
|
|
48
|
+
mime-types (1.25.1)
|
|
49
|
+
multi_json (1.10.1)
|
|
50
|
+
polyglot (0.3.5)
|
|
51
|
+
rack (1.4.5)
|
|
52
|
+
rack-cache (1.2)
|
|
53
|
+
rack (>= 0.4)
|
|
54
|
+
rack-ssl (1.3.4)
|
|
55
|
+
rack
|
|
56
|
+
rack-test (0.6.2)
|
|
57
|
+
rack (>= 1.0)
|
|
58
|
+
rails (3.2.13)
|
|
59
|
+
actionmailer (= 3.2.13)
|
|
60
|
+
actionpack (= 3.2.13)
|
|
61
|
+
activerecord (= 3.2.13)
|
|
62
|
+
activeresource (= 3.2.13)
|
|
63
|
+
activesupport (= 3.2.13)
|
|
64
|
+
bundler (~> 1.0)
|
|
65
|
+
railties (= 3.2.13)
|
|
66
|
+
railties (3.2.13)
|
|
67
|
+
actionpack (= 3.2.13)
|
|
68
|
+
activesupport (= 3.2.13)
|
|
69
|
+
rack-ssl (~> 1.3.2)
|
|
70
|
+
rake (>= 0.8.7)
|
|
71
|
+
rdoc (~> 3.4)
|
|
72
|
+
thor (>= 0.14.6, < 2.0)
|
|
73
|
+
rake (10.4.2)
|
|
74
|
+
rdoc (3.12.2)
|
|
75
|
+
json (~> 1.4)
|
|
76
|
+
sprockets (2.2.3)
|
|
77
|
+
hike (~> 1.2)
|
|
78
|
+
multi_json (~> 1.0)
|
|
79
|
+
rack (~> 1.0)
|
|
80
|
+
tilt (~> 1.1, != 1.3.0)
|
|
81
|
+
thor (0.19.1)
|
|
82
|
+
tilt (1.4.1)
|
|
83
|
+
treetop (1.4.15)
|
|
84
|
+
polyglot
|
|
85
|
+
polyglot (>= 0.3.1)
|
|
86
|
+
tzinfo (0.3.42)
|
|
87
|
+
|
|
88
|
+
PLATFORMS
|
|
89
|
+
ruby
|
|
90
|
+
|
|
91
|
+
DEPENDENCIES
|
|
92
|
+
chrno_audit!
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2011 Denis Diachkov aka chrno
|
|
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.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# chrno_audit
|
|
2
|
+
|
|
3
|
+
Простейшая реализация аудита для ActiveRecord.
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
6
|
+
|
|
7
|
+
Добавьте в Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "chrno_audit"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
и запустите `bundle install`. Затем нужно будет сгенерировать необходимые для работы файлы и создать таблицы в БД:
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
rails g chrno_audit:install
|
|
17
|
+
rake db:migrate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Помните, что необходимо перезапустить приложение, если оно уже запущено.
|
|
21
|
+
|
|
22
|
+
## Пример использования
|
|
23
|
+
|
|
24
|
+
### Модель
|
|
25
|
+
|
|
26
|
+
После установки джема в моделях становится доступен метод `audit( *params )`, подключающий модель к системе аудита. В качестве параметров методу audit можно передавать список полей для аудита (по умолчанию все), например:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class Page < ActiveRecord::Base
|
|
30
|
+
audit :text, :subject
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Можно использовать псевдо-поле :all, если необходим аудит всех полей:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
class Page < ActiveRecord::Base
|
|
38
|
+
audit :all
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Можно указывать список полей, которые необходимо игнорировать при аудите:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
class Page < ActiveRecord::Base
|
|
46
|
+
audit :all, :except => [ :author ]
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Можно указать список действий для аудита (create, update, destroy):
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
class Page < ActiveRecord::Base
|
|
54
|
+
audit :all, :when => [ :create ]
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Контроллер
|
|
59
|
+
|
|
60
|
+
После установки джема в контроллерах становятся доступны следующие методы:
|
|
61
|
+
* `audit_context( proc_or_symbol = nil, &block )`: задаёт контекст аудита;
|
|
62
|
+
* `create_audit_record!( context = {}, initiator = nil )`: создаёт запись в логе для текущего экшена;
|
|
63
|
+
|
|
64
|
+
Cохраняем в контексте текущего пользователя и его IP:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class ApplicationController < ActionController::Base
|
|
68
|
+
audit_context -> {{ ip: request.remote_addr, current_user: current_user }}
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
_NB:_ блок исполняется в контексте контроллера и обязан возврашать хеш!
|
|
73
|
+
|
|
74
|
+
Используем метод для генерации контекста:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
class ApplicationController < ActionController::Base
|
|
78
|
+
audit_context :generate_context
|
|
79
|
+
|
|
80
|
+
def generate_context
|
|
81
|
+
{ ip: request.remote_addr }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class MyController < ApplicationController
|
|
86
|
+
def generate_context
|
|
87
|
+
super.merge { foo: "bar" }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Генерируем запись в аудит-логе:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
...
|
|
96
|
+
def some_action
|
|
97
|
+
create_audit_record!({ foo: "bar" }, current_user )
|
|
98
|
+
end
|
|
99
|
+
...
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
В качестве `auditable_type` будет использовано имя контроллера, а в качестве `action` — имя экшена ("some_action").
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Обзервер записывает изменения моделей в лог.
|
|
5
|
+
#
|
|
6
|
+
class ChrnoAudit::AuditObserver < ActiveRecord::Observer
|
|
7
|
+
# По умолчанию ничего не обозреваем
|
|
8
|
+
def self.observed_classes
|
|
9
|
+
[]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Добавляет обзервер к заданной модели.
|
|
14
|
+
# @param [Class] model модель
|
|
15
|
+
#
|
|
16
|
+
def self.attach( model )
|
|
17
|
+
self.instance.send :add_observer!, model
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Создаёт запись о создании сущности.
|
|
21
|
+
def after_create( entity )
|
|
22
|
+
create_audit_record! entity, :create
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Создаёт запись об изменении сущности.
|
|
26
|
+
def after_update( entity )
|
|
27
|
+
create_audit_record! entity, :update
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Создаёт запись об удалении сущности.
|
|
31
|
+
def after_destroy( entity )
|
|
32
|
+
create_audit_record! entity, :destroy
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Создаёт запись в логе.
|
|
39
|
+
#
|
|
40
|
+
# @param [ActiveRecord::Base] entity
|
|
41
|
+
# @param [#to_s] action
|
|
42
|
+
#
|
|
43
|
+
def create_audit_record!( entity, action )
|
|
44
|
+
# Ничего не делаем, если аудит данного действия отключён
|
|
45
|
+
return unless entity.class.auditable_actions.include? action
|
|
46
|
+
|
|
47
|
+
# Получаем изменения и контекст
|
|
48
|
+
changes_to_store = get_changes( entity )
|
|
49
|
+
context = get_context.with_indifferent_access
|
|
50
|
+
|
|
51
|
+
# Ничего не делаем если модель не изменилась
|
|
52
|
+
return if entity.class.auditable_options[ :ignore_empty_diff ] && changes_to_store.empty? && action != :destroy
|
|
53
|
+
|
|
54
|
+
ChrnoAudit::AuditRecord.create do |record|
|
|
55
|
+
record.action = action.to_s
|
|
56
|
+
record.auditable = entity
|
|
57
|
+
record.diff = changes_to_store
|
|
58
|
+
record.initiator = context.delete( :initiator ) || context.delete( :current_user )
|
|
59
|
+
record.context = context
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Формирует массив изменений для заданной записи.
|
|
65
|
+
# @param [ActiveRecord::Base] entity
|
|
66
|
+
# @return [Hash]
|
|
67
|
+
#
|
|
68
|
+
def get_changes( entity )
|
|
69
|
+
entity.changes.select { |field| entity.class.auditable_fields.include? field }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Возвращает текущий контекст для аудита.
|
|
74
|
+
# @return [Hash]
|
|
75
|
+
#
|
|
76
|
+
def get_context
|
|
77
|
+
( Thread.current[ :audit_context ].respond_to?( :call ) ? Thread.current[ :audit_context ].call : {} ).tap do |context|
|
|
78
|
+
# Проверяем, что контекст это хеш
|
|
79
|
+
raise "Invalid audit context: Hash expected, got: #{context.inspect}" if context && !context.is_a?( Hash )
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Сущность "запись лога".
|
|
5
|
+
#
|
|
6
|
+
class ChrnoAudit::AuditRecord < ActiveRecord::Base
|
|
7
|
+
# Имя таблицы с логами
|
|
8
|
+
self.table_name = ChrnoAudit.config.table_name || "audit_log"
|
|
9
|
+
|
|
10
|
+
# Кто изменил?
|
|
11
|
+
belongs_to :initiator, :polymorphic => true
|
|
12
|
+
|
|
13
|
+
# Что изменил?
|
|
14
|
+
belongs_to :auditable, :polymorphic => true
|
|
15
|
+
|
|
16
|
+
# Изменения
|
|
17
|
+
serialize :diff, ChrnoAudit.config.serializer || Object
|
|
18
|
+
|
|
19
|
+
# Контекст
|
|
20
|
+
serialize :context, ChrnoAudit.config.serializer || Object
|
|
21
|
+
|
|
22
|
+
# Возвращает записи для заданного типа сущности.
|
|
23
|
+
scope :for_type, -> *types { where( auditable_type: types.map { |t| t.class.model_name })}
|
|
24
|
+
|
|
25
|
+
# Возвращает записи для заданных моделей.
|
|
26
|
+
scope :for_object, -> *records {
|
|
27
|
+
t = self.arel_table
|
|
28
|
+
|
|
29
|
+
# Логи для заданных записей
|
|
30
|
+
conditions = records.compact.map do |record|
|
|
31
|
+
t.where( t[ :auditable_type ].eq( record.class.model_name.to_s )) \
|
|
32
|
+
.where( t[ :auditable_id ].eq( record.id )) \
|
|
33
|
+
.project( t[ :id ] )
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Джойним результаты с помощью UNION ALL
|
|
37
|
+
query = conditions.inject { |c1, c2| c1.union( :all, c2 ) }
|
|
38
|
+
|
|
39
|
+
# Результирующий запрос
|
|
40
|
+
where( t[ :id ].in query )
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Возвращает записи заданного типа.
|
|
44
|
+
scope :with_action, -> action { where( action: action ) }
|
|
45
|
+
end
|
|
Binary file
|
data/chrno_audit.gemspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
$:.push File.expand_path( "../lib", __FILE__ )
|
|
3
|
+
require "chrno_audit/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "chrno_audit"
|
|
7
|
+
s.version = ChrnoAudit::VERSION
|
|
8
|
+
s.authors = [ "Denis Diachkov" ]
|
|
9
|
+
s.email = [ "d.diachkov@gmail.com" ]
|
|
10
|
+
s.homepage = "https://github.com/ddiachkov/chrno_audit"
|
|
11
|
+
s.summary = "Simple ActiveRecord audit system"
|
|
12
|
+
|
|
13
|
+
s.files = Dir[ "*", "app/**/*", "lib/**/*" ]
|
|
14
|
+
s.require_paths = [ "lib" ]
|
|
15
|
+
|
|
16
|
+
s.required_ruby_version = ">= 1.9.3"
|
|
17
|
+
|
|
18
|
+
s.add_runtime_dependency "rails", ">= 3.1"
|
|
19
|
+
s.add_runtime_dependency "activerecord", ">= 3.1"
|
|
20
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module ChrnoAudit
|
|
3
|
+
##
|
|
4
|
+
# Расширение для ActionController.
|
|
5
|
+
#
|
|
6
|
+
module ActionControllerConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
##
|
|
11
|
+
# Хелпер для установки контекста аудита.
|
|
12
|
+
#
|
|
13
|
+
# @param [Proc, Symbol] proc_or_symbol
|
|
14
|
+
# символ или Proc для генерации контента (также может быть задано в виде блока)
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# audit_context -> {{ ip: request.remote_addr }}
|
|
18
|
+
#
|
|
19
|
+
def audit_context( proc_or_symbol = nil, &block )
|
|
20
|
+
# Получаем Proc для генерации контекста
|
|
21
|
+
context_generator =
|
|
22
|
+
# Если задан блок -- используем его
|
|
23
|
+
if block_given?
|
|
24
|
+
block
|
|
25
|
+
|
|
26
|
+
# Если указан символ, то вызываем соответствующий метод
|
|
27
|
+
elsif proc_or_symbol.is_a? Symbol
|
|
28
|
+
Proc.new { self.send proc_or_symbol }
|
|
29
|
+
|
|
30
|
+
# Если указан Proc -- используем его
|
|
31
|
+
elsif proc_or_symbol.is_a? Proc
|
|
32
|
+
proc_or_symbol
|
|
33
|
+
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError, "Proc or Symbol expected, got: #{proc_or_symbol.inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
before_filter do |controller|
|
|
39
|
+
# NB: согласно документации Ruby в Thread.current хранятся per-fiber атрибуты!
|
|
40
|
+
Thread.current[ :audit_context ] = Proc.new { controller.instance_exec( &context_generator ) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Хелпер для создания записи аудита из контроллера.
|
|
47
|
+
# В качестве типа будет использовано имя текущего контоллера, в качестве действия -- текущий экшен.
|
|
48
|
+
#
|
|
49
|
+
# @param [Hash] context контекст
|
|
50
|
+
# @param [ActiveRecord::Base] initiator инициатор обновления
|
|
51
|
+
# @yield [ChrnoAudit::AuditRecord]
|
|
52
|
+
#
|
|
53
|
+
def create_audit_record!( context = {}, initiator = nil, &block )
|
|
54
|
+
ChrnoAudit::AuditRecord.create! do |record|
|
|
55
|
+
record.auditable_type = self.class.name
|
|
56
|
+
record.action = request.symbolized_path_parameters[ :action ]
|
|
57
|
+
record.initiator = initiator
|
|
58
|
+
record.context = context
|
|
59
|
+
|
|
60
|
+
# Даём возможность переопределить параметры
|
|
61
|
+
block.call( record ) if block
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module ChrnoAudit
|
|
3
|
+
##
|
|
4
|
+
# Расширение для ActiveRecord.
|
|
5
|
+
#
|
|
6
|
+
module ActiveRecordConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
##
|
|
11
|
+
# Макрос добавляет в модель аудит изменений.
|
|
12
|
+
#
|
|
13
|
+
# @param [Symbol] fields
|
|
14
|
+
# список полей для аудита (по умолчанию все поля). Можно использовать
|
|
15
|
+
# псевдо-поле :all если необходим аудит всех полей.
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash] options параметры аудита
|
|
18
|
+
# @option options [Array] :except ([])
|
|
19
|
+
# список полей, которые необходимо игнорировать при аудите (применяется
|
|
20
|
+
# совместно с :all)
|
|
21
|
+
#
|
|
22
|
+
# @option options [Array] :when ([ :create, :update, :destroy ])
|
|
23
|
+
# список действий над моделью (создание, изменение, удаление), при
|
|
24
|
+
# которых необходим аудит
|
|
25
|
+
#
|
|
26
|
+
# @option [true, false] :ignore_empty_diff (true)
|
|
27
|
+
# флаг: не записывать данные в базу, если аудируемые поля не изменились
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# audit :all, :except => :foo
|
|
31
|
+
#
|
|
32
|
+
def audit( *fields )
|
|
33
|
+
# Если таблицы ещё нет, ничего не делаем (полезно для миграций)
|
|
34
|
+
unless table_exists?
|
|
35
|
+
Rails.logger.warn "Audit: try to audit model [#{name}] with non-existent table"
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Добавляем связь
|
|
40
|
+
has_many :audit_records, :as => :auditable, :class_name => "ChrnoAudit::AuditRecord"
|
|
41
|
+
|
|
42
|
+
# Добавляем обсервер
|
|
43
|
+
ChrnoAudit::AuditObserver.attach( self )
|
|
44
|
+
|
|
45
|
+
# Добавляем необходимые параметры
|
|
46
|
+
cattr_accessor :auditable_fields, :auditable_actions, :auditable_options
|
|
47
|
+
|
|
48
|
+
options = fields.extract_options!
|
|
49
|
+
|
|
50
|
+
# Настройки по умолчанию
|
|
51
|
+
options.reverse_merge! \
|
|
52
|
+
:except => [],
|
|
53
|
+
:when => [ :create, :update, :destroy ],
|
|
54
|
+
:ignore_empty_diff => true
|
|
55
|
+
|
|
56
|
+
# Нормализуем параметры
|
|
57
|
+
options[ :except ] = Array.wrap( options[ :except ] ).map( &:to_s )
|
|
58
|
+
options[ :when ] = Array.wrap( options[ :when ] ).map( &:to_sym )
|
|
59
|
+
|
|
60
|
+
# Получаем список полей.
|
|
61
|
+
self.auditable_fields =
|
|
62
|
+
# Аудит на всех полях?
|
|
63
|
+
if fields.count == 1 and fields.first == :all
|
|
64
|
+
# Всегда выкидываем timestamp и id.
|
|
65
|
+
column_names - %W{ id created_at updated_at } - options[ :except ]
|
|
66
|
+
else
|
|
67
|
+
( fields - options.delete( :except )).map( &:to_s )
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
self.auditable_actions = options.delete( :when )
|
|
71
|
+
self.auditable_options = options
|
|
72
|
+
|
|
73
|
+
# Проверки
|
|
74
|
+
Rails.logger.warn "Audit: no fields to audit" if self.auditable_fields.empty?
|
|
75
|
+
Rails.logger.warn "Audit: no actions to audit" if self.auditable_actions.empty?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/chrno_audit.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
if Rails.version.to_i >= 4
|
|
3
|
+
# В четвёртых рельсах обзерверы вынесены в джем
|
|
4
|
+
gem "rails-observers", "~> 0.1.1"
|
|
5
|
+
require "rails-observers"
|
|
6
|
+
|
|
7
|
+
# Хак для eager load: по умолчанию класс ActiveRecord::Observer грузится ПОСЛЕ
|
|
8
|
+
# того грузятся модели из app/models при eager_load = true, что приводит к ошибке
|
|
9
|
+
# если рядом с моделями лежат обзервере. Поэтому грузим определение класса сразу.
|
|
10
|
+
require "rails/observers/activerecord/active_record"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require "active_support/configurable"
|
|
14
|
+
require "chrno_audit/version"
|
|
15
|
+
|
|
16
|
+
module ChrnoAudit
|
|
17
|
+
include ActiveSupport::Configurable
|
|
18
|
+
|
|
19
|
+
class Engine < Rails::Engine
|
|
20
|
+
initializer "chrno_audit.initialize", :before => :load_active_support do
|
|
21
|
+
require "chrno_audit/action_controller_concern"
|
|
22
|
+
require "chrno_audit/active_record_concern"
|
|
23
|
+
|
|
24
|
+
ActiveSupport.on_load( :action_controller ) do
|
|
25
|
+
include ChrnoAudit::ActionControllerConcern
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ActiveSupport.on_load( :active_record ) do
|
|
29
|
+
include ChrnoAudit::ActiveRecordConcern
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "rails/generators"
|
|
3
|
+
require "rails/generators/migration"
|
|
4
|
+
require "active_record"
|
|
5
|
+
|
|
6
|
+
# Генератор создаёт миграцию и файл инициализации
|
|
7
|
+
module ChrnoAudit
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path( "../templates", __FILE__ )
|
|
10
|
+
desc "installs migration and initializer"
|
|
11
|
+
|
|
12
|
+
def make_migration
|
|
13
|
+
migration_template "migration.rb", "db/migrate/create_audit_log"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def make_initializer
|
|
17
|
+
copy_file "initializer.rb", "config/initializers/chrno_audit.rb"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def show_readme
|
|
21
|
+
readme "README"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
include Rails::Generators::Migration
|
|
27
|
+
|
|
28
|
+
# Код взят из генератора моделей
|
|
29
|
+
def self.next_migration_number(dirname)
|
|
30
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
31
|
+
|
|
32
|
+
if ActiveRecord::Base.timestamped_migrations
|
|
33
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
|
34
|
+
else
|
|
35
|
+
"%.3d" % next_migration_number
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
============================================================================
|
|
3
|
+
|
|
4
|
+
Ура! Всё готово! Осталось только добавить немного кода.
|
|
5
|
+
|
|
6
|
+
Вот пример кода, который может использоваться в контроллере:
|
|
7
|
+
|
|
8
|
+
def history$
|
|
9
|
+
@page = Page.find params[:id]
|
|
10
|
+
@history = ChrnoAudit::AuditRecord.for_object( @page ).order( "created_at DESC" )
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Чтобы подключить модель к системе аудита эту строку необходимо добавить в файл модели:
|
|
14
|
+
|
|
15
|
+
audit :all
|
|
16
|
+
|
|
17
|
+
Have fun!
|
|
18
|
+
https://github.com/Undev/chrno_audit/tree/docs#Пример-использования
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
class CreateAuditLog < ActiveRecord::Migration
|
|
3
|
+
def change
|
|
4
|
+
create_table :audit_log do |t|
|
|
5
|
+
t.string :auditable_type, :null => false
|
|
6
|
+
t.integer :auditable_id
|
|
7
|
+
t.string :initiator_type
|
|
8
|
+
t.integer :initiator_id
|
|
9
|
+
t.string :action, :null => false
|
|
10
|
+
t.text :diff
|
|
11
|
+
t.text :context
|
|
12
|
+
t.datetime :created_at, :null => false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_index :audit_log, [ :auditable_type, :auditable_id ]
|
|
16
|
+
add_index :audit_log, [ :initiator_type, :initiator_id ]
|
|
17
|
+
add_index :audit_log, :action
|
|
18
|
+
add_index :audit_log, :created_at, :order => { :created_at => :desc }
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: chrno_audit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Denis Diachkov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-08-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.1'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.1'
|
|
41
|
+
description:
|
|
42
|
+
email:
|
|
43
|
+
- d.diachkov@gmail.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- Gemfile
|
|
49
|
+
- Gemfile.lock
|
|
50
|
+
- MIT-LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- Rakefile
|
|
53
|
+
- TODO
|
|
54
|
+
- app/models/chrno_audit/audit_observer.rb
|
|
55
|
+
- app/models/chrno_audit/audit_record.rb
|
|
56
|
+
- chrno_audit-0.4.0.gem
|
|
57
|
+
- chrno_audit.gemspec
|
|
58
|
+
- lib/chrno_audit.rb
|
|
59
|
+
- lib/chrno_audit/action_controller_concern.rb
|
|
60
|
+
- lib/chrno_audit/active_record_concern.rb
|
|
61
|
+
- lib/chrno_audit/version.rb
|
|
62
|
+
- lib/generators/chrno_audit/install_generator.rb
|
|
63
|
+
- lib/generators/chrno_audit/templates/README
|
|
64
|
+
- lib/generators/chrno_audit/templates/initializer.rb
|
|
65
|
+
- lib/generators/chrno_audit/templates/migration.rb
|
|
66
|
+
homepage: https://github.com/ddiachkov/chrno_audit
|
|
67
|
+
licenses: []
|
|
68
|
+
metadata: {}
|
|
69
|
+
post_install_message:
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 1.9.3
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubyforge_project:
|
|
85
|
+
rubygems_version: 2.4.8
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Simple ActiveRecord audit system
|
|
89
|
+
test_files: []
|
|
90
|
+
has_rdoc:
|