effective_logging 1.4.1 → 1.5.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.
- checksums.yaml +4 -4
- data/README.md +47 -0
- data/app/controllers/effective/logs_controller.rb +4 -4
- data/app/helpers/effective_logging_helper.rb +1 -1
- data/app/models/concerns/acts_as_loggable.rb +68 -0
- data/app/models/effective/datatables/logs.rb +11 -2
- data/app/models/effective/log.rb +7 -7
- data/lib/effective_logging.rb +14 -2
- data/lib/effective_logging/active_record_logger.rb +131 -0
- data/lib/effective_logging/engine.rb +16 -0
- data/lib/effective_logging/log_changes_user.rb +17 -0
- data/lib/effective_logging/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1df4e571d32d8173adad1e00cc1df96abf6cab61
|
4
|
+
data.tar.gz: e17269c8ad802e902237ce2ac10339da99c04cf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 583ddd1c3f1cf4cba8a6e28b5163909b44944dc51350b1cee16a8fc22b3fdaf8bb935505621f27caf742ad321ef5fefb55ced9a1650af737ee80296fc5ea443f
|
7
|
+
data.tar.gz: 14d0148fd0d692dd55214a38fe513f32285b8003c5557042b42ec3ec3295e961355ca885e09b1bb933e1154ed2e84df8770e4f663899d02a8994c311c042a0b2
|
data/README.md
CHANGED
@@ -89,6 +89,13 @@ log.error('record 2 failed to import', :associated => @record2)
|
|
89
89
|
log.success('record 3 imported', :associated => @record3)
|
90
90
|
```
|
91
91
|
|
92
|
+
### Model
|
93
|
+
|
94
|
+
(optional) Sometimes you'd like to work with the `Effective::Log` relationship directly. Add to your model:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
has_many :logs, class_name: Effective::Log, as: :associated
|
98
|
+
```
|
92
99
|
|
93
100
|
### Automatic Logging of E-mails
|
94
101
|
|
@@ -185,6 +192,46 @@ end
|
|
185
192
|
Similarly, skip_log_page_views also accepts any options that a before_filter would accept.
|
186
193
|
|
187
194
|
|
195
|
+
### Logging ActiveRecord changes
|
196
|
+
|
197
|
+
All changes made to an ActiveRecord object can be logged.
|
198
|
+
|
199
|
+
This logging includes the resource's base attributes, all autosaved associations (any accepts_nested_attributes has_manys) and the string representation of all belongs_tos.
|
200
|
+
|
201
|
+
It will recurse through all accepts_nested_attributes has_many's and handle any number of child objects.
|
202
|
+
|
203
|
+
Add to your model:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
class Post < ActiveRecord::Base
|
207
|
+
log_changes
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
And to your controller:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
class ApplicationController < ActionController::Base
|
215
|
+
set_log_changes_user
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
Then to see the log for this resource, on any view:
|
220
|
+
|
221
|
+
```erb
|
222
|
+
<%= render_datatable(@post.log_changes_datatable) %>
|
223
|
+
```
|
224
|
+
|
225
|
+
The `log_changes` mixin sets up `before_save`, `before_destroy` and `after_commit` hooks to record both an activity log, and an audit log of all changes.
|
226
|
+
|
227
|
+
Each changed attribute will have its before and after values logged to form an activity log.
|
228
|
+
|
229
|
+
And on each create / destroy / update, a full dump of all current attributes is saved, forming an audit log.
|
230
|
+
|
231
|
+
There is some initial support for passing `only`, `except`, and `additionally` to the mixin to customize what attributes are saved.
|
232
|
+
|
233
|
+
Define your model with `log_changes additionally: [:method1, :method2]` to also log the value of methods.
|
234
|
+
|
188
235
|
### Logging From JavaScript
|
189
236
|
|
190
237
|
First, require the javascript in your application.js:
|
@@ -42,14 +42,14 @@ module Effective
|
|
42
42
|
|
43
43
|
# This is the User show event
|
44
44
|
def show
|
45
|
-
@log = Effective::Log.
|
46
|
-
@log.next_log = Effective::Log.unscoped.
|
47
|
-
@log.prev_log = Effective::Log.unscoped.
|
45
|
+
@log = Effective::Log.includes(:logs).find(params[:id])
|
46
|
+
@log.next_log = Effective::Log.unscoped.order(:id).where(parent_id: @log.parent_id).where('id > ?', @log.id).first
|
47
|
+
@log.prev_log = Effective::Log.unscoped.order(:id).where(parent_id: @log.parent_id).where('id < ?', @log.id).last
|
48
48
|
|
49
49
|
@page_title = "Log ##{@log.to_param}"
|
50
50
|
|
51
51
|
if @log.logs.present?
|
52
|
-
@log.datatable = Effective::Datatables::Logs.new(:
|
52
|
+
@log.datatable = Effective::Datatables::Logs.new(log_id: @log.id) if defined?(EffectiveDatatables)
|
53
53
|
end
|
54
54
|
|
55
55
|
EffectiveLogging.authorized?(self, :show, @log)
|
@@ -64,7 +64,7 @@ module EffectiveLoggingHelper
|
|
64
64
|
value = log.details[key]
|
65
65
|
|
66
66
|
if value.kind_of?(Hash)
|
67
|
-
tableize_hash(value, :class => 'table', :
|
67
|
+
tableize_hash(value, :class => 'table', :th => true)
|
68
68
|
elsif value.kind_of?(Array)
|
69
69
|
value.map { |value| effective_logging_simple_format(value) }.join.html_safe
|
70
70
|
else
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ActsAsLoggable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
def log_changes(*options)
|
6
|
+
@acts_as_loggable_options = options.try(:first) || {}
|
7
|
+
|
8
|
+
unless @acts_as_loggable_options.kind_of?(Hash)
|
9
|
+
raise ArgumentError.new("invalid arguments passed to (effective_logging) log_changes. Example usage: log_changes except: [:created_at]")
|
10
|
+
end
|
11
|
+
|
12
|
+
include ::ActsAsLoggable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
included do
|
17
|
+
has_many :logged_changes, -> { where(status: EffectiveLogging.log_changes_status) }, as: :associated, class_name: Effective::Log
|
18
|
+
|
19
|
+
before_save do
|
20
|
+
@acts_as_loggable_new_record = new_record?
|
21
|
+
EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).changed! unless @acts_as_loggable_new_record
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
before_destroy do
|
26
|
+
EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).destroyed!
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
after_commit do
|
31
|
+
if @acts_as_loggable_new_record
|
32
|
+
EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).created!
|
33
|
+
else
|
34
|
+
EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).updated!
|
35
|
+
end
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parse Options
|
40
|
+
log_changes_options = {
|
41
|
+
only: Array(@acts_as_loggable_options[:only]).map { |attribute| attribute.to_s },
|
42
|
+
except: Array(@acts_as_loggable_options[:except]).map { |attribute| attribute.to_s },
|
43
|
+
additionally: Array(@acts_as_loggable_options[:additionally]).map { |attribute| attribute.to_s }
|
44
|
+
}
|
45
|
+
|
46
|
+
self.send(:define_method, :log_changes_options) { log_changes_options }
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
end
|
51
|
+
|
52
|
+
# Regular instance methods
|
53
|
+
|
54
|
+
def log_changes_datatable
|
55
|
+
if persisted?
|
56
|
+
@log_changes_datatable ||= (
|
57
|
+
Effective::Datatables::Logs.new(
|
58
|
+
associated_id: id,
|
59
|
+
associated_type: self.class.name,
|
60
|
+
status: false,
|
61
|
+
log_changes: true
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -15,12 +15,17 @@ if defined?(EffectiveDatatables)
|
|
15
15
|
end
|
16
16
|
|
17
17
|
unless attributes[:status] == false
|
18
|
-
table_column :status, filter: { type: :select, values: EffectiveLogging.statuses }
|
18
|
+
table_column :status, filter: { type: :select, values: (EffectiveLogging.statuses + [EffectiveLogging.log_changes_status]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
table_column :message do |log|
|
22
|
+
log.message.starts_with?("\t") ? log.message.gsub("\t", " ") : log.message
|
19
23
|
end
|
20
24
|
|
21
|
-
table_column :message
|
22
25
|
table_column :logs_count, visible: false
|
23
26
|
|
27
|
+
table_column :associated, filter: false, sortable: false, visible: false
|
28
|
+
|
24
29
|
table_column :details, visible: false, sortable: false do |log|
|
25
30
|
tableize_hash(log.details.except(:email), th: true, sub_th: false, width: '100%')
|
26
31
|
end
|
@@ -47,6 +52,10 @@ if defined?(EffectiveDatatables)
|
|
47
52
|
collection = collection.where(associated_id: attributes[:associated_id], associated_type: attributes[:associated_type])
|
48
53
|
end
|
49
54
|
|
55
|
+
if attributes[:log_changes]
|
56
|
+
collection = collection.where(status: EffectiveLogging.log_changes_status)
|
57
|
+
end
|
58
|
+
|
50
59
|
if attributes[:associated]
|
51
60
|
collection = collection.where(associated: attributes[:associated])
|
52
61
|
end
|
data/app/models/effective/log.rb
CHANGED
@@ -8,13 +8,13 @@ module Effective
|
|
8
8
|
attr_accessor :prev_log
|
9
9
|
|
10
10
|
# Self-Referencing relationship
|
11
|
-
belongs_to :parent, :
|
12
|
-
has_many :logs, :
|
11
|
+
belongs_to :parent, class_name: 'Effective::Log', counter_cache: true
|
12
|
+
has_many :logs, class_name: 'Effective::Log', foreign_key: :parent_id
|
13
13
|
|
14
14
|
# The user this log item is referring to
|
15
15
|
# An associated object, if we wanna add anything extra
|
16
16
|
belongs_to :user
|
17
|
-
belongs_to :associated, :
|
17
|
+
belongs_to :associated, polymorphic: true
|
18
18
|
|
19
19
|
serialize :details, Hash
|
20
20
|
|
@@ -26,17 +26,17 @@ module Effective
|
|
26
26
|
# timestamps
|
27
27
|
# end
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
validates :message, presence: true
|
30
|
+
validates :status, presence: true, inclusion: { in: (EffectiveLogging.statuses + [EffectiveLogging.log_changes_status]) }
|
31
31
|
|
32
|
-
default_scope -> { order(
|
32
|
+
default_scope -> { order(updated_at: :desc) }
|
33
33
|
|
34
34
|
def log(message, status = EffectiveLogging.statuses.first, options = {})
|
35
35
|
EffectiveLogger.log(message, status, (options || {}).merge({:parent => self}))
|
36
36
|
end
|
37
37
|
|
38
38
|
def details
|
39
|
-
|
39
|
+
self[:details] || {}
|
40
40
|
end
|
41
41
|
|
42
42
|
# def next_log
|
data/lib/effective_logging.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'haml-rails'
|
2
2
|
require 'effective_logging/engine'
|
3
3
|
require 'effective_logging/version'
|
4
|
-
require 'effective_logging/log_page_views'
|
5
|
-
require 'effective_logging/user_logger'
|
6
4
|
|
7
5
|
module EffectiveLogging
|
6
|
+
|
8
7
|
# The following are all valid config keys
|
9
8
|
mattr_accessor :logs_table_name
|
10
9
|
mattr_accessor :use_active_admin
|
@@ -39,4 +38,17 @@ module EffectiveLogging
|
|
39
38
|
@statuses ||= (Array(@@additional_statuses).map { |status| status.to_s.downcase } | ['info', 'success', 'error'])
|
40
39
|
end
|
41
40
|
|
41
|
+
def self.log_changes_status
|
42
|
+
'logged change'.freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
# This is set by the "set_log_changes_user" before_filter.
|
46
|
+
def self.log_changes_user=(user)
|
47
|
+
@log_changes_user = user
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.log_changes_user
|
51
|
+
@log_changes_user
|
52
|
+
end
|
53
|
+
|
42
54
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module EffectiveLogging
|
2
|
+
class ActiveRecordLogger
|
3
|
+
attr_accessor :resource, :logger, :depth, :options
|
4
|
+
|
5
|
+
def initialize(resource, options = {})
|
6
|
+
raise ArgumentError.new('options must be a Hash') unless options.kind_of?(Hash)
|
7
|
+
raise ArgumentError.new('logger must respond to logged_changes') unless (options[:logger] || resource).respond_to?(:logged_changes)
|
8
|
+
|
9
|
+
@resource = resource
|
10
|
+
@logger = options.delete(:logger) || resource
|
11
|
+
@depth = options.delete(:depth) || 0
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# execute! is called when we recurse, otherwise the following methods are best called individually
|
16
|
+
def execute!
|
17
|
+
if resource.new_record?
|
18
|
+
created!
|
19
|
+
elsif resource.marked_for_destruction?
|
20
|
+
destroyed!
|
21
|
+
else
|
22
|
+
changed!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# before_destroy
|
27
|
+
def destroyed!
|
28
|
+
log('Deleted', applicable(attributes))
|
29
|
+
end
|
30
|
+
|
31
|
+
# after_commit
|
32
|
+
def created!
|
33
|
+
log('Created', applicable(attributes))
|
34
|
+
end
|
35
|
+
|
36
|
+
# after_commit
|
37
|
+
def updated!
|
38
|
+
log('Updated', applicable(attributes))
|
39
|
+
end
|
40
|
+
|
41
|
+
# before_save
|
42
|
+
def changed!
|
43
|
+
applicable(changes).each do |attribute, (before, after)|
|
44
|
+
if after.present?
|
45
|
+
log("#{attribute.titleize} changed from '#{before}' to '#{after}'", { attribute: attribute, before: before, after: after })
|
46
|
+
else
|
47
|
+
log("#{attribute.titleize} set to '#{before}'", { attribute: attribute, value: before })
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Log changes on all accepts_as_nested_parameters has_many associations
|
52
|
+
(resource.class.try(:reflect_on_all_autosave_associations) || []).each do |association|
|
53
|
+
child_name = association.name.to_s.singularize.titleize
|
54
|
+
|
55
|
+
resource.send(association.name).each_with_index do |child, index|
|
56
|
+
ActiveRecordLogger.new(child, options.merge(logger: logger, depth: (depth + 1), prefix: "#{child_name} ##{index+1}: ")).execute!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def attributes
|
62
|
+
attributes = { attributes: resource.attributes }
|
63
|
+
|
64
|
+
# Collect to_s representations of all belongs_to associations
|
65
|
+
(resource.class.try(:reflect_on_all_associations, :belongs_to) || []).each do |association|
|
66
|
+
attributes[association.name] = resource.send(association.name).to_s.presence || 'nil'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Collect to_s representations for all has_one associations
|
70
|
+
(resource.class.try(:reflect_on_all_associations, :has_one) || []).each do |association|
|
71
|
+
attributes[association.name] = resource.send(association.name).to_s.presence || 'nil'
|
72
|
+
end
|
73
|
+
|
74
|
+
# Collects attributes for all accepts_as_nested_parameters has_many associations
|
75
|
+
(resource.class.try(:reflect_on_all_autosave_associations) || []).each do |association|
|
76
|
+
attributes[association.name] = {}
|
77
|
+
|
78
|
+
resource.send(association.name).each_with_index do |child, index|
|
79
|
+
attributes[association.name][index+1] = ActiveRecordLogger.new(child, options.merge(logger: logger)).attributes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attributes
|
84
|
+
end
|
85
|
+
|
86
|
+
def changes
|
87
|
+
changes = resource.changes
|
88
|
+
|
89
|
+
# Log to_s changes on all belongs_to associations
|
90
|
+
(resource.class.try(:reflect_on_all_associations, :belongs_to) || []).each do |association|
|
91
|
+
if (change = changes.delete(association.foreign_key)).present?
|
92
|
+
changes[association.name] = [association.klass.find_by_id(change.first), resource.send(association.name)]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
changes
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def log(message, details = {})
|
102
|
+
logger.logged_changes.build(
|
103
|
+
user: EffectiveLogging.log_changes_user,
|
104
|
+
status: EffectiveLogging.log_changes_status,
|
105
|
+
message: "#{"\t" * depth}#{options[:prefix]}#{message}",
|
106
|
+
details: details
|
107
|
+
).tap { |log| log.save }
|
108
|
+
end
|
109
|
+
|
110
|
+
# TODO: Make this work better with nested objects
|
111
|
+
def applicable(attributes)
|
112
|
+
atts = if options[:only].present?
|
113
|
+
attributes.slice(*options[:only])
|
114
|
+
elsif options[:except].present?
|
115
|
+
attributes.except(*options[:except])
|
116
|
+
else
|
117
|
+
attributes
|
118
|
+
end
|
119
|
+
|
120
|
+
options[:additionally].each do |attribute|
|
121
|
+
value = (resource.send(attribute) rescue :effective_logging_nope)
|
122
|
+
next if attributes[attribute].present? || value == :effective_logging_nope
|
123
|
+
|
124
|
+
atts[attribute] = value
|
125
|
+
end
|
126
|
+
|
127
|
+
atts
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -2,6 +2,8 @@ module EffectiveLogging
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
engine_name 'effective_logging'
|
4
4
|
|
5
|
+
config.autoload_paths += Dir["#{config.root}/lib/"]
|
6
|
+
|
5
7
|
# Set up our default configuration options.
|
6
8
|
initializer "effective_logging.defaults", :before => :load_config_initializers do |app|
|
7
9
|
eval File.read("#{config.root}/lib/generators/templates/effective_logging.rb")
|
@@ -23,6 +25,20 @@ module EffectiveLogging
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
28
|
+
# Include acts_as_loggable concern and allow any ActiveRecord object to call it with log_changes()
|
29
|
+
initializer 'effective_logging.active_record' do |app|
|
30
|
+
ActiveSupport.on_load :active_record do
|
31
|
+
ActiveRecord::Base.extend(ActsAsLoggable::ActiveRecord)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Register the log_page_views concern so that it can be called in ActionController or elsewhere
|
36
|
+
initializer 'effective_logging.log_changes_action_controller' do |app|
|
37
|
+
ActiveSupport.on_load :action_controller do
|
38
|
+
ActionController::Base.include(EffectiveLogging::LogChangesUser)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
26
42
|
# Register the log_page_views concern so that it can be called in ActionController or elsewhere
|
27
43
|
initializer 'effective_logging.action_controller' do |app|
|
28
44
|
ActiveSupport.on_load :action_controller do
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module EffectiveLogging
|
2
|
+
module LogChangesUser
|
3
|
+
|
4
|
+
# Add me to your ApplicationController
|
5
|
+
# before_action :set_log_changes_user
|
6
|
+
|
7
|
+
def set_log_changes_user
|
8
|
+
if responds_to?(:current_user)
|
9
|
+
EffectiveLogging.log_changes_user = current_user
|
10
|
+
else
|
11
|
+
raise "(effective_logging) set_log_changes_user expects a current_user() method to be available"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_logging
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- app/controllers/admin/logs_controller.rb
|
97
97
|
- app/controllers/effective/logs_controller.rb
|
98
98
|
- app/helpers/effective_logging_helper.rb
|
99
|
+
- app/models/concerns/acts_as_loggable.rb
|
99
100
|
- app/models/effective/access_denied.rb
|
100
101
|
- app/models/effective/datatables/logs.rb
|
101
102
|
- app/models/effective/log.rb
|
@@ -111,8 +112,10 @@ files:
|
|
111
112
|
- config/routes.rb
|
112
113
|
- db/migrate/01_create_effective_logging.rb.erb
|
113
114
|
- lib/effective_logging.rb
|
115
|
+
- lib/effective_logging/active_record_logger.rb
|
114
116
|
- lib/effective_logging/email_logger.rb
|
115
117
|
- lib/effective_logging/engine.rb
|
118
|
+
- lib/effective_logging/log_changes_user.rb
|
116
119
|
- lib/effective_logging/log_page_views.rb
|
117
120
|
- lib/effective_logging/user_logger.rb
|
118
121
|
- lib/effective_logging/version.rb
|