frederick_operations_logger 1.1.2
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
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c8489f6536a285d6114451401fd94d9d4043faf6
|
4
|
+
data.tar.gz: 9ad30e94ba02ee33085ca246a200783735b85e73
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5565ba1daa79f65f25323d080a9ffcb9c0de12cc9483622d3516fa859b7e1fa3b6991a59982e4db2828fda42b8e0e23d6602910993dceb5b33b9808d5521446e
|
7
|
+
data.tar.gz: 132ca1e9a3ba35ef17ef6cd3a734718de1a7d689a9345088c074e460854b988994d5d3171fac627a08558f9bd72cc2df6f91efee58cb137bbd21bebd9a1d83a2
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Frederick Operations Logger
|
2
|
+
|
3
|
+
[ ](https://app.codeship.com/projects/261776)
|
4
|
+
|
5
|
+
Provides a Rails / ActiveRecord helper to automatically log all CRUD operations to the appropriate Kafka
|
6
|
+
`operations_log` topic for other services to be notified on resource changes.
|
7
|
+
|
8
|
+
## Example Usage
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
# Gemfile
|
12
|
+
|
13
|
+
FRED_GITHUB_URL = 'https://8218b10ce7d4e20bd6d96d25bd3358ecafb286d8:x-oauth-basic@github.com/BookerSoftwareInc/'
|
14
|
+
gem 'frederick_internal_api', git: "#{FRED_GITHUB_URL}frederick_internal_api_gem.git"
|
15
|
+
gem 'frederick_operations_logger', git: "#{FRED_GITHUB_URL}frederick_operations_logger.git"
|
16
|
+
```
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class FooModel < ActiveRecord::Base
|
20
|
+
include FrederickOperationsLogger::ActiveRecord::Helper
|
21
|
+
|
22
|
+
# Adds an after_commit callback to log CRUD operations.
|
23
|
+
# Both a JSONAPI Resource class and Frederick API client class must be provided
|
24
|
+
# to be used by the logger when serializing the resource
|
25
|
+
# API Client classes are provided by the frederick_api gem or frederick_internal_api gem
|
26
|
+
# You can provide a string (as below) to reference a class if needed to avoid
|
27
|
+
# dependecy / load order issues between model and resource classes
|
28
|
+
logs_operations_with resource_class: 'Api::V2::FooResource', api_client_class: FrederickAPI::V2::Foo
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
## RSpec Test Helper - add this to your model spec
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# spec/models/foo_model_spec.rb
|
36
|
+
require 'spec_helper'
|
37
|
+
require 'frederick_operations_logger/test/shared_examples'
|
38
|
+
|
39
|
+
describe FooModel do
|
40
|
+
include_examples 'logs_operations_with', {
|
41
|
+
resource_class: Api::V2::FooResource,
|
42
|
+
api_client_class: FrederickAPI::V2::Foo
|
43
|
+
}
|
44
|
+
end
|
45
|
+
```
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'frederick_internal_api'
|
4
|
+
require 'frederick_operations_logger/active_record/helper'
|
5
|
+
require 'request_store'
|
6
|
+
|
7
|
+
# Base gem module
|
8
|
+
module FrederickOperationsLogger
|
9
|
+
REQUEST_STORE_KEY = 'frederick_operations_logger_context'
|
10
|
+
ALLOWED_ACTIVITIES = [
|
11
|
+
're_accept_tos', # when subscribers re-accept TOS from ui dialog
|
12
|
+
'update_communication_preferences', # when consumers update preferences from a ui
|
13
|
+
].freeze
|
14
|
+
AUTH_INFO_KEYS = %i[user_id application_id application_name ip_address].freeze
|
15
|
+
|
16
|
+
# FrederickOperationsLogger.context = { activity: 'foo', auth_info: { user_id: 'some...uuid' } }
|
17
|
+
# using applications can set local context without concern for empty context in consumed message
|
18
|
+
# TODO: auth_info should be set automatically from frederick_api_auth_gem for REST calls
|
19
|
+
# auth_info: {
|
20
|
+
# user_id: The user id performing the action (if not performed by a system function),
|
21
|
+
# application_id: The Doorkeeper application ID that relates to the access token being used,
|
22
|
+
# application_name: The Doorkeeper application name or internal application name performing the action
|
23
|
+
# ip_address: The IP address of the authorized entity performing the action (user or application)
|
24
|
+
# }
|
25
|
+
# Use merge: false to replace an already set context instead of merging new info
|
26
|
+
def self.context=(activity: nil, auth_info: nil, extras: nil, merge: true)
|
27
|
+
auth_info = auth_info.compact.symbolize_keys if auth_info.present?
|
28
|
+
extras = extras.compact.deep_symbolize_keys if extras.present?
|
29
|
+
|
30
|
+
return if activity.nil? && auth_info.blank? && extras.blank? # safeguard against setting empty context
|
31
|
+
validate_context(activity, auth_info)
|
32
|
+
|
33
|
+
RequestStore.store[REQUEST_STORE_KEY] ||= {}
|
34
|
+
RequestStore.store[REQUEST_STORE_KEY][:context] = if merge
|
35
|
+
merge_context(
|
36
|
+
activity: activity,
|
37
|
+
auth_info: auth_info,
|
38
|
+
extras: extras
|
39
|
+
)
|
40
|
+
else
|
41
|
+
{
|
42
|
+
activity: activity,
|
43
|
+
auth_info: auth_info,
|
44
|
+
extras: extras
|
45
|
+
}.compact
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.merge_context(activity: nil, auth_info: nil, extras: nil)
|
50
|
+
previous = self.context
|
51
|
+
new_auth_info = if auth_info.present?
|
52
|
+
previous[:auth_info].present? ? previous[:auth_info].merge(auth_info) : auth_info
|
53
|
+
else
|
54
|
+
previous[:auth_info]
|
55
|
+
end
|
56
|
+
new_extras = if extras.present?
|
57
|
+
previous[:extras].present? ? previous[:extras].merge(extras) : extras
|
58
|
+
else
|
59
|
+
previous[:extras]
|
60
|
+
end
|
61
|
+
|
62
|
+
{
|
63
|
+
activity: activity || previous[:activity],
|
64
|
+
auth_info: new_auth_info,
|
65
|
+
extras: new_extras
|
66
|
+
}.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
# FrederickOperationsLogger.context
|
70
|
+
def self.context
|
71
|
+
RequestStore.store.dig(REQUEST_STORE_KEY, :context) || {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.validate_context(activity, auth_info)
|
75
|
+
raise "Invalid activity: #{activity}" unless activity.nil? || ALLOWED_ACTIVITIES.include?(activity)
|
76
|
+
return unless auth_info.present? && (auth_info.keys - AUTH_INFO_KEYS).any?
|
77
|
+
|
78
|
+
raise "Invalid auth_info: only #{AUTH_INFO_KEYS.join(', ')} are allowed"
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrederickOperationsLogger
|
4
|
+
module ActiveRecord
|
5
|
+
# Include this mixin and use `logs_operations_for` to add an
|
6
|
+
# `after_commit` callback that will log any database activity on this model to Kafka.
|
7
|
+
# The topic used is `{resource_name}_operations_log`.
|
8
|
+
module Helper
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# Helper to set up resource operations logging
|
13
|
+
# @param resource_class string or class (string allowed to circumvent loading order issues)
|
14
|
+
def logs_operations_with(resource_class:, api_client_class:)
|
15
|
+
@resource_class = resource_class
|
16
|
+
@api_client_class = api_client_class
|
17
|
+
after_commit :log_to_operations_log
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the resource class set in .logs_operations_for
|
21
|
+
# Checks base_class as well to work in STI models (subclasses)
|
22
|
+
def resource_class
|
23
|
+
@resource_class ||= base_class.resource_class
|
24
|
+
@resource_class.is_a?(String) ? @resource_class = @resource_class.constantize : @resource_class
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the api client class set in .logs_operations_for
|
28
|
+
# Checks base_class as well to work in STI models (subclasses)
|
29
|
+
def api_client_class
|
30
|
+
@api_client_class ||= base_class.api_client_class
|
31
|
+
@api_client_class.is_a?(String) ? @api_client_class = @api_client_class.constantize : @api_client_class
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return valid attributes for JSONAPI resource i.e. ['id', 'name']
|
35
|
+
def resource_attributes
|
36
|
+
return @resource_attributes if @resource_attributes
|
37
|
+
|
38
|
+
attrs = resource_class.instance_variable_get(:@_attributes)
|
39
|
+
raise "Resource: #{resource_class} has no attributes!" unless attrs.is_a?(Hash)
|
40
|
+
|
41
|
+
@resource_attributes = resource_class.instance_variable_get(:@_attributes).stringify_keys.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
# Gets relationships from JSONAPI Resource
|
45
|
+
# @return hash keyed on relationship foreign key
|
46
|
+
# For example: { 'contact_type_id' => { name: 'contact_type', type: 'contact_types' } }
|
47
|
+
def resource_relationships
|
48
|
+
@resource_relationships ||= if resource_class.instance_variable_get(:@_relationships).nil?
|
49
|
+
{}
|
50
|
+
else
|
51
|
+
resource_class.instance_variable_get(:@_relationships)
|
52
|
+
.each_with_object({}) do |(k, v), o|
|
53
|
+
o[v.foreign_key] = { name: k, type: v.type.to_s }
|
54
|
+
end.stringify_keys
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Additional options to write into kafka message along with json resource
|
59
|
+
def async_options
|
60
|
+
{ context: FrederickOperationsLogger.context }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private def attributes_for_resource
|
65
|
+
attributes.slice(*included_attributes)
|
66
|
+
end
|
67
|
+
|
68
|
+
private def included_attributes
|
69
|
+
# Online log updated attributes and relationships on updates
|
70
|
+
return ((self.class.resource_attributes & previous_changes.keys) + %w[location_id id]) if log_action == :update
|
71
|
+
self.class.resource_attributes
|
72
|
+
end
|
73
|
+
|
74
|
+
private def add_relationships_for_log!(api_resource)
|
75
|
+
self.class.resource_relationships.each do |k, v|
|
76
|
+
if attributes[k].present?
|
77
|
+
api_resource.relationships.send(:"#{v[:name]}=", data: { type: v[:type], id: attributes[k] })
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private def log_to_operations_log
|
83
|
+
additional_attr = {}
|
84
|
+
resource = self.class.api_client_class.new(attributes_for_resource)
|
85
|
+
add_relationships_for_log!(resource)
|
86
|
+
|
87
|
+
case log_action
|
88
|
+
when :destroy
|
89
|
+
return resource.async_log.destroy(self.class.async_options)
|
90
|
+
when :update
|
91
|
+
additional_attr = add_previous_changes
|
92
|
+
resource.mark_as_persisted!
|
93
|
+
end
|
94
|
+
|
95
|
+
resource.async_log.save(self.class.async_options.merge(additional_attr))
|
96
|
+
end
|
97
|
+
|
98
|
+
# resource: { previous_attributes: { location_group_id: 'foo' } }
|
99
|
+
private def add_previous_changes
|
100
|
+
previous_changes.each_with_object({}) do |(k, v), h|
|
101
|
+
h[k.to_sym] = v.first
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private def log_action
|
106
|
+
if previous_changes.key?('id')
|
107
|
+
:create
|
108
|
+
elsif destroyed?
|
109
|
+
:destroy
|
110
|
+
else
|
111
|
+
:update
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'logs_operations_with' do |meta|
|
4
|
+
resource_class = meta[:resource_class]
|
5
|
+
api_client_class = meta[:api_client_class]
|
6
|
+
|
7
|
+
describe 'logs_operations_with' do
|
8
|
+
let(:instance) { subject }
|
9
|
+
|
10
|
+
before do
|
11
|
+
allow(instance).to receive(:log_to_operations_log)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'after_commit' do
|
15
|
+
instance.run_callbacks(:commit)
|
16
|
+
expect(instance).to have_received(:log_to_operations_log)
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'resource_class' do
|
20
|
+
it resource_class.to_s do
|
21
|
+
expect(described_class.resource_class).to be resource_class
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'api_client_class' do
|
26
|
+
it api_client_class.to_s do
|
27
|
+
expect(described_class.api_client_class).to be api_client_class
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frederick_operations_logger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frederick Engineering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jsonapi-resources
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: request_store
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.4'
|
41
|
+
description: Provides Rails helper for ActiveRecord Models to log operations for JSON
|
42
|
+
API Resources
|
43
|
+
email:
|
44
|
+
- tech@hirefrederick.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- README.md
|
50
|
+
- lib/frederick_operations_logger.rb
|
51
|
+
- lib/frederick_operations_logger/active_record/helper.rb
|
52
|
+
- lib/frederick_operations_logger/test/shared_examples.rb
|
53
|
+
- lib/frederick_operations_logger/version.rb
|
54
|
+
homepage: https://github.com/BookerSoftwareInc/frederick_operations_logger
|
55
|
+
licenses: []
|
56
|
+
metadata: {}
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 2.6.14
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Frederick Internal Operations Logger
|
77
|
+
test_files: []
|