effective_logging 2.1.2 → 3.0.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: 2c1119cce5caf2fb9dca0d4eb7411b47c37bbf1a
4
- data.tar.gz: ae41f8263546dee230f0f52527d81ae590c083da
3
+ metadata.gz: 20e9e57d5a67995068422a609568b3124e6bab4b
4
+ data.tar.gz: be6fdf6286b4b661881e13e529b8f7ce220cdccf
5
5
  SHA512:
6
- metadata.gz: 12de1d7714145177529bfe2ce9d4d258c7ecdb790bc116246b4daf01452d4f1ff70a4c5bf79d6c67cbacbd90891368aa23874e7809cedc0efd06c7b6e6f49441
7
- data.tar.gz: eca3118328ae97ffdd3c835236477ffe2d4c465de367fe83f664cadada66be41bcc412fe588a62d8aeac790837e3a3ec18e5ebe0f78bf6fc17967667a7ae8201
6
+ metadata.gz: eb7025f50e12b1fca55f87a66ae177b923a0d06800d9b3fa1fc0979e08b97a48976e31c341a2f522d521c49d487ccdf652be893a7c98c0644eefe5af421f9269
7
+ data.tar.gz: 744819fb3d4dd1d27c02064c5cd09ce504c650a3cb91d96c1aed298d5810b3bcabc79ce24abac6e3b7d517672cd3da89d9e850c8d63f9dad5649b03114e78ccf
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2018 Code and Effect Inc.
1
+ Copyright 2019 Code and Effect Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -233,9 +233,7 @@ class Post < ActiveRecord::Base
233
233
  end
