instrumentable 0.0.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,12 +1,18 @@
1
- # Instrumentable
1
+ # Instrumentable (1.0.0)
2
+ -note, this version is not backwards compatiable
2
3
 
3
- TODO: Write a gem description
4
+ Decorate all your favorite methods with ActiveSupport::Notifications.instrument
5
+
6
+ This gem allows you to wrap methods on your classes for use with AS::N without having
7
+ to put AS::N.instrument do blocks around everything. You can customize the
8
+ payload sent with the event, and in addition all of the method args(if any) are sent
9
+ as well
4
10
 
5
11
  ## Installation
6
12
 
7
13
  Add this line to your application's Gemfile:
8
14
 
9
- gem 'instrumentable'
15
+ gem "instrumentable", "~> 1.0.0"
10
16
 
11
17
  And then execute:
12
18
 
@@ -17,20 +23,75 @@ Or install it yourself as:
17
23
  $ gem install instrumentable
18
24
 
19
25
  ## Usage
26
+ include the gem in your class
27
+ ```ruby
28
+ include Instrumentable
29
+ ```
30
+ to instrument an instance method
31
+ ```ruby
32
+ instrument_method :method_name, 'event.to.fire', :payload_key => :payload_value
33
+ ```
34
+
35
+ ``` :method_name ```
36
+
37
+ Is the name of the method you want to instrument
38
+
39
+ ``` 'event.to.fire' ```
40
+
41
+ Is the name of the event you'll setup your subscriber for
42
+ This can be any string
43
+
44
+ ``` :payload_key => :payload_value ```
45
+
46
+ The last part is the payload, which consists of a key and a value.
47
+ What is sent to the subscriber depends on what is passed in as the value
48
+ + String
49
+ + ex: ```'static_string'```
50
+ + string is passed in as-is to the payload
51
+ + Symbol
52
+ + ex: ```:method_name```
53
+ + calls symbol as a method on the class in the current context (class, intance)
54
+ + Proc
55
+ + ex: ```Proc.new { Time.now }```
56
+ + calls the proc, returning the value
57
+
58
+ All payloads will recieve a list of the arugments called with the method under
59
+ ```:_method_args```, this will be an empty array if the method was called with no args
60
+
61
+ If you want to instrument a class method, you must use a separate method
62
+ ```ruby
63
+ class_instrument_method self, :method_name, 'event.to.fire', :payload_key => :payload_value
64
+ ```
65
+ You must use ```class_instrument_method``` instead and pass the first argument in as ```self```
66
+
67
+ ## Examples
20
68
  ```ruby
21
69
  require "instrumentable"
22
70
 
23
71
  class WidgetRenderer
24
72
  include Instrumentable
25
73
 
26
- attr_reader :id
27
- attr_accessor :name
74
+ attr_reader :id, :name
28
75
 
29
76
  def render
30
77
  # do crazy render here
31
78
  end
32
79
 
33
- instrument_for :render, 'load.widget', :widget_id => :id, :widget_name => :name
80
+ def load(options)
81
+ # make call here
82
+ end
83
+
84
+ def self.add(location)
85
+ # ...
86
+ end
87
+
88
+ private
89
+ def valid?
90
+ # returns if call is valid
91
+ end
92
+ instrument_method :render, 'render.widget', :widget_id => :id, :widget_name => :name
93
+ instrument_method :load, 'load.widget', :status => 'loading', :valid => :valid?
94
+ class_instrument_method :add, 'add.widget'
34
95
  end
35
96
  ```
36
97
 
@@ -39,7 +100,7 @@ end
39
100
 
40
101
  ## Running Tests
41
102
 
42
- ruby -Itest test/instrumentable_test.rb
103
+ rake test
43
104
 
44
105
  ## Contributing
45
106
 
@@ -3,41 +3,99 @@ require "active_support/concern"
3
3
  require "active_support/notifications"
