effective_logging 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4ffc97c7a82cc7200aa95e8c47c498816bbce9b
4
- data.tar.gz: bb2df38354a9a0cb055f91ab5a07bae5199ac8ea
3
+ metadata.gz: 1df4e571d32d8173adad1e00cc1df96abf6cab61
4
+ data.tar.gz: e17269c8ad802e902237ce2ac10339da99c04cf7
5
5
  SHA512:
6
- metadata.gz: 56fa1f182939c81552fdc31bccc3c0976a7a3052c92963ad6c74e6ebab551c2f455dc8294c21690607bde318707054d3d2b1a94603ea9f9af1c99d0cc7a69a23
7
- data.tar.gz: 7d8631e9a817b077e1d6615092c62b6a865750ab30934cfa59719306e0ba1e5fa7ceb7f8f8ee0915569c4ac45e9c84ee0adc786e285c3f07be0103111b241101
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.where(:user_id => current_user.id).includes(:logs).find(params[:id])
46
- @log.next_log = Effective::Log.unscoped.where(:user_id => current_user.id).order(:id).where(:parent_id => @log.parent_id).where('id > ?', @log.id).first
47
- @log.prev_log = Effective::Log.unscoped.where(:user_id => current_user.id).order(:id).where(:parent_id => @log.parent_id).where('id < ?', @log.id).last
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(:user_id => current_user.id, :log_id => @log.id) if defined?(EffectiveDatatables)
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', :style => 'width: 50%', :th => true)
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", "&nbsp;&nbsp;") : 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
@@ -8,13 +8,13 @@ module Effective
8
8
  attr_accessor :prev_log
9
9
 
10
10
  # Self-Referencing relationship
11
- belongs_to :parent, :class_name => 'Effective::Log', :counter_cache => true
12
- has_many :logs, :dependent => :destroy, :class_name => 'Effective::Log', :foreign_key => :parent_id
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, :polymorphic => true
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
- validates_presence_of :message, :status
30
- validates_inclusion_of :status, in: EffectiveLogging.statuses
29
+ validates :message, presence: true
30
+ validates :status, presence: true, inclusion: { in: (EffectiveLogging.statuses + [EffectiveLogging.log_changes_status]) }
31
31
 
32
- default_scope -> { order("#{EffectiveLogging.logs_table_name.to_s}.updated_at DESC") }
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
- ((self[:details] ||= {}) rescue {})
39
+ self[:details] || {}
40
40
  end
41
41
 
42
42
  # def next_log
@@ -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
+
@@ -1,3 +1,3 @@
1
1
  module EffectiveLogging
2
- VERSION = '1.4.1'.freeze
2
+ VERSION = '1.5.0'.freeze
3
3
  end
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.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-03-12 00:00:00.000000000 Z
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