234
234
  ```
235
235
 
236
- There is some initial support for passing `only`, `except`, and `additionally` to the mixin to customize what attributes are saved.
237
-
238
- Define your model with `log_changes additionally: [:method1, :method2]` to also _always_ log the value of that method. Even if it's unchanged.
236
+ There is some initial support for passing `only`, and `except`, to the mixin to customize what attributes are saved.
239
237
 
240
238
  Apply your own formatting to the logged title of each attribute by creating an instance method on the resource:
241
239
 
@@ -0,0 +1,43 @@
1
+ class EffectiveLogChangesDatatable < Effective::Datatable
2
+ datatable do
3
+ order :updated_at
4
+
5
+ col :updated_at, label: 'Date'
6
+ col :id, visible: false
7
+
8
+ col :user, sort: false
9
+
10
+ col :associated_type, visible: false
11
+ col :associated_id, visible: false
12
+ col :associated_to_s, visible: false
13
+
14
+ col :message, sort: false do |log|
15
+ message = log.message.gsub("\n", '<br>')
16
+
17
+ if log.associated_id == attributes[:changes_to_id] && log.associated_type == attributes[:changes_to_type]
18
+ message
19
+ else
20
+ "#{log.associated_type} #{log.associated_to_s} - #{message}"
21
+ end
22
+
23
+ end.search do |collection, term, column, sql_column|
24
+ collection.where("associated_type #{resource.ilike} ? OR associated_to_s #{resource.ilike} ? OR message #{resource.ilike} ?", "%#{term}%", "%#{term}%", "%#{term}%")
25
+ end
26
+
27
+ col :details, visible: false, sort: false do |log|
28
+ tableize_hash(log.details)
29
+ end
30
+
31
+ unless attributes[:actions] == false
32
+ actions_col partial: 'admin/logs/actions', partial_as: :log
33
+ end
34
+ end
35
+
36
+ # A nil attributes[:log_id] means give me all the top level log entries
37
+ # If we set a log_id then it's for sub logs
38
+ collection do
39
+ Effective::Log.logged_changes.deep
40
+ .where(changes_to_type: attributes[:changes_to_type], changes_to_id: attributes[:changes_to_id])
41
+ end
42
+
43
+ end
@@ -17,18 +17,12 @@ class EffectiveLogsDatatable < Effective::Datatable
17
17
  col :status, search: { collection: EffectiveLogging.statuses }
18
18
  end
19
19
 
20
- unless attributes[:log_changes]
21
- col :associated_type, search: { as: :string }
22
- col :associated_id, search: { as: :integer }, visible: false, label: 'Associated Id'
23
- col :associated_to_s, search: { as: :string }, label: 'Associated'
24
- end
20
+ col :associated_type, search: { as: :string }
21
+ col :associated_id, search: { as: :integer }, visible: false, label: 'Associated Id'
22
+ col :associated_to_s, search: { as: :string }, label: 'Associated'
25
23
 
26
- if attributes[:log_changes]
27
- col :message do |log|
28
- log.message.starts_with?("\t") ? log.message.gsub("\t", "&nbsp;&nbsp;") : log.message
29
- end
30
- else
31
- col :message
24
+ col :message do |log|
25
+ log.message.gsub("\n", '<br>')
32
26
  end
33
27
 
34
28
  col :logs_count, visible: false
@@ -47,10 +41,6 @@ class EffectiveLogsDatatable < Effective::Datatable
47
41
  collection do
48
42
  scope = Effective::Log.deep.where(parent_id: attributes[:log_id])
49
43
 
50
- if attributes[:log_changes]
51
- scope = scope.where(status: EffectiveLogging.log_changes_status)
52
- end
53
-
54
44
  if attributes[:for]
55
45
  user_ids = Array(attributes[:for])
56
46
  scope = scope.where('user_id IN (?) OR (associated_id IN (?) AND associated_type = ?)', user_ids, user_ids, 'User')
@@ -9,7 +9,7 @@ module ActsAsLoggable
9
9
  raise ArgumentError.new('invalid arguments passed to (effective_logging) log_changes. Example usage: log_changes except: [:created_at]')
10
10
  end
11
11
 
12
- if (unknown = (@acts_as_loggable_options.keys - [:only, :except, :additionally, :include_associated, :include_nested])).present?
12
+ if (unknown = (@acts_as_loggable_options.keys - [:to, :prefix, :only, :except])).present?
13
13
  raise ArgumentError.new("unknown keyword: #{unknown.join(', ')}")
14
14
  end
15
15
 
@@ -18,64 +18,52 @@ module ActsAsLoggable
18
18
  end
19
19
 
20
20
  included do
21
- has_many :logged_changes, -> { order(:id).where(status: EffectiveLogging.log_changes_status) }, as: :associated, class_name: 'Effective::Log'
21
+ has_many :logged_changes, -> { order(:id).where(status: EffectiveLogging.log_changes_status) }, as: :changes_to, class_name: 'Effective::Log'
22
22
 
23
23
  log_changes_options = {
24
- only: Array(@acts_as_loggable_options[:only]).map { |attribute| attribute.to_s },
25
- except: Array(@acts_as_loggable_options[:except]).map { |attribute| attribute.to_s },
26
- additionally: Array(@acts_as_loggable_options[:additionally]).map { |attribute| attribute.to_s },
27
- include_associated: @acts_as_loggable_options.fetch(:include_associated, true),
28
- include_nested: @acts_as_loggable_options.fetch(:include_nested, true)
24
+ to: @acts_as_loggable_options[:to],
25
+ prefix: @acts_as_loggable_options[:prefix],
26
+ only: Array(@acts_as_loggable_options[:only]),
27
+ except: Array(@acts_as_loggable_options[:except])
29
28
  }
30
29
 
31
30
  if name == 'User'
32
- log_changes_options[:except] += %w(sign_in_count current_sign_in_at current_sign_in_ip last_sign_in_at last_sign_in_ip encrypted_password remember_created_at reset_password_token invitation_sent_at invitation_created_at invitation_token)
31
+ log_changes_options[:except] += %i(sign_in_count current_sign_in_at current_sign_in_ip last_sign_in_at last_sign_in_ip encrypted_password remember_created_at reset_password_token invitation_sent_at invitation_created_at invitation_token)
33
32
  end
34
33
 
35
34
  self.send(:define_method, :log_changes_options) { log_changes_options }
36
35
 
37
36
  after_create(unless: -> { EffectiveLogging.supressed? }) do
38
- ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).execute!
37
+ ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).created!
39
38
  end
40
39
 
41
40
  after_destroy(unless: -> { EffectiveLogging.supressed? }) do
42
- ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).execute!
41
+ ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).destroyed!
43
42
  end
44
43
 
45
44
  after_update(unless: -> { EffectiveLogging.supressed? }) do
46
- ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).execute!
45
+ ::EffectiveLogging::ActiveRecordLogger.new(self, log_changes_options).updated!
47
46
  end
48
47
  end
49
48
 
50
49
  module ClassMethods
50
+ def acts_as_loggable?; true; end
51
51
  end
52
52
 
53
53
  # Regular instance methods
54
54
 
55
55
  # Format the title of this attribute. Return nil to use the default attribute.titleize
56
56
  def log_changes_formatted_attribute(attribute)
57
- if attribute == 'roles_mask' && defined?(EffectiveRoles) && respond_to?(:roles)
58
- 'Roles'
59
- end
57
+ 'Roles' if attribute == 'roles_mask' && defined?(EffectiveRoles) && respond_to?(:roles)
60
58
  end
61
59
 
62
60
  # Format the value of this attribute. Return nil to use the default to_s
63
61
  def log_changes_formatted_value(attribute, value)
64
- if attribute == 'roles_mask' && defined?(EffectiveRoles) && respond_to?(:roles)
65
- EffectiveRoles.roles_for(value)
66
- end
62
+ EffectiveRoles.roles_for(value) if attribute == 'roles_mask' && defined?(EffectiveRoles) && respond_to?(:roles)
67
63
  end
68
64
 
69
65
  def log_changes_datatable
70
- return nil unless persisted?
71
-
72
- @log_changes_datatable ||= (
73
- EffectiveLogsDatatable.new(associated_id: id, associated_type: self.class.name, log_changes: true, status: false)
74
- )
75
- end
76
-
77
- def refresh_datatables
78
- @refresh_datatables ||= [:effective_logs]
66
+ EffectiveLogChangesDatatable.new(changes_to_id: id, changes_to_type: self.class.name) if persisted?
79
67
  end
80
68
 
81
69
  end
@@ -12,6 +12,8 @@ module Effective
12
12
  has_many :logs, class_name: 'Effective::Log', foreign_key: :parent_id
13
13
 
14
14
  belongs_to :user
15
+
16
+ belongs_to :changes_to, polymorphic: true # This is the log_changes to: option
15
17
  belongs_to :associated, polymorphic: true
16
18
 
17
19
  serialize :details, Hash
@@ -19,10 +21,13 @@ module Effective
19
21
  # Attributes
20
22
  # logs_count :integer # Rails Counter Cache
21
23
 
24
+ # changes_to_type :string
25
+ # changes_to_id :string
26
+
22
27
  # associated_type :string
23
28
  # associated_id :integer
24
29
  # associated_to_s :string
25
- # message :string
30
+ # message :text
26
31
  # details :text
27
32
  # status :string
28
33
  # timestamps
@@ -39,10 +44,6 @@ module Effective
39
44
  "Log #{id}"
40
45
  end
41
46
 
42
- def message=(msg)
43
- write_attribute(:message, msg.kind_of?(String) ? msg.to_s[0...255] : msg)
44
- end
45
-
46
47
  def log(message, status = EffectiveLogging.statuses.first, options = {})
47
48
  EffectiveLogger.log(message, status, (options || {}).merge(parent: self))
48
49
  end
@@ -5,7 +5,7 @@
5
5
  - parents_of_log(log).each do |parent|
6
6
  = link_to(log.message, request.fullpath.sub('/' + log.to_param, '/' + parent.to_param))
7
7
  = ' > '
8
- %p= log.message.html_safe
8
+ %p= log.message.to_s.gsub("\n", '<br>').html_safe
9
9
 
10
10
  .col-md-4.text-right
11
11
  - if log.prev_log
@@ -5,13 +5,16 @@ class CreateEffectiveLogging < ActiveRecord::Migration[4.2]
5
5
 
6
6
  t.integer :user_id
7
7
 
8
+ t.string :changes_to_type
9
+ t.integer :changes_to_id
10
+
8
11
  t.string :associated_type
9
12
  t.integer :associated_id
10
13
  t.string :associated_to_s
11
14
 
12
15
  t.integer :logs_count
13
16
 
14
- t.string :message
17
+ t.text :message
15
18
  t.text :details
16
19
 
17
20
  t.string :status
@@ -1,179 +1,89 @@
1
1
  module EffectiveLogging
2
2
  class ActiveRecordLogger
3
- attr_accessor :object, :resource, :logger, :depth, :include_associated, :include_nested, :log_parents, :options
3
+ attr_accessor :object, :resource, :options
4
4
 
5
5
  BLANK = "''"
6
+ BLACKLIST = [:updated_at, :created_at, :encrypted_password, :status_steps] # Don't log changes or attributes
6
7
 
7
- def initialize(object, options = {})
8
- raise ArgumentError.new('options must be a Hash') unless options.kind_of?(Hash)
9
-
8
+ def initialize(object, to: nil, prefix: nil, only: nil, except: nil)
10
9
  @object = object
11
10
  @resource = Effective::Resource.new(object)
12
11
 
13
- @logger = options.fetch(:logger, object)
14
- @depth = options.fetch(:depth, 0)
15
- @include_associated = options.fetch(:include_associated, true)
16
- @include_nested = options.fetch(:include_nested, true)
17
- @log_parents = options.fetch(:log_parents, true)
18
- @options = options
19
-
20
- raise ArgumentError.new('logger must respond to logged_changes') unless @logger.respond_to?(:logged_changes)
21
- end
22
-
23
- def execute!
24
- @logged = false
25
-
26
- if new_record?(object)
27
- created!
28
- elsif destroyed_record?(object)
29
- destroyed!
30
- else
31
- updated!
12
+ # Validate changes_to value
13
+ if to.present? && !@resource.belong_tos.map(&:name).include?(to)
14
+ raise ArgumentError.new("unable to find existing belongs_to relationship matching #{to}. Expected a symbol matching a belongs_to.")
32
15
  end
33
16
 
34
- logged?
17
+ @options = { to: to, prefix: prefix, only: only, except: Array(except) + BLACKLIST }.compact
35
18
  end
36
19
 
20
+ # Effective::Log.where(message: 'Deleted').where('details ILIKE ?', '%lab_test_id: 263%')
37
21
  def destroyed!
38
- log('Deleted', applicable(instance_attributes))
22
+ log('Deleted', resource_attributes)
39
23
  end
40
24
 
41
25
  def created!
42
- log('Created', applicable(instance_attributes))
26
+ log('Created', resource_attributes)
43
27
  end
44
28
 
45
29
  def updated!
46
- log_resource_changes!
47
- log_nested_resources!
48
-
49
- (logged? && depth == 0) ? log('Updated', applicable(instance_attributes)) : true
50
- end
51
-
52
- def log_resource_changes!
53
- applicable(resource.instance_changes).each do |attribute, (before, after)|
54
- if object.respond_to?(:log_changes_formatted_value)
55
- before = object.log_changes_formatted_value(attribute, before) || before
56
- after = object.log_changes_formatted_value(attribute, after) || after
57
- end
58
-
59
- before = before.to_s if before.kind_of?(ActiveRecord::Base) || before.kind_of?(FalseClass)
60
- after = after.to_s if after.kind_of?(ActiveRecord::Base) || after.kind_of?(FalseClass)
30
+ changes = resource_changes
61
31
 
62
- attribute = if object.respond_to?(:log_changes_formatted_attribute)
63
- object.log_changes_formatted_attribute(attribute)
64
- end || attribute.titleize
65
-
66
- log("#{attribute}: #{before.presence || BLANK} &rarr; #{after.presence || BLANK}", { attribute: attribute, before: before, after: after })
67
- end
68
- end
32
+ return true if changes.blank? # If you just click save and change nothing, don't log it.
69
33
 
70
- def log_nested_resources!
71
- return unless include_nested
34
+ message = (['Updated'] + changes.map do |attribute, (before, after)|
35
+ "#{attribute}: #{before.presence || BLANK} &rarr; #{after.presence || BLANK}"
36
+ end).join("\n")
72
37
 
73
- # Log changes on all accepts_as_nested_parameters has_many associations
74
- resource.nested_resources.each do |association|
75
- title = association.name.to_s.singularize.titleize
76
-
77
- Array(object.send(association.name)).each_with_index do |child, index|
78
- next unless child.present?
79
-
80
- child_options = options.merge(logger: logger, depth: depth+1, prefix: "#{title} #{index} - #{child} - ", include_associated: include_associated, include_nested: include_nested)
81
- child_options = child_options.merge(child.log_changes_options) if child.respond_to?(:log_changes_options)
82
-
83
- @logged = true if ActiveRecordLogger.new(child, child_options).execute!
84
- end
85
- end
38
+ log(message, resource_attributes.merge(changes: changes))
86
39
  end
87
40
 
88
41
  def log(message, details)
89
- log = logger.logged_changes.build(
42
+ Effective::Log.create!(
43
+ changes_to: log_changes_to,
44
+ associated: object,
45
+ associated_to_s: (object.to_s rescue nil),
90
46
  user: EffectiveLogging.current_user,
91
47
  status: EffectiveLogging.log_changes_status,
92
- message: "#{"\t" * depth}#{options[:prefix]}#{message}",
93
- associated_to_s: (logger.to_s rescue nil),
48
+ message: [options[:prefix].presence, message].compact.join,
94
49
  details: (details.presence || {})
95
50
  )
51
+ end
96
52
 
97
- log_changes_to_parents(message, details) if depth == 0 && log_parents
53
+ private
98
54
 
99
- @logged = log.save!
100
- log
101
- end
55
+ def log_changes_to
56
+ logger = object
102
57
 
103
- def log_changes_to_parents(message, details)
104
- log_changes_parents.each do |parent|
105
- title = object.class.name.to_s.singularize.titleize
106
- parent_options = { logger: parent, prefix: "#{title} - #{object} - " }
107
- ActiveRecordLogger.new(parent, parent_options).log(message, details)
58
+ while(logger.log_changes_options[:to].present?)
59
+ belongs_to = logger.public_send(logger.log_changes_options[:to])
60
+ break unless belongs_to.respond_to?(:log_changes_options)
61
+ logger = belongs_to
108
62
  end
109
- end
110
63
 
111
- private
112
-
113
- def logged?
114
- @logged == true
64
+ logger
115
65
  end
116
66
 
117
- def instance_attributes # effective_resources gem
118
- resource.instance_attributes(include_associated: include_associated, include_nested: include_nested)
67
+ def resource_attributes # effective_resources gem
68
+ resource.instance_attributes(only: options[:only], except: options[:except])
119
69
  end
120
70
 
121
- # A parent is a belongs_to that accepts_nested_attributes for this resource
122
- def log_changes_parents
123
- resource.belong_tos.map do |association|
124
- parent_klass = (association.options[:polymorphic] ? object.public_send(association.name).class : association.klass)
125
-
126
- # Skip if the parent doesn't log_changes
127
- next unless parent_klass.respond_to?(:log_changes)
128
-
129
- # Skip unless the parent accepts_nested_attributes
130
- next unless Effective::Resource.new(parent_klass).nested_resources.find { |ass| ass.plural_name == resource.plural_name }
131
-
132
- parent = object.public_send(association.name).presence
133
-
134
- # Sanity check
135
- next unless parent.respond_to?(:log_changes_options)
136
-
137
- # Skip if the parent does not log its nested or associated items
138
- next unless parent.log_changes_options[:include_nested] || parent.log_changes_options[:include_associated]
139
-
140
- parent
141
- end.compact
142
- end
71
+ def resource_changes # effective_resources gem
72
+ resource.instance_changes(only: options[:only], except: options[:except]).inject({}) do |h, (attribute, (before, after))|
73
+ if object.respond_to?(:log_changes_formatted_value)
74
+ before = object.log_changes_formatted_value(attribute, before) || before
75
+ after = object.log_changes_formatted_value(attribute, after) || after
76
+ end
143
77
 
144
- # TODO: Make this work better with nested objects
145
- def applicable(attributes)
146
- atts = if options[:only].present?
147
- attributes.stringify_keys.slice(*options[:only])
148
- elsif options[:except].present?
149
- attributes.stringify_keys.except(*options[:except])
150
- else
151
- attributes.except(:updated_at, :created_at, 'updated_at', 'created_at')
152
- end
78
+ before = before.to_s if before.kind_of?(ActiveRecord::Base) || before.kind_of?(FalseClass)
79
+ after = after.to_s if after.kind_of?(ActiveRecord::Base) || after.kind_of?(FalseClass)
153
80
 
154
- (options[:additionally] || []).each do |attribute|
155
- value = (object.send(attribute) rescue :effective_logging_nope)
156
- next if attributes[attribute].present? || value == :effective_logging_nope
81
+ attribute = if object.respond_to?(:log_changes_formatted_attribute)
82
+ object.log_changes_formatted_attribute(attribute)
83
+ end || attribute.to_s.titleize
157
84
 
158
- atts[attribute] = value
85
+ h[attribute] = [before, after]; h
159
86
  end
160
-
161
- # Blacklist
162
- atts.except(:logged_changes, :trash, :updated_at, :status_steps, 'logged_changes', 'trash', 'updated_at', 'status_steps')
163
- end
164
-
165
- def new_record?(object)
166
- return true if object.respond_to?(:new_record?) && object.new_record?
167
- return true if object.respond_to?(:id_was) && object.id_was.nil?
168
- return true if object.respond_to?(:previous_changes) && object.previous_changes.key?('id') && object.previous_changes['id'].first.nil?
169
- false
170
- end
171
-
172
- def destroyed_record?(object)
173
- return true if object.respond_to?(:destroyed?) && object.destroyed?
174
- return true if object.respond_to?(:marked_for_destruction?) && object.marked_for_destruction?
175
- return true if object.respond_to?(:previous_changes) && object.previous_changes.key?('id') && object.previous_changes['id'].last.nil?
176
- false
177
87
  end
178
88
  end
179
89
 
@@ -1,3 +1,3 @@
1
1
  module EffectiveLogging
2
- VERSION = '2.1.2'.freeze
2
+ VERSION = '3.0.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: 2.1.2
4
+ version: 3.0.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: 2019-01-24 00:00:00.000000000 Z
11
+ date: 2019-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,6 +94,7 @@ files:
94
94
  - app/assets/javascripts/effective_logging/effective_logger.js.coffee.erb
95
95
  - app/controllers/admin/logs_controller.rb
96
96
  - app/controllers/effective/logs_controller.rb
97
+ - app/datatables/effective_log_changes_datatable.rb
97
98
  - app/datatables/effective_logs_datatable.rb
98
99
  - app/helpers/effective_logging_helper.rb
99
100
  - app/models/concerns/acts_as_loggable.rb