4
4
  require "instrumentable/version"
5
5
 
6
- # Includes +instrument_for+ into the class. The class uses it by adding
7
- # the instrument_for method to the end of the class specifying
8
- # what method to apply it to.
6
+ # Instrumentable is a module you can include in your classes to help with
7
+ # setting up instrumention with ActiveSupport::Notifications.instrument
8
+ # Commoningly you would use ActiveSupport::Notifications.instrument and
9
+ # wrap it around the code you want you care about in order to fire off
10
+ # the approiate event. This allows you to do the same thing but without
11
+ # having to modify your existing method definitions.
12
+ #
13
+ # ==== Example
14
+ #
15
+ # class Person
16
+ # include Instrumentable
17
+ # attr_reader :phone
18
+ #
19
+ # def call
20
+ # puts "Calling at #{@phone} number"
21
+ # end
22
+ #
23
+ # def self.find(id)
24
+ # PersonStore.find(id)
25
+ # end
26
+ #
27
+ # instrument_method :call, 'person.call', :phone => :phone
28
+ # class_instrument_method :find, 'person.find'
29
+ # end
30
+ #
31
+ # person = Person.find(5)
32
+ # person.call
33
+ #
34
+ # Result:
35
+ # An event with name 'person.find' would fire when `Person.find` is called
36
+ # with a payload of { :_method_args => 5 }
37
+ #
38
+ # An event with name 'person.call' would fire when person.call is called
39
+ # with a payload of { :phone => (555)555-555 }
9
40
  module Instrumentable
10
41
  extend ActiveSupport::Concern
11
42
 
12
43
  module ClassMethods
13
- # Internal: Decorates :method_to_instrument with AS::N.instrument
14
- # firing with :event_name to the matching AS::N.subscribe
44
+ # Wraps method_to_instrument in an AS:N:instrument with event_name as
45
+ # the name and passes the payload given to it.
15
46
  #
16
- # Example:
47
+ # Payload is a hash of payload_name to payload_value
17
48
  #
18
- # # Decorates render method with AS:N:instrument 'model.render' and passes
19
- # # a payload of :model_name and :id to the subscribe method
20
- # instrument_for :render, 'model.render', {:model_name => :model_name, :id => :id
21
- def instrument_for(method_to_instrument, event_name, payload={})
22
- instrument_method = :"instrument_for_#{method_to_instrument}"
49
+ # payload_value supported types:
50
+ #
51
+ # String:: Passes the string straight to the payload_name
52
+ # and does nothing else with it
53
+ # Symbol:: A method to call on the class being instrumented
54
+ # Proc:: A proc to call with the object
55
+ #
56
+ # All of these have their value then calculated (if needed) and set
57
+ # as the value for the given key
58
+ def instrument_method(method_to_instrument, event_name, payload={})
59
+ Instrumentality.begin(self, method_to_instrument, event_name, payload)
60
+ end
23
61
 
24
- # Hide original method under new method
25
- alias_method instrument_method, method_to_instrument
62
+ # Class implementation of +instrument_method+
63
+ def class_instrument_method(klass, method_to_instrument, event_name, payload={})
64
+ class << klass; self; end.class_eval do
65
+ Instrumentality.begin(self, method_to_instrument, event_name, payload)
66
+ end
67
+ end
68
+ end
26
69
 
27
- # Redefine method_to_instrument to call inside the Notification
28
- define_method(method_to_instrument) do |*args, &block|
29
- callable_payload = payload.inject({}) do |result, (payload_key, payload_value)|
30
- value = if respond_to?(payload_value)
31
- __send__ payload_value
32
- else
33
- payload_value
34
- end
70
+ private
71
+ class Instrumentality
72
+
73
+ def self.begin(klass, method, event, payload)
74
+ instrumentality = self
75
+ instrumented_method = :"_instrument_for_#{method}"
76
+ klass.send :alias_method, instrumented_method, method
77
+
78
+ klass.send(:define_method, method) do |*args, &block|
79
+ event_payload = payload.inject({}) do |result, (payload_key, payload_value)|
80
+ value = instrumentality.invoke_value(self, payload_value)
35
81
  result.tap { |r| r[payload_key] = value }
36
82
  end
37
- ActiveSupport::Notifications.instrument event_name, callable_payload do
38
- __send__(instrument_method, *args, &block)
83
+ event_payload.merge!({:_method_args => args})
84
+ ActiveSupport::Notifications.instrument event, event_payload do
85
+ __send__(instrumented_method, *args, &block)
39
86
  end
40
87
  end
41
88
  end
89
+
90
+ def self.invoke_value(klass, obj)
91
+ case obj
92
+ when Symbol
93
+ klass.__send__ obj
94
+ when Proc
95
+ obj.call
96
+ when String
97
+ obj
98
+ end
99
+ end
42
100
  end
43
101
  end
@@ -1,3 +1,3 @@
1
1
  module Instrumentable
2
- VERSION = "0.0.4"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,56 +1,118 @@
1
1
  require "minitest_helper"
2
2
  require_relative "../lib/instrumentable"
3
3
 
4
- class FakeModel
4
+ class FatherCat
5
5
  include Instrumentable
6
- def simple_event; end
7
- def false_event; end
8
- def payload_event; end
9
- instrument_for :simple_event, 'event.name', :my_payload => :id
10
- instrument_for :false_event, 'event.name', :my_payload => :valid
11
- instrument_for :payload_event, 'payload.name', :my_payload => 'megaman'
6
+ def hello_everynyan; end
7
+ def fake_cat; end
8
+ def osaka; end
9
+ def teachers(home, pe, geo); end
10
+ def self.omg; end
11
+ def self.sorry; "I'mma so sorry"; end
12
+ def self.who?(person); end
13
+
14
+ # String
15
+ instrument_method :hello_everynyan, 'fathercat.greet', :payload => 'FatherCat'
16
+ # Symbol
17
+ instrument_method :fake_cat, 'fathercat.rant', :payload => :look_alike
18
+ # Proc
19
+ instrument_method :osaka, 'fathercat.ask', :payload => Proc.new { 'also cat tounged' }
20
+ # Args
21
+ instrument_method :teachers, 'fathercat.teachers', :payload => 'teachers'
22
+
23
+ # Class
24
+ class_instrument_method self, :omg, 'fathercat.anger', :payload => :sorry
25
+ # Args
26
+ class_instrument_method self, :who?, 'fathercat.who', :payload => 'who?'
12
27
  end
13
28
 
14
29
  describe Instrumentable do
15
30
 
16
- it "must instrument simple_event" do
17
- fm = FakeModel.new
18
- def fm.id; 1; end
19
- expected = ["event.name-#{fm.id}"]
20
- events = []
31
+ describe ".instrument_method" do
32
+ it "must instrument fathercat.greet" do
33
+ cat = FatherCat.new
34
+ expected = ["fathercat.greet-FatherCat"]
35
+ events = []
21
36
 
22
- callback = lambda { |*_| events << "#{_.first}-#{_.last[:my_payload]}" }
23
- ActiveSupport::Notifications.subscribed(callback, 'event.name') do
24
- fm.simple_event
25
- ActiveSupport::Notifications.instrument('other.event')
37
+ callback = lambda { |*_| events << "#{_.first}-#{_.last[:payload]}" }
38
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.greet') do
39
+ cat.hello_everynyan
40
+ ActiveSupport::Notifications.instrument('some.other.event')
41
+ end
42
+ events.must_equal expected
43
+ end
44
+
45
+ it "must instrument fathercat.rant" do
46
+ cat = FatherCat.new
47
+ def cat.look_alike; 'Mori'; end
48
+ expected = ["fathercat.rant-#{cat.look_alike}"]
49
+ events = []
50
+
51
+ callback = lambda { |*_| events << "#{_.first}-#{_.last[:payload]}" }
52
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.rant') do
53
+ cat.fake_cat
54
+ ActiveSupport::Notifications.instrument('some.other.event')
55
+ end
56
+ events.must_equal expected
26
57
  end
