stenotype 0.1.0 → 0.1.1

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.
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'stenotype/context_handlers/base'
4
- require 'stenotype/context_handlers/rails/controller'
5
- require 'stenotype/context_handlers/rails/active_job'
6
- require 'stenotype/context_handlers/klass'
7
- require 'stenotype/context_handlers/collection'
3
+ require "stenotype/context_handlers/base"
4
+ require "stenotype/context_handlers/rails/controller"
5
+ require "stenotype/context_handlers/rails/active_job"
6
+ require "stenotype/context_handlers/klass"
7
+ require "stenotype/context_handlers/collection"
8
8
 
9
9
  module Stenotype
10
10
  #
@@ -24,9 +24,7 @@ module Stenotype
24
24
  #
25
25
  # @param handler {#publish} A handler with implemented method [#publish]
26
26
  #
27
- def register(handler)
28
- known.register(handler)
29
- end
27
+ delegate :register, to: :known
30
28
  end
31
29
  end
32
30
  end
@@ -9,6 +9,18 @@ module Stenotype
9
9
  # @attr_reader {Object} context A context in which the event was emitted
10
10
  # @attr_reader {Hash} options A hash of additional options
11
11
  #
12
+ # @example Defining a custom Handler
13
+ # class MyCustomHandler < Stenotype::ContextHandlers::Base
14
+ # self.context_name = :custom_handler
15
+ #
16
+ # def as_json(*_args)
17
+ # {
18
+ # value1: context.value1,
19
+ # value2: context.value2
20
+ # }
21
+ # end
22
+ # end
23
+ #
12
24
  class Base
13
25
  attr_reader :context, :options
14
26
 
@@ -18,7 +30,7 @@ module Stenotype
18
30
  #
19
31
  # @return {#as_json} A context handler implementing [#as_json]
20
32
  #
21
- def initialize(context, options = {})
33
+ def initialize(context, options: {})
22
34
  @context = context
23
35
  @options = options
24
36
  end
@@ -43,8 +55,7 @@ module Stenotype
43
55
  # @raise {NotImplementedError} in case handler name is not specified.
44
56
  #
45
57
  def handler_name
46
- @handler_name || raise(NotImplementedError,
47
- "Please, specify the handler_name of #{self}")
58
+ @handler_name || raise(NotImplementedError, "Please, specify the handler_name of #{self}")
48
59
  end
49
60
  end
50
61
  end
@@ -4,61 +4,105 @@ module Stenotype
4
4
  module ContextHandlers
5
5
  #
6
6
  # A class representing a list of available context handlers
7
+ # @example Complete usage overview
8
+ # class MyCustomHander
9
+ # self.handler_name = :custom_handler
7
10
  #
8
- class Collection < Array
11
+ # def as_json(*_args)
12
+ # {
13
+ # key: :value
14
+ # }
15
+ # end
16
+ # end
17
+ #
18
+ # collection = Stenotype::ContextHandlers::Collection.new
19
+ # collection.register(MyCustomHandler)
20
+ #
21
+ # collection.registered?(MyCustomHandler) #=> true
22
+ # collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
23
+ # collection.unregister(MyCustomHandler)
24
+ # collection.registered?(MyCustomHandler) #=> false
25
+ #
26
+ # collection.register(SomeRandomClass) #=> ArgumentError
27
+ # collection.unregister(SomeRandomClass) #=> ArgumentError
28
+ # collection.choose(handler_name: :unknown) #=> Stenotype::UnknownHandlerError
29
+ #
30
+ class Collection < ::Collectible::CollectionBase
31
+ #
32
+ # Return a handler with given handler_name if found in collection,
33
+ # raises if a handler is not registered
9
34
  #
10
35
  # @param handler_name {Symbol} a handler to be found in the collection
11
- # @raise {Stenotype::Exceptions::UnknownHandler} in case a handler is not registered
36
+ # @raise {Stenotype::Errors::UnknownHandler} in case a handler is not registered
12
37
  # @return {#as_json} A handler which respond to #as_json
13
38
  #
39
+ # @example When a handler is present in the collection
40
+ # collection = Stenotype::ContextHandlers::Collection.new
41
+ # collection.register(MyCustomHandler) # with handler_name = :custom_handler
42
+ # collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
43
+ #
44
+ # @example When a handler is not present in the collection
45
+ # collection = Stenotype::ContextHandlers::Collection.new
46
+ # collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
47
+ #
14
48
  def choose(handler_name:)
15
- handler = detect { |e| e.handler_name == handler_name }
16
- handler || raise(Stenotype::Exceptions::UnknownHandler,
17
- "Handler '#{handler_name}' is not found. " \
18
- "Please make sure the handler you've specified is " \
19
- 'registered in the list of known handlers. ' \
20
- "See #{Stenotype::ContextHandlers} for more information.")
49
+ handler = find_by(handler_name: handler_name)
50
+ handler || raise(
51
+ Stenotype::UnknownHandlerError,
52
+ "Handler '#{handler_name}' is not found. "\
53
+ "Please make sure the handler you've specified is "\
54
+ "registered in the list of known handlers. "\
55
+ "See #{Stenotype::ContextHandlers} for more information.",
56
+ )
21
57
  end
22
58
 
23
59
  #
24
- # @param handler {#as_json} a new handler to be added to collection
25
- # @raise {ArgumentError} in case handler does not inherit from {Stenotype::ContextHandlers::Base}
26
- # @return {Stenotype::ContextHandlers::Collection} a collection object
60
+ # @!method register(handler)
61
+ # Registers a new handler.
62
+ # @!scope instance
63
+ # @param handler {#as_json} a new handler to be added to collection
64
+ # @return {Stenotype::ContextHandlers::Collection} a collection object
65
+ # @example Registering a new handler
66
+ # collection = Stenotype::ContextHandlers::Collection.new
67
+ # collection.register(MyCustomHandler) # with handler_name = :custom_handler
27
68
  #
28
- def register(handler)
29
- unless handler < Stenotype::ContextHandlers::Base
30
- raise ArgumentError,
31
- "Handler must inherit from #{Stenotype::ContextHandlers::Base}, " \
32
- "but inherited from #{handler.superclass}"
33
- end
34
-
35
- push(handler) unless registered?(handler)
36
- self
37
- end
69
+ alias_method :register, :push
38
70
 
71
+ #
72
+ # Removes a registered handler.
73
+ #
74
+ # @example Register and unregister a handler
75
+ # collection = Stenotype::ContextHandlers::Collection.new
76
+ # collection.register(MyCustomHandler) # with handler_name = :custom_handler
77
+ # collection.unregister(MyCustomHandler) # removes MyCustomHandler from the list of handlers
39
78
  #
40
79
  # @param handler {#as_json} a handler to be removed from the collection of known handlers
41
- # @raise {ArgumentError} in case handler does not inherit from {Stenotype::ContextHandlers::Base}
42
80
  # @return {Stenotype::ContextHandlers::Collection} a collection object
43
81
  #
82
+ # @todo Add delete to the collectible delegation list
83
+ # and then use `alias_method :unregister, :delete`
44
84
  def unregister(handler)
45
- unless handler < Stenotype::ContextHandlers::Base
46
- raise ArgumentError,
47
- "Handler must inherit from #{Stenotype::ContextHandlers::Base}, " \
48
- "but inherited from #{handler.superclass}"
49
- end
50
-
51
- delete(handler) if registered?(handler)
85
+ items.delete(handler)
52
86
  self
53
87
  end
54
88
 
55
89
  #
56
- # @param handler {#as_json} a handler to be checked for presence in a collection
57
- # @return [true, false]
90
+ # @!method registered?(handler)
91
+ # Checks whether a given handler is registered in collection
92
+ # @!scope instance
93
+ # @param handler {#as_json} a handler to be checked for presence in a collection
94
+ # @return [true, false]
95
+ # @example When a handler is registered
96
+ # collection = Stenotype::ContextHandlers::Collection.new
97
+ # collection.register(MyCustomHandler) # with handler_name = :custom_handler
98
+ # collection.registered?(MyCustomHandler) # true
58
99
  #
59
- def registered?(handler)
60
- include?(handler)
61
- end
100
+ # @example When a handler is not registered
101
+ # collection = Stenotype::ContextHandlers::Collection.new
102
+ # collection.registered?(UnregisteredHandler) # false
103
+ #
104
+
105
+ alias_method :registered?, :include?
62
106
  end
63
107
  end
64
108
  end
@@ -22,21 +22,13 @@ module Stenotype
22
22
  def as_json(*_args)
23
23
  {
24
24
  job_id: job_id,
25
- enqueued_at: Time.now,
25
+ enqueued_at: Time.now.utc,
26
26
  queue_name: queue_name,
27
- class: context.class.name
27
+ class: context.class.name,
28
28
  }
29
29
  end
30
30
 
31
- private
32
-
33
- def job_id
34
- context.job_id
35
- end
36
-
37
- def queue_name
38
- context.queue_name
39
- end
31
+ delegate :job_id, :queue_name, to: :context
40
32
  end
41
33
  end
42
34
  end
@@ -14,20 +14,19 @@ module Stenotype
14
14
  #
15
15
  def as_json(*_args)
16
16
  {
17
- class: request.controller_class.name,
18
- method: request.method,
19
- url: request.url,
20
- referer: request.referer,
21
- params: request.params.except('controller', 'action'),
22
- ip: request.remote_ip
17
+ class: controller_class.name,
18
+ method: method,
19
+ url: url,
20
+ referer: referer,
21
+ params: params.except("controller", "action"),
22
+ ip: remote_ip,
23
23
  }
24
24
  end
25
25
 
26
- private
27
-
28
- def request
29
- context.request
30
- end
26
+ delegate :request, to: :context
27
+ delegate :method, :url, :referer, :remote_ip, :params,
28
+ :controller_class, to: :request
29
+ private :request, :method, :url, :referer, :remote_ip, :params, :controller_class
31
30
  end
32
31
  end
33
32
  end
@@ -9,8 +9,7 @@ module Stenotype
9
9
  #
10
10
  # Publishes an event to the list of configured targets.
11
11
  #
12
- # @example
13
- #
12
+ # @example Manually dispatching an event
14
13
  # event = Stenotype::Event.new(data, options, eval_context)
15
14
  # Stenotype::Dispatcher.new.publish(event)
16
15
  #
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stenotype
4
+ #
5
+ # An extension for a plain Ruby class in order to track invocation of
6
+ # instance methods.
7
+ #
8
+ module Emitter
9
+ #
10
+ # Class methods for target to be extended by
11
+ #
12
+ ClassMethodsExtension = Class.new(Module)
13
+ #
14
+ # Instance methods to be included into target class ancestors chain
15
+ #
16
+ InstanceMethodsExtension = Class.new(Module)
17
+
18
+ # @!visibility private
19
+ def self.included(klass)
20
+ instance_mod = InstanceMethodsExtension.new
21
+ class_mod = ClassMethodsExtension.new
22
+
23
+ build_instance_methods(instance_mod)
24
+ build_class_methods(class_mod)
25
+
26
+ klass.const_set(:InstanceProxy, Module.new)
27
+ klass.const_set(:ClassProxy, Module.new)
28
+
29
+ klass.public_send(:include, instance_mod)
30
+ klass.extend(class_mod)
31
+
32
+ super
33
+ end
34
+
35
+ #
36
+ # @!method emit_event(data = {}, method: caller_locations.first.label, eval_context: nil)
37
+ # A method injected into target class to manually track events
38
+ # @!scope instance
39
+ # @param data {Hash} Data to be sent to the targets
40
+ # @param method {String} An optional method name
41
+ # @param eval_context {Hash} A hash linking object to context handler
42
+ # @return {Stenotype::Event} An instance of emitted event
43
+ # @example Usage of emit_event
44
+ # class SomeRubyClass
45
+ # include Stenotype::Emitter
46
+ #
47
+ # def some_method
48
+ # data = collection_data
49
+ # emit_event(data, eval_context: self) # Track event with given data
50
+ # data
51
+ # end
52
+ # end
53
+ #
54
+
55
+ #
56
+ # @!method emit_event_before(*methods)
57
+ # A class method injected into target class to track instance methods invocation
58
+ # @param methods {Array<Symbol>} A list of method before which an event will be emitted
59
+ # @!scope class
60
+ # @example Usage of emit_event_before
61
+ # class SomeRubyClass
62
+ # include Stenotype::Emitter
63
+ # emit_event_before :some_method # Triggers an event upon calling some_method
64
+ #
65
+ # def some_method
66
+ # # do something
67
+ # end
68
+ # end
69
+ #
70
+
71
+ #
72
+ # @!method emit_klass_event_before(*class_methods)
73
+ # A class method injected into a target class to track class methods invocation
74
+ # @!scope class
75
+ # @param class_methods {Array<Symbol>} A list of class method before which
76
+ # an event will be emitted
77
+ # @example Usage emit_klass_event_before
78
+ # class SomeRubyClass
79
+ # include Stenotype::Emitter
80
+ # emit_klass_event_before :some_method # Triggers an event upon calling some_method
81
+ #
82
+ # def self.some_method
83
+ # # do something
84
+ # end
85
+ # end
86
+ #
87
+
88
+ #
89
+ # Adds an instance method: [#emit_event] to a target class
90
+ # where {Stenotype::Emitter} in included in
91
+ #
92
+ def self.build_instance_methods(instance_mod)
93
+ instance_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
94
+ def emit_event(data = {}, method: caller_locations.first.label, eval_context: nil)
95
+ Stenotype::Event.emit!(
96
+ {
97
+ type: "class_instance",
98
+ **data,
99
+ },
100
+ options: {
101
+ class: self.class.name,
102
+ method: method.to_sym
103
+ },
104
+ eval_context: (eval_context || { klass: self })
105
+ )
106
+ end
107
+ RUBY
108
+ end
109
+
110
+ #
111
+ # Adds class method [#emit_klass_event_before] to every class
112
+ # inherited from [Object]
113
+ #
114
+ def self.build_class_methods(class_mod)
115
+ class_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
116
+ def emit_event_before(*methods)
117
+ proxy = const_get(:InstanceProxy)
118
+
119
+ methods.each do |method|
120
+ proxy.module_eval do
121
+ define_method(method) do |*args, **rest_args, &block|
122
+ Stenotype::Event.emit!(
123
+ { type: "class_instance" },
124
+ options: {
125
+ class: self.class.name,
126
+ method: __method__
127
+ },
128
+ eval_context: { klass: self }
129
+ )
130
+ super(*args, **rest_args, &block)
131
+ end
132
+ end
133
+ end
134
+
135
+ send(:prepend, proxy)
136
+ end
137
+
138
+ def emit_klass_event_before(*class_methods)
139
+ proxy = const_get(:ClassProxy)
140
+
141
+ class_methods.each do |method|
142
+ proxy.module_eval do
143
+ define_method(method) do |*args, **rest_args, &block|
144
+ Stenotype::Event.emit!(
145
+ { type: "class" },
146
+ options: {
147
+ class: self.name,
148
+ method: __method__
149
+ },
150
+ eval_context: { klass: self }
151
+ )
152
+ super(*args, **rest_args, &block)
153
+ end
154
+ end
155
+
156
+ singleton_class.send(:prepend, proxy)
157
+ end
158
+ end
159
+ RUBY
160
+ end
161
+ end
162
+ end
@@ -8,8 +8,7 @@ module Stenotype
8
8
  #
9
9
  # Delegates event to instance of {Stenotype::Event}.
10
10
  #
11
- # @example
12
- #
11
+ # @example Emit an event using class method
13
12
  # Stenotype::Event.emit!(data, options, eval_context)
14
13
  #
15
14
  # @param data {Hash} Data to be published to the targets.
@@ -27,9 +26,10 @@ module Stenotype
27
26
  attr_reader :data, :options, :eval_context, :dispatcher
28
27
 
29
28
  #
30
- # @example
31
- #
32
- # Stenotype::Event.emit!(data, options, eval_context)
29
+ # @example Create an event
30
+ # event = Stenotype::Event.new(data, options, eval_context)
31
+ # @example Create an event with custom dispatcher
32
+ # event = Stenotype::Event.new(data, options, eval_context, dispatcher: MyDispatcher.new)
33
33
  #
34
34
  # @param {Hash} data Data to be published to the targets.
35
35
  # @param {Hash} options A hash of additional options to be tracked.
@@ -41,16 +41,15 @@ module Stenotype
41
41
  @data = data
42
42
  @options = options
43
43
  @eval_context = eval_context
44
- @dispatcher = dispatcher
44
+ @dispatcher = dispatcher.new
45
45
  end
46
46
 
47
47
  #
48
48
  # Emits a {Stenotype::Event}.
49
49
  #
50
- # @example
51
- #
50
+ # @example Emit an instance of event
52
51
  # event = Stenotype::Event.new(data, options, eval_context)
53
- # event.emit!
52
+ # event.emit! #=> Publishes the event to targets
54
53
  #
55
54
  def emit!
56
55
  dispatcher.publish(self)