stenotype 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.configure do
4
+ #
5
+ # Stenotype allows you to emit events which are then being published to a list of
6
+ # targets. Currently two targets are provided by default: Google Cloud Pub Sub and
7
+ # STDOUT for debug purposes.
8
+ #
9
+ # There is also a rails integration module which defines a handy DSL
10
+ # for triggering events in various Rails components. ActionController and
11
+ # ActiveJob are supported at the time of writing.
12
+ #
13
+ # Both extensions for Rails are enabled by default, but there are config options
14
+ # to control their presence.
15
+ # config.stenotype.rails do |rails_modules|
16
+ # rails_modules.enable_action_controller_ext = true
17
+ # rails_modules.enable_active_job_ext = true
18
+ # end
19
+ #
20
+ #
21
+ # To make publishing possible one must specify a list of targets. You could use
22
+ # StdoutAdapter for debug purposes before switching to a production publisher.
23
+ # By default the list of targets is empty and you'll get an error saying
24
+ # that no targets are specified.
25
+ #
26
+ # A config option is available for setting up the targets:
27
+ #
28
+ # config.stenotype.targets = [Stenotype::Adapters::StdoutAdapter.new]
29
+ #
30
+ # Or using Google Cloud:
31
+ #
32
+ # config.stenotype.targets = [Stenotype::Adapters::GoogleCloud.new]
33
+ #
34
+ # Or both:
35
+ #
36
+ # config.stenotype.targets = [
37
+ # Stenotype::Adapters::StdoutAdapter.new,
38
+ # Stenotype::Adapters::GoogleCloud.new
39
+ # ]
40
+ #
41
+ # To be able to use Google Cloud one has to specify Google Cloud credentials:
42
+ #
43
+ # config.stenotype.google_cloud do |gc_config|
44
+ # gc_config.credentials = "SPECIFY YOUR CREDENTIALS" # path/to/key.json
45
+ # gc_config.project_id = "SPECIFY YOUR PROJECT ID"
46
+ # gc_config.topic = "SPECIFY YOUR TOPIC"
47
+ # gc_config.mode = :async
48
+ # end
49
+ #
50
+ # Each event is shipped with a UUID generated with SecureRandom by default.
51
+ # This might be changed by using a corresponding config option. Note that
52
+ # uuid_generator expects method #uuid to be implemented
53
+ #
54
+ # config.stenotype.uuid_generator = SecureRandom
55
+ #
56
+ # In rare cases you might want to get control over how the event is being dispatched.
57
+ # Dispatcher must implement instance method #publish.
58
+ # Which dispatcher to use is controlled by the following config option:
59
+ #
60
+ # config.stenotype.dispatcher = Stenotype::Dispatcher
61
+ #
62
+ end
63
+
64
+ # For more usage instructions please refer to either README.md or yard documentation
65
+ # in gem repository https://github.com/Freshly/stenotype
@@ -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
- # @return {Stenotype::Configuration}
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/exceptions"
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
- config.targets = [
60
- Stenotype::Adapters::StdoutAdapter.new,
61
- Stenotype::Adapters::GoogleCloud.new
62
- ]
63
-
64
- Stenotype::ContextHandlers.module_eval do
65
- register Stenotype::ContextHandlers::Klass
66
- end
67
-
68
- Object.send(:include, Stenotype::Frameworks::ObjectExt)
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
- if defined?(Rails)
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
- ActiveSupport.on_load(:action_controller) do
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'stenotype/adapters/base'
4
- require 'stenotype/adapters/google_cloud'
5
- require 'stenotype/adapters/stdout_adapter'
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
@@ -1,55 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'google/cloud/pubsub'
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
11
37
  #
12
38
  # @param event_data {Hash} The data to be published to Google Cloud
13
- # @raise {Stenotype::Exceptions::GoogleCloudUnsupportedMode} unless the mode
14
- # in configured to be :sync or :async
15
- # @raise {Stenotype::Exceptions::MessageNotPublished} unless message is published
39
+ # @raise {Stenotype::MessageNotPublishedError} unless message is published
40
+ #
41
+ # @example With default client
42
+ # google_cloud_adapter = Stenotype::Adapters::GoogleCloud.new
43
+ # # publishes to default client
44
+ # google_cloud_adapter.publish({ event: :data }, { additional: :data })
16
45
  #