27
- events.must_equal expected
28
- end
29
58
 
30
- it "must work with callable payload that returns false" do
31
- fm = FakeModel.new
32
- def fm.valid; false; end
33
- expected = ["event.name-#{fm.valid}"]
34
- events = []
59
+ it "must instrument fathercat.ask" do
60
+ cat = FatherCat.new
61
+ expected = ["fathercat.ask-also cat tounged"]
62
+ events = []
35
63
 
36
- callback = lambda { |*_| events << "#{_.first}-#{_.last[:my_payload]}" }
37
- ActiveSupport::Notifications.subscribed(callback, 'event.name') do
38
- fm.false_event
39
- ActiveSupport::Notifications.instrument('other.event')
64
+ callback = lambda { |*_| events << "#{_.first}-#{_.last[:payload]}" }
65
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.ask') do
66
+ cat.osaka
67
+ ActiveSupport::Notifications.instrument('some.other.event')
68
+ end
69
+ events.must_equal expected
70
+ end
71
+
72
+ it "must pass method args to payload" do
73
+ cat = FatherCat.new
74
+ teachers = { :home => 'Yukari', :pe => 'Nyamo', :geo => 'Kimura' }
75
+ expected = ["fathercat.teachers-#{teachers[:home]}_#{teachers[:pe]}_#{teachers[:geo]}"]
76
+ events = []
77
+
78
+ callback = lambda do |*_|
79
+ event_name = _.first
80
+ payload = _.last[:_method_args].join('_')
81
+ events << "#{event_name}-#{payload}"
82
+ end
83
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.teachers') do
84
+ cat.teachers(teachers[:home], teachers[:pe], teachers[:geo])
85
+ ActiveSupport::Notifications.instrument('some.other.event')
86
+ end
87
+ events.must_equal expected
40
88
  end
41
- events.must_equal expected
42
89
  end
43
90
 
44
- it "must handle non-callable payloads" do
45
- fm = FakeModel.new
46
- expected = ['payload.name-megaman']
47
- events = []
91
+ describe ".class_instrument_method" do
92
+ it "must instrument fathercat.anger" do
93
+ expected = ["fathercat.anger-#{FatherCat.sorry}"]
94
+ events = []
95
+
96
+ callback = lambda { |*_| events << "#{_.first}-#{_.last[:payload]}" }
97
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.anger') do
98
+ FatherCat.omg
99
+ ActiveSupport::Notifications.instrument('some.other.event')
100
+ end
101
+ events.must_equal expected
102
+ end
103
+
104
+ #class_instrument_method self, :who?, 'fathercat.who', :payload => 'who?'
105
+ it "must pass method args to payload" do
106
+ person = 'chiyo'
107
+ expected = ["fathercat.who-#{person}"]
108
+ events = []
48
109
 
49
- callback = lambda { |*_| events << "#{_.first}-#{_.last[:my_payload]}" }
50
- ActiveSupport::Notifications.subscribed(callback, 'payload.name') do
51
- fm.payload_event
52
- ActiveSupport::Notifications.instrument('other.event')
110
+ callback = lambda { |*_| events << "#{_.first}-#{_.last[:_method_args].first}" }
111
+ ActiveSupport::Notifications.subscribed(callback, 'fathercat.who') do
112
+ FatherCat.who?(person)
113
+ ActiveSupport::Notifications.instrument('some.other.event')
114
+ end
115
+ events.must_equal expected
53
116
  end
54
- events.must_equal expected
55
117
  end
56
118
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instrumentable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-14 00:00:00.000000000 Z
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport