stenotype 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)