17
- # rubocop:disable Metrics/MethodLength
46
+ # @example With client override
47
+ # google_cloud_adapter = Stenotype::Adapters::GoogleCloud.new(CustomGCClient.new)
48
+ # # publishes to default CustomGCClient
49
+ # google_cloud_adapter.publish({ event: :data }, { additional: :data })
18
50
  #
19
51
  def publish(event_data, **additional_arguments)
20
- case config.gc_mode
21
- when :async
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)
52
+ if config.async
53
+ publish_async(event_data, **additional_arguments)
27
54
  else
28
- raise Stenotype::Exceptions::GoogleCloudUnsupportedMode,
29
- 'Please use either :sync or :async modes for publishing the events.'
55
+ publish_sync(event_data, **additional_arguments)
30
56
  end
31
57
  end
32
- # rubocop:enable Metrics/MethodLength
33
58
 
34
59
  private
35
60
 
61
+ def publish_sync(event_data, **additional_arguments)
62
+ topic.publish(event_data, additional_arguments)
63
+ end
64
+
65
+ def publish_async(event_data, **additional_arguments)
66
+ topic.publish_async(event_data, additional_arguments) do |result|
67
+ raise Stenotype::MessageNotPublishedError unless result.succeeded?
68
+ end
69
+ end
70
+
36
71
  # :nocov:
37
72
  def client
38
- @client ||= Google::Cloud::PubSub.new(
39
- project_id: config.gc_project_id,
40
- credentials: config.gc_credentials
41
- )
73
+ @client ||= Google::Cloud::PubSub.new(project_id: config.project_id, credentials: config.credentials)
42
74
  end
43
75
 
44
76
  # Use memoization, otherwise a new topic will be created
45
77
  # every time. And a new async_publisher will be created.
46
78
  # :nocov:
47
79
  def topic
48
- @topic ||= client.topic config.gc_topic
80
+ @topic ||= client.topic config.topic
49
81
  end
50
82
 
51
83
  def config
52
- Stenotype.config
84
+ Stenotype.config.google_cloud
53
85
  end
54
86
  end
55
87
  end
@@ -5,12 +5,34 @@ module Stenotype
5
5
  #
6
6
  # An adapter implementing method {#publish} to send data to STDOUT
7
7
  #
8
+ # @example
9
+ # class SomeClassWithEvents
10
+ # def method_emitting_enent
11
+ # result_of_calculations = collect_some_data
12
+ # # This will print the data to STDOUT by default
13
+ # stdout_adapter.publish(result_of_calculation, additional: :data, more: :data)
14
+ # result_of_calculations
15
+ # end
16
+ #
17
+ # def stdout_adapter
18
+ # Stenotype::Adapters::StdoutAdapter.new
19
+ # end
20
+ # end
21
+ #
8
22
  class StdoutAdapter < Base
9
23
  #
10
24
  # @param event_data {Hash} The data to be published to STDOUT
11
25
  #
26
+ # @example Publishing to default client (STDOUT)
27
+ # adapter = Stenotype::Adapters::StdoutAdapter.new
28
+ # adapter.publish({ event: :data }, { additional: :data })
29
+ #
30
+ # @example Publishing to custom client (STDERR)
31
+ # adapter = Stenotype::Adapters::StdoutAdapter.new(client: STDERR)
32
+ # adapter.publish({ event: :data }, { additional: :data })
33
+ #
12
34
  def publish(event_data, **additional_arguments)
13
- client.info(event_data, **additional_arguments)
35
+ client.info(**event_data, **additional_arguments)
14
36
  end
15
37
 
16
38
  private
@@ -4,46 +4,101 @@ module Stenotype
4
4
  #
5
5
  # A module containing freshly-event gem configuration
6
6
  #
7
+ # @example Configuring the library
8
+ # Stenotype.configure do |config|
9
+ # config.targets = [Target1.new, Target2.new]
10
+ # config.uuid_generator = SecureRandom
11
+ #
12
+ # config.google_cloud do |gc|
13
+ # gc.credentials = "abc"
14
+ # gc.project_id = "project"
15
+ # gc.topic = "42"
16
+ # gc.async = true
17
+ # end
18
+ #
19
+ # config.rails do |rc|
20
+ # rc.enable_action_controller_ext = true
21
+ # rc.enable_active_job_ext = false
22
+ # end
23
+ # end
24
+ #
7
25
  module Configuration
8
- class << self
9
- # @return {Array<#publish>} a list of targets responding to method [#publish]
10
- attr_writer :targets
11
- # @return {Sting} a string with GC API credential. Refer to GC PubSub documentation
12
- attr_accessor :gc_credentials
13
- # @return {String} a name of the project in GC PubSub
14
- attr_accessor :gc_project_id
15
- # @return {String} a name of the topic in GC PubSub
16
- attr_accessor :gc_topic
17
- # @return [:sync, :async] GC publish mode
18
- attr_accessor :gc_mode
19
- # @return {#publish} as object responding to method [#publish]
20
- attr_accessor :dispatcher
21
- # @return {#uuid} an object responding to method [#uuid]
22
- attr_accessor :uuid_generator
23
-
24
- #
25
- # Yields control to the caller
26
- # @yield {Stenotype::Configuration}
27
- # @return {Stenotype::Configuration}
28
- #
29
- def configure
30
- yield self
31
- self
26
+ extend Spicerack::Configurable
27
+
28
+ # @!attribute targets
29
+ # @return {Array<#publish>} a list of targets responding to method [#publish]
30
+
31
+ # @!attribute [rw] dispatcher
32
+ # @return {#publish} as object responding to method [#publish]
33
+
34
+ # @!attribute [rw] uuid_generator
35
+ # @return {#uuid} an object responding to method [#uuid]
36
+
37
+ # @!attribute [rw] google_cloud
38
+ # @return [NestedConfiguration] google cloud configuration.
39
+
40
+ # @!attribute [rw] google_cloud.credentials
41
+ # @return {String} a string with GC API credential. Refer to GC PubSub documentation
42
+
43
+ # @!attribute [rw] google_cloud.project_id
44
+ # @return {String} a name of the project in GC PubSub
45
+
46
+ # @!attribute [rw] google_cloud.topic
47
+ # @return {String} a name of the topic in GC PubSub
48
+
49
+ # @!attribute [rw] google_cloud.async
50
+ # @return [true, false] GC publish mode, either async if true, sync if false
51
+
52
+
53
+ # @!attribute [rw] rails
54
+ # @return [NestedConfiguration] Rails configuration.
55
+
56
+ # @!attribute [rw] rails.enable_action_controller_ext
57
+ # @return [true, false] A flag of whether ActionController ext is enabled
58
+
59
+ # @!attribute [rw] rails.enable_active_job_ext
60
+ # @return [true, false] A flag of whether ActiveJob ext is enabled
61
+
62
+ configuration_options do
63
+ option :targets, default: []
64
+ option :dispatcher, default: Stenotype::Dispatcher
65
+ option :uuid_generator, default: SecureRandom
66
+
67
+ nested :google_cloud do
68
+ option :credentials, default: nil
69
+ option :project_id, default: nil
70
+ option :topic, default: nil
71
+ option :async, default: true
32
72
  end
33
73
 
34
- #
35
- # @raise {Stenotype::Exceptions::NoTargetsSpecified} in case no targets are configured
36
- # @return {Array<#publish>} An array of targets implementing method [#publish]
37
- #
38
- def targets
39
- if @targets.nil? || @targets.empty?
40
- raise Stenotype::Exceptions::NoTargetsSpecified,
41
- 'Please configure a target(s) for events to be sent to. ' \
42
- 'See Stenotype::Configuration for reference.'
43
- else
44
- @targets
45
- end
74
+ nested :rails do
75
+ option :enable_action_controller_ext, default: true
76
+ option :enable_active_job_ext, default: true
46
77
  end
47
78
  end
79
+
80
+ module_function
81
+
82
+ #
83
+ # @example When at least one target is present
84
+ # Stenotype.configure do |config|
85
+ # config.targets = [Target1.new, Target2.new]
86
+ # end
87
+ # Stenotype.config.targets #=> [target_obj1, target_obj2]
88
+ #
89
+ # @example When no targets have been configured
90
+ # Stenotype.configure { |config| config.targets = [] }
91
+ # Stenotype.config.targets #=> Stenotype::NoTargetsSpecifiedError
92
+ #
93
+ # @raise {Stenotype::NoTargetsSpecifiedError} in case no targets are configured
94
+ # @return {Array<#publish>} An array of targets implementing method [#publish]
95
+ #
96
+ def targets
97
+ return config.targets unless config.targets.empty?
98
+
99
+ raise Stenotype::NoTargetsSpecifiedError,
100
+ "Please configure a target(s) for events to be sent to. "\
101
+ "See #{Stenotype::Configuration} for reference."
102
+ end
48
103
  end
49
104
  end