stenotype 0.1.0 → 0.1.6
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/.gitignore +3 -0
- data/.rubocop.yml +3 -2
- data/CHANGELOG.md +43 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +111 -60
- data/README.md +48 -17
- data/Rakefile +2 -2
- data/TODO.md +18 -0
- data/bin/console +3 -3
- data/lib/generators/USAGE +8 -0
- data/lib/generators/stenotype/initializer/initializer_generator.rb +23 -0
- data/lib/generators/stenotype/initializer/templates/initializer.rb.erb +82 -0
- data/lib/stenotype.rb +24 -85
- data/lib/stenotype/adapters.rb +3 -3
- data/lib/stenotype/adapters/base.rb +25 -2
- data/lib/stenotype/adapters/google_cloud.rb +70 -22
- data/lib/stenotype/adapters/stdout_adapter.rb +36 -2
- data/lib/stenotype/at_exit.rb +8 -0
- data/lib/stenotype/configuration.rb +127 -36
- data/lib/stenotype/context_handlers.rb +6 -8
- data/lib/stenotype/context_handlers/base.rb +14 -3
- data/lib/stenotype/context_handlers/collection.rb +78 -34
- data/lib/stenotype/context_handlers/rails/active_job.rb +3 -11
- data/lib/stenotype/context_handlers/rails/controller.rb +10 -11
- data/lib/stenotype/dispatcher.rb +1 -2
- data/lib/stenotype/emitter.rb +166 -0
- data/lib/stenotype/event.rb +48 -25
- data/lib/stenotype/event_serializer.rb +31 -11
- data/lib/stenotype/frameworks/rails/action_controller.rb +44 -21
- data/lib/stenotype/frameworks/rails/active_job.rb +3 -5
- data/lib/stenotype/railtie.rb +37 -0
- data/lib/stenotype/version.rb +1 -1
- data/stenotype.gemspec +30 -26
- metadata +70 -19
- data/lib/stenotype/exceptions.rb +0 -31
- data/lib/stenotype/frameworks/object_ext.rb +0 -145
data/Rakefile
CHANGED
data/TODO.md
CHANGED
@@ -15,3 +15,21 @@
|
|
15
15
|
- [ ] Figure out the params for plain ruby class context handler.
|
16
16
|
- [ ] Consider `ContextHandlers::ActiveJob` params. How to deal with \_args? It won't necessarily respond to `#as_json`.
|
17
17
|
- [ ] Consider a way to switch from evaluating ruby code to using plain modules extension.
|
18
|
+
|
19
|
+
Feedback TODO:
|
20
|
+
|
21
|
+
- [x] Move all exceptions into root module Stenotype. **Moved exceptions into root module**
|
22
|
+
- [x] Inherit gem specific error from a root error object specific for the gem. **All gem specific error inherit from Stenotype::Errors**
|
23
|
+
- [x] Inherit the root error from standard error. **Stenotype::Errors inherits from StandardError**
|
24
|
+
- [x] Utilize concerns and allow gem users to extend the code they need with a concern rather than aggressively extend Object. **Added an Stenotype::Emitter module instead of aggressively including it into Object**
|
25
|
+
- [x] Use Railtie to introduce rails specific logic instead of checking whether Rails is defined in the root module. **Added a Railtie, active only in Rails world**
|
26
|
+
- [?] Use delegation instead of defining tiny methods. Do not forget about Demeter's. **Rails `delegate` method is used in Rails specific extensions. Otherwise simple methods are used**
|
27
|
+
- [x] Add more examples to yard documentation, consider collecting README.md from the yard doc. **Covered most of the classes/modules with yard examples**
|
28
|
+
- [x] Enable on-off triggers in the configuration to enable/disable framework specific features. Like whether we want to extend ActiveJob or not. **Added two configuration options for currently implemented Rails extensions**
|
29
|
+
- [x] Memoization! **Not actually needed**
|
30
|
+
- [ ] Consider potential double wrapping in the meta-programming stuff. Take a look at around-the-world and consider switching to using it.
|
31
|
+
- [ ] Utilize input objects for attr_readers (what was the name of the tool?)
|
32
|
+
- [ ] Consider configurable for configuration instead of implementing custom configuration object
|
33
|
+
- [ ] Consider using collectible gem to handle collection of context handlers
|
34
|
+
- [ ] Consider naming (e.g. #as_json => #to_h), try to be more specific to not pollute the namespace or introduce any ambiguity.
|
35
|
+
- [ ] Remove freshly mentions from the gem.
|
data/bin/console
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "stenotype"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -11,5 +11,5 @@ require 'stenotype'
|
|
11
11
|
# require "pry"
|
12
12
|
# Pry.start
|
13
13
|
|
14
|
-
require
|
14
|
+
require "pry"
|
15
15
|
Pry.start(__FILE__)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stenotype
|
4
|
+
#
|
5
|
+
# A module enclosing Rails generators for gem setup
|
6
|
+
#
|
7
|
+
module Generators
|
8
|
+
#
|
9
|
+
# A class for generating a Rails initializer to setup the gem
|
10
|
+
# upon rails application load.
|
11
|
+
#
|
12
|
+
class InitializerGenerator < Rails::Generators::Base
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
14
|
+
|
15
|
+
#
|
16
|
+
# Creates an initializer for rails application
|
17
|
+
#
|
18
|
+
def create_initializer
|
19
|
+
template "initializer.rb.erb", File.join("config/initializers/stenotype.rb")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rails.application.configure do
|
4
|
+
Stenotype.configure do |config|
|
5
|
+
#
|
6
|
+
# Stenotype allows you to emit events which are then being published to a list of
|
7
|
+
# targets. Currently two targets are provided by default: Google Cloud Pub Sub and
|
8
|
+
# STDOUT for debug purposes.
|
9
|
+
#
|
10
|
+
# There is also a rails integration module which defines a handy DSL
|
11
|
+
# for triggering events in various Rails components. ActionController and
|
12
|
+
# ActiveJob are supported at the time of writing.
|
13
|
+
#
|
14
|
+
# Both extensions for Rails are enabled by default, but there are config options
|
15
|
+
# to control their presence.
|
16
|
+
# config.rails do |rails_modules|
|
17
|
+
# rails_modules.enable_action_controller_ext = true
|
18
|
+
# rails_modules.enable_active_job_ext = true
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# To enable or disable the library use the following config option:
|
22
|
+
#
|
23
|
+
# config.enabled = true # or false
|
24
|
+
#
|
25
|
+
# To make publishing possible one must specify a list of targets. You could use
|
26
|
+
# StdoutAdapter for debug purposes before switching to a production publisher.
|
27
|
+
# By default the list of targets is empty and you'll get an error saying
|
28
|
+
# that no targets are specified.
|
29
|
+
#
|
30
|
+
# A config option is available for setting up the targets:
|
31
|
+
#
|
32
|
+
# config.targets = [Stenotype::Adapters::StdoutAdapter.new]
|
33
|
+
#
|
34
|
+
# Or using Google Cloud:
|
35
|
+
#
|
36
|
+
# config.targets = [Stenotype::Adapters::GoogleCloud.new]
|
37
|
+
#
|
38
|
+
# Or both:
|
39
|
+
#
|
40
|
+
# config.targets = [
|
41
|
+
# Stenotype::Adapters::StdoutAdapter.new,
|
42
|
+
# Stenotype::Adapters::GoogleCloud.new
|
43
|
+
# ]
|
44
|
+
#
|
45
|
+
# To be able to use Google Cloud one has to specify Google Cloud credentials:
|
46
|
+
#
|
47
|
+
# config.google_cloud do |gc_config|
|
48
|
+
# gc_config.credentials = "SPECIFY YOUR CREDENTIALS" # path/to/key.json
|
49
|
+
# gc_config.project_id = "SPECIFY YOUR PROJECT ID"
|
50
|
+
# gc_config.topic = "SPECIFY YOUR TOPIC"
|
51
|
+
# gc_config.async = true
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Each event is shipped with a UUID generated with SecureRandom by default.
|
55
|
+
# This might be changed by using a corresponding config option. Note that
|
56
|
+
# uuid_generator expects method #uuid to be implemented
|
57
|
+
#
|
58
|
+
# config.uuid_generator = SecureRandom
|
59
|
+
#
|
60
|
+
# In rare cases you might want to get control over how the event is being dispatched.
|
61
|
+
# Dispatcher must implement instance method #publish.
|
62
|
+
# Which dispatcher to use is controlled by the following config option:
|
63
|
+
#
|
64
|
+
# config.dispatcher = Stenotype::Dispatcher
|
65
|
+
#
|
66
|
+
# An option to suppress exception within a gem is available:
|
67
|
+
#
|
68
|
+
# config.graceful_error_handling = true
|
69
|
+
#
|
70
|
+
# To log errors a logger config option is available. Logger.new(STDOUT) is used by default.
|
71
|
+
#
|
72
|
+
# config.logger = Logger.new(STDOUT)
|
73
|
+
#
|
74
|
+
# Add your own context handlers
|
75
|
+
#
|
76
|
+
# Stenotype::ContextHandlers.register Your::Custom::HandlerClass
|
77
|
+
#
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# For more usage instructions please refer to either README.md or yard documentation
|
82
|
+
# in gem repository https://github.com/Freshly/stenotype
|
data/lib/stenotype.rb
CHANGED
@@ -1,98 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
# A top level namespace for the freshly-events gem
|
5
|
-
#
|
6
|
-
module Stenotype
|
7
|
-
class << self
|
8
|
-
##
|
9
|
-
# Configures the library.
|
10
|
-
# @yield {Stenotype::Configuration}
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
#
|
14
|
-
# Stenotype.configure do |config|
|
15
|
-
# config.targets = [
|
16
|
-
# Stenotype::Adapters::StdoutAdapter.new,
|
17
|
-
# Stenotype::Adapters::GoogleCloud.new
|
18
|
-
# ]
|
19
|
-
#
|
20
|
-
# config.dispatcher = Stenotype::Dispatcher.new
|
21
|
-
# config.gc_project_id = ENV['GC_PROJECT_ID']
|
22
|
-
# config.gc_credentials = ENV['GC_CREDENTIALS']
|
23
|
-
# config.gc_topic = ENV['GC_TOPIC']
|
24
|
-
# config.gc_mode = :async
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
def configure(&block)
|
28
|
-
Stenotype::Configuration.configure(&block)
|
29
|
-
end
|
3
|
+
require "securerandom"
|
30
4
|
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
def config
|
35
|
-
Stenotype::Configuration
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
5
|
+
require "spicery"
|
6
|
+
require "stenotype/version"
|
39
7
|
|
40
8
|
require "stenotype/adapters"
|
9
|
+
require "stenotype/dispatcher"
|
41
10
|
require "stenotype/configuration"
|
42
11
|
require "stenotype/context_handlers"
|
43
|
-
require "stenotype/dispatcher"
|
44
12
|
require "stenotype/event"
|
45
13
|
require "stenotype/event_serializer"
|
46
|
-
require "stenotype/
|
47
|
-
require "stenotype/version"
|
48
|
-
require "stenotype/frameworks/object_ext"
|
49
|
-
|
50
|
-
Stenotype.configure do |config|
|
51
|
-
config.uuid_generator = SecureRandom
|
52
|
-
config.dispatcher = Stenotype::Dispatcher.new
|
53
|
-
|
54
|
-
config.gc_project_id = ENV['GC_PROJECT_ID']
|
55
|
-
config.gc_credentials = ENV['GC_CREDENTIALS']
|
56
|
-
config.gc_topic = ENV['GC_TOPIC']
|
57
|
-
config.gc_mode = :async
|
14
|
+
require "stenotype/emitter"
|
58
15
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
|
16
|
+
#
|
17
|
+
# A top level namespace for the freshly-events gem
|
18
|
+
#
|
19
|
+
module Stenotype
|
20
|
+
# A wrapper class for Stenotype specific errors
|
21
|
+
class Error < StandardError; end
|
22
|
+
# This exception is being raised upon unsuccessful publishing of an event.
|
23
|
+
class MessageNotPublishedError < Error; end
|
24
|
+
# This exception is being raised in case no targets are
|
25
|
+
# specified {Stenotype::Configuration}.
|
26
|
+
class NoTargetsSpecifiedError < Error; end
|
27
|
+
# This exception is being raised upon using a context handler which
|
28
|
+
# has never been registered in known handlers in {Stenotype::ContextHandlers::Collection}.
|
29
|
+
class UnknownHandlerError < Error; end
|
30
|
+
|
31
|
+
include Spicerack::Configurable::ConfigDelegation
|
32
|
+
delegates_to_configuration
|
69
33
|
end
|
70
34
|
|
71
|
-
|
72
|
-
require "stenotype/frameworks/rails/action_controller"
|
73
|
-
require "stenotype/frameworks/rails/active_job"
|
74
|
-
|
75
|
-
module Stenotype
|
76
|
-
class Railtie < Rails::Railtie # :nodoc:
|
77
|
-
config.stenotype = Stenotype.config
|
78
|
-
|
79
|
-
#
|
80
|
-
# Register Rails handlers
|
81
|
-
#
|
82
|
-
Stenotype::ContextHandlers.module_eval do
|
83
|
-
register Stenotype::ContextHandlers::Rails::Controller
|
84
|
-
register Stenotype::ContextHandlers::Rails::ActiveJob
|
85
|
-
end
|
35
|
+
Stenotype::ContextHandlers.register(Stenotype::ContextHandlers::Klass)
|
86
36
|
|
87
|
-
|
88
|
-
include Stenotype::Frameworks::Rails::ActionControllerExtension
|
89
|
-
end
|
90
|
-
|
91
|
-
ActiveSupport.on_load(:active_job) do
|
92
|
-
# @todo: consider using `::ActiveJob::Base.around_perform`
|
93
|
-
# or `::ActiveJob::Base.around_enqueue`
|
94
|
-
extend Stenotype::Frameworks::Rails::ActiveJobExtension
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
37
|
+
require "stenotype/railtie" if defined?(Rails)
|
data/lib/stenotype/adapters.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "stenotype/adapters/base"
|
4
|
+
require "stenotype/adapters/google_cloud"
|
5
|
+
require "stenotype/adapters/stdout_adapter"
|
@@ -11,6 +11,16 @@ module Stenotype
|
|
11
11
|
# An abstract base class for implementing adapters
|
12
12
|
#
|
13
13
|
# @abstract
|
14
|
+
# @example Defining a custom adapter
|
15
|
+
# MyCustomAdapter < Stenotype::Adapters::Base
|
16
|
+
# def publish(event_data, **additional_arguments)
|
17
|
+
# client.publish(event_data, **additional_arguments)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def client
|
21
|
+
# @client ||= SomeCustomClient.new(some_credential)
|
22
|
+
# end
|
23
|
+
# end
|
14
24
|
#
|
15
25
|
class Base
|
16
26
|
attr_reader :client
|
@@ -25,12 +35,25 @@ module Stenotype
|
|
25
35
|
#
|
26
36
|
# This method is expected to be implemented by subclasses
|
27
37
|
# @abstract
|
28
|
-
# @raise
|
38
|
+
# @raise {NotImplementedError} unless implemented in a subclass
|
29
39
|
#
|
30
|
-
def publish(_event_data, **
|
40
|
+
def publish(_event_data, **_additional_attrs)
|
31
41
|
raise NotImplementedError,
|
32
42
|
"#{self.class.name} must implement method #publish"
|
33
43
|
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# This method is expected to be implemented by subclasses. In case async
|
47
|
+
# publisher is used the process might end before the async queue of
|
48
|
+
# messages is processed, so this method is going to be used in a
|
49
|
+
# `at_exit` hook to flush the queue.
|
50
|
+
# @abstract
|
51
|
+
# @raise {NotImplementedError} unless implemented in a subclass
|
52
|
+
#
|
53
|
+
def flush!
|
54
|
+
raise NotImplementedError,
|
55
|
+
"#{self.class.name} must implement method #flush"
|
56
|
+
end
|
34
57
|
end
|
35
58
|
end
|
36
59
|
end
|
@@ -1,55 +1,103 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "google/cloud/pubsub"
|
4
4
|
|
5
5
|
module Stenotype
|
6
6
|
module Adapters
|
7
7
|
#
|
8
8
|
# An adapter implementing method {#publish} to send data to Google Cloud PubSub
|
9
9
|
#
|
10
|
+
# @example A general usage within some method in the class
|
11
|
+
# class EventEmittingClass
|
12
|
+
# def method_emitting_enent
|
13
|
+
# result_of_calculations = collect_some_data
|
14
|
+
# gc_adapter.publish(result_of_calculation, additional: :data, more: :data)
|
15
|
+
# result_of_calculations
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def gc_adapter
|
19
|
+
# Stenotype::Adapters::GoogleCloud.new
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Overriding a client
|
24
|
+
# class EventEmittingClass
|
25
|
+
# def method_emitting_enent
|
26
|
+
# result_of_calculations = collect_some_data
|
27
|
+
# gc_adapter.publish(result_of_calculation, additional: :data, more: :data)
|
28
|
+
# result_of_calculations
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def gc_adapter
|
32
|
+
# Stenotype::Adapters::GoogleCloud.new(client: CustomGcClient.new)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
10
36
|
class GoogleCloud < Base
|
37
|
+
attr_reader :topic
|
38
|
+
|
39
|
+
def initialize(client: nil, topic: nil)
|
40
|
+
super(client: client)
|
41
|
+
@topic = topic
|
42
|
+
end
|
11
43
|
#
|
12
44
|
# @param event_data {Hash} The data to be published to Google Cloud
|
13
|
-
# @raise {Stenotype::
|
14
|
-
#
|
15
|
-
# @
|
45
|
+
# @raise {Stenotype::MessageNotPublishedError} unless message is published
|
46
|
+
#
|
47
|
+
# @example With default client
|
48
|
+
# google_cloud_adapter = Stenotype::Adapters::GoogleCloud.new
|
49
|
+
# # publishes to default client
|
50
|
+
# google_cloud_adapter.publish({ event: :data }, { additional: :data })
|
16
51
|
#
|
17
|
-
#
|
52
|
+
# @example With client override
|
53
|
+
# google_cloud_adapter = Stenotype::Adapters::GoogleCloud.new(CustomGCClient.new)
|
54
|
+
# # publishes to default CustomGCClient
|
55
|
+
# google_cloud_adapter.publish({ event: :data }, { additional: :data })
|
18
56
|
#
|
19
|
-
def publish(event_data, **
|
20
|
-
|
21
|
-
|
22
|
-
topic.publish_async(event_data, additional_arguments) do |result|
|
23
|
-
raise Stenotype::Exceptions::MessageNotPublished unless result.succeeded?
|
24
|
-
end
|
25
|
-
when :sync
|
26
|
-
topic.publish(event_data, additional_arguments)
|
57
|
+
def publish(event_data, **additional_attrs)
|
58
|
+
if config.async
|
59
|
+
publish_async(event_data, **additional_attrs)
|
27
60
|
else
|
28
|
-
|
29
|
-
'Please use either :sync or :async modes for publishing the events.'
|
61
|
+
publish_sync(event_data, **additional_attrs)
|
30
62
|
end
|
31
63
|
end
|
32
|
-
|
64
|
+
|
65
|
+
#
|
66
|
+
# Flushes the topic's async queue
|
67
|
+
#
|
68
|
+
def flush!
|
69
|
+
# a publisher might be uninitialized until the first event is published
|
70
|
+
return unless topic.async_publisher
|
71
|
+
|
72
|
+
topic.async_publisher.stop.wait!
|
73
|
+
end
|
33
74
|
|
34
75
|
private
|
35
76
|
|
77
|
+
def publish_sync(event_data, **additional_attrs)
|
78
|
+
topic.publish(event_data, additional_attrs)
|
79
|
+
end
|
80
|
+
|
81
|
+
def publish_async(event_data, **additional_attrs)
|
82
|
+
topic.publish_async(event_data, additional_attrs) do |result|
|
83
|
+
raise Stenotype::MessageNotPublishedError unless result.succeeded?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
36
87
|
# :nocov:
|
37
88
|
def client
|
38
|
-
@client ||= Google::Cloud::PubSub.new(
|
39
|
-
project_id: config.gc_project_id,
|
40
|
-
credentials: config.gc_credentials
|
41
|
-
)
|
89
|
+
@client ||= Google::Cloud::PubSub.new(project_id: config.project_id, credentials: config.credentials)
|
42
90
|
end
|
43
91
|
|
44
92
|
# Use memoization, otherwise a new topic will be created
|
45
93
|
# every time. And a new async_publisher will be created.
|
46
94
|
# :nocov:
|
47
95
|
def topic
|
48
|
-
@topic ||= client.topic config.
|
96
|
+
@topic ||= client.topic config.topic
|
49
97
|
end
|
50
98
|
|
51
99
|
def config
|
52
|
-
Stenotype.config
|
100
|
+
Stenotype.config.google_cloud
|
53
101
|
end
|
54
102
|
end
|
55
103
|
end
|