chubas-peeping 1.0.0 → 1.1.0

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.
data/README.markdown CHANGED
@@ -59,16 +59,17 @@ produces output:
59
59
 
60
60
  ## Tests
61
61
 
62
- The library comes with its rspec test suite, located in folder _tests_
62
+ The library comes with its rspec test suite, located in folder _spec_
63
63
 
64
64
  ## Updates
65
65
 
66
+ 15 Jul 09 - Finished hook behavior specification. Version 1.1.0 released
67
+
66
68
  ## TODO
67
69
 
68
- - Add option to eval in the context of the caller with instance_exec
69
- - Selective remove for existing hooks
70
- - Add option to either replace or wrap singleton method hooks when instance method hooks are already defined
71
- - Accept options for wrapping, replacing or adding hooks at custom positions in the bubble chain
72
- - Gem packaging
70
+ ( Several previously marked TODO's are not anymore. Peeping it's not aimed at being a full framework )
71
+
72
+ - Refactor classes (lot of ugly things there)
73
+ - Add option to override ot keep instance hooks when defining singleton hooks
73
74
 
74
75
  [1]: http://rdoc.info/projects/chubas/peeping
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -3,4 +3,5 @@ module Peeping
3
3
  class UndefinedMethodException < Exception; end
4
4
  class NotAClassException < Exception; end
5
5
  class AlreadyDefinedHookException < Exception; end
6
+ class UndefinedHookException < Exception; end
6
7
  end
@@ -0,0 +1,150 @@
1
+ require 'peeping/hooks/hooking'
2
+
3
+ module Peeping
4
+ module ClassMethodHooks
5
+
6
+ #--
7
+ # Extend class and instance methods to the base class
8
+ #++
9
+ def self.included(base) #:nodoc:
10
+ base.extend Peeping::ClassMethodHooks::ClassMethods
11
+ base.class_eval do
12
+ include Peeping::ClassMethodHooks::InstanceMethods
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ include Hooking
19
+
20
+ # Holds the hooks declared for class methods
21
+ CLASS_METHOD_HOOKS = {}
22
+
23
+ # Returns true if hooks given for class methods of the class +klass+ exist
24
+ def is_class_hooked?(klass)
25
+ CLASS_METHOD_HOOKS.has_key?(klass) and not CLASS_METHOD_HOOKS[klass].empty?
26
+ end
27
+
28
+ # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
29
+ def hooked_class_methods_for(klass)
30
+ CLASS_METHOD_HOOKS[klass] || {}
31
+ end
32
+
33
+ # Adds +hooks+ hooks to the given +klass+ class methods +hooked_methods+
34
+ # This parameter can be either a symbol or an array of symbols.
35
+ #
36
+ # Possible thrown exceptions:
37
+ # NotAClassException:: When the specified parameter +klass+ is not a class
38
+ # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined class method
39
+ # AlreadyDefinedHookException:: When a hook for this class and specified method exists
40
+ # InvalidHooksException:: When hooks contains keys other than :before and :after calls
41
+ def hook_class!(klass, hooked_methods, hooks)
42
+
43
+ hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
44
+
45
+ validate_hooks(hooks) # Validate hooks
46
+ validate_is_class(klass) # Validate class
47
+
48
+ hooked_methods.each do |hooked_method| # Validate methods defined
49
+ validate_has_method_defined(klass.metaclass, hooked_method)
50
+ end
51
+
52
+ hooked_methods.each do |hooked_method|
53
+ CLASS_METHOD_HOOKS[klass] ||= {}
54
+ new_hooked_method_name = "#{CLASS_HOOK_METHOD_PREFIX}#{hooked_method}"
55
+
56
+ if CLASS_METHOD_HOOKS[klass].has_key?(hooked_method)
57
+ raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{klass.metaclass}")
58
+ end
59
+ if klass.metaclass.method_defined?(new_hooked_method_name)
60
+ raise AlreadyDefinedHookException.new("Method #{new_hooked_method_name} hook already defined for #{hooked_method} in #{klass.metaclass}")
61
+ end
62
+
63
+ CLASS_METHOD_HOOKS[klass][hooked_method] = {}
64
+
65
+ hooks.each do |where, callback|
66
+ CLASS_METHOD_HOOKS[klass][hooked_method][where] = callback
67
+ end
68
+
69
+ hook_key = klass.name
70
+ before_hook_call = <<-BEFORE_HOOK_CALL
71
+ before_hook_callback = Peeping::Peep.hooked_class_methods_for(#{hook_key})[:"#{hooked_method}"][:before]
72
+ before_hook_callback.call(self, *args) if before_hook_callback
73
+ BEFORE_HOOK_CALL
74
+
75
+ after_hook_call = <<-AFTER_HOOK_CALL
76
+ after_hook_callback = Peeping::Peep.hooked_class_methods_for(#{hook_key})[:"#{hooked_method}"][:after]
77
+ after_hook_callback.call(self, proxied_result) if after_hook_callback
78
+ AFTER_HOOK_CALL
79
+
80
+ class_eval_call = Proc.new do
81
+ eval <<-REDEF
82
+ alias :"#{new_hooked_method_name}" :"#{hooked_method}"
83
+ def #{hooked_method}(*args, &block)
84
+ #{before_hook_call}
85
+ proxied_result = if block_given?
86
+ __send__("#{new_hooked_method_name}", *args, &block)
87
+ else
88
+ __send__("#{new_hooked_method_name}", *args)
89
+ end
90
+ #{after_hook_call}
91
+ proxied_result
92
+ end
93
+ REDEF
94
+ end
95
+ klass.metaclass.class_eval(&class_eval_call)
96
+ end
97
+ end
98
+
99
+ # Removes hook class methods as well as returns the hooked methods to their original definition
100
+ def unhook_class!(klass, methods = :all, hooks = :all)
101
+ methods = (methods == :all) ? CLASS_METHOD_HOOKS[klass].keys : (methods.is_a?(Symbol) ? [methods] : methods)
102
+ raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
103
+
104
+ # Validate all methods exist before doing anything
105
+ methods.each do |method|
106
+ raise UndefinedHookException.new("No hook defined for class method #{method})") unless CLASS_METHOD_HOOKS[klass][method]
107
+ end
108
+
109
+ methods.each do |method|
110
+ if hooks == :all
111
+ klass.metaclass.class_eval <<-REDEF_OLD_METHOD
112
+ alias :"#{method}" :"#{CLASS_HOOK_METHOD_PREFIX}#{method}"
113
+ undef :"#{CLASS_HOOK_METHOD_PREFIX}#{method}"
114
+ REDEF_OLD_METHOD
115
+ CLASS_METHOD_HOOKS[klass].delete(method)
116
+ else
117
+ unless CLASS_METHOD_HOOKS[klass][method][hooks]
118
+ raise UndefinedHookException.new("No hook defined for class method #{method}) at #{hooks.inspect}")
119
+ end
120
+ CLASS_METHOD_HOOKS[klass][method].delete(hooks)
121
+ CLASS_METHOD_HOOKS[klass].delete(method) if CLASS_METHOD_HOOKS[klass][method].empty?
122
+ end
123
+ end
124
+
125
+ CLASS_METHOD_HOOKS.delete(klass) if methods == :all or (CLASS_METHOD_HOOKS[klass] || {}).empty?
126
+
127
+ end
128
+
129
+ def clear_all_class_hooks!
130
+ CLASS_METHOD_HOOKS.each do |klass, hooked_methods|
131
+ hooked_methods.each do |hooked_method, callbacks|
132
+ redefined_method = "#{CLASS_HOOK_METHOD_PREFIX}#{hooked_method}"
133
+ klass.metaclass.class_eval <<-UNDEF_EVAL
134
+ if method_defined?(:"#{redefined_method}")
135
+ alias :"#{hooked_method}" :"#{redefined_method}"
136
+ undef :"#{redefined_method}"
137
+ end
138
+ UNDEF_EVAL
139
+ end
140
+ end
141
+ CLASS_METHOD_HOOKS.clear
142
+ end
143
+
144
+ end
145
+
146
+ module InstanceMethods
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,42 @@
1
+ require 'peeping/exceptions'
2
+
3
+ module Peeping
4
+ module Hooking
5
+
6
+ HOOKED_METHOD_PREFIX = '__peeping__hooked'
7
+
8
+ # Prefix for hooked class methods
9
+ CLASS_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__class__method__"
10
+
11
+ # Prefix for hooked class methods
12
+ INSTANCE_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__instance__method__"
13
+
14
+ # Prefix for hooked class methods
15
+ SINGLETON_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__singleton__method__"
16
+
17
+ private
18
+
19
+ # Validates that the array consists only in the keys :before or/and :after
20
+ #--
21
+ # TODO: Add extensibility for possible keys. Ideas: warn_on_override, :dont_override, :execute_in_place
22
+ #++
23
+ def validate_hooks(hooks)
24
+ raise InvalidHooksException.new("At least an :after or a :before hook are expected") if hooks.empty?
25
+ unknown_keys = hooks.keys - [:before, :after]
26
+ raise InvalidHooksException.new("Unknown keys #{unknown_keys.join(', ')}") unless unknown_keys.empty?
27
+ end
28
+
29
+ # Validates that the passed object is a class
30
+ def validate_is_class(klass)
31
+ raise NotAClassException.new("#{klass} is not a Class") unless klass.is_a? Class
32
+ end
33
+
34
+ # Validates that the the method that will be hooked for the given object is defined.
35
+ def validate_has_method_defined(holding_class, method)
36
+ unless holding_class.method_defined?(method)
37
+ raise UndefinedMethodException.new("Undefined method #{method.inspect} for #{holding_class}")
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,145 @@
1
+ require 'peeping/hooks/hooking'
2
+
3
+ module Peeping
4
+ module InstanceMethodHooks
5
+
6
+ def self.included(base)
7
+ base.extend Peeping::InstanceMethodHooks::ClassMethods
8
+ base.class_eval do
9
+ include Peeping::InstanceMethodHooks::InstanceMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ include Hooking
16
+
17
+ # Holds the hooks defined for instance methods
18
+ INSTANCE_METHOD_HOOKS = {}
19
+
20
+ # Returns true if hooks given for instance methods of the class +klass+ exist
21
+ def are_instances_hooked?(klass)
22
+ INSTANCE_METHOD_HOOKS.has_key?(klass)
23
+ end
24
+
25
+ # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
26
+ def hooked_instance_methods_for(klass)
27
+ INSTANCE_METHOD_HOOKS[klass] || {}
28
+ end
29
+
30
+ # Adds +hooks+ hooks to the given +klass+ instance methods +hooked_methods+
31
+ # This parameter can be either a symbol or an array of symbols.
32
+ #
33
+ # Possible thrown exceptions:
34
+ # NotAClassException:: When the specified parameter +klass+ is not a class
35
+ # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined instance method
36
+ # AlreadyDefinedHookException:: When a hook for this class and specified instance method exists
37
+ # InvalidHooksException:: When hooks contains keys other than :before and :after calls
38
+ def hook_instances!(klass, hooked_methods, hooks)
39
+ hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
40
+
41
+ #=== Validations ===
42
+ validate_hooks(hooks)
43
+ validate_is_class(klass)
44
+ hooked_methods.each { |hooked_method| validate_has_method_defined(klass, hooked_method) }
45
+
46
+ hooked_methods.each do |hooked_method|
47
+ INSTANCE_METHOD_HOOKS[klass] ||= {}
48
+ hooked_method_name = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
49
+
50
+ if INSTANCE_METHOD_HOOKS[klass].has_key?(hooked_method)
51
+ raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{klass}")
52
+ end
53
+ if klass.method_defined?(hooked_method_name)
54
+ raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{klass}")
55
+ end
56
+
57
+ INSTANCE_METHOD_HOOKS[klass][hooked_method] = {}
58
+
59
+ hooks.each do |where, callback|
60
+ INSTANCE_METHOD_HOOKS[klass][hooked_method][where] = callback
61
+ end
62
+
63
+ hook_key = klass.name
64
+ before_hook_call = <<-BEFORE_HOOK_CALL
65
+ before_hook_callback = Peeping::Peep.hooked_instance_methods_for(#{hook_key})[:"#{hooked_method}"][:before]
66
+ before_hook_callback.call(self, *args) if before_hook_callback
67
+ BEFORE_HOOK_CALL
68
+
69
+ after_hook_call = <<-AFTER_HOOK_CALL
70
+ after_hook_callback = Peeping::Peep.hooked_instance_methods_for(#{hook_key})[:"#{hooked_method}"][:after]
71
+ after_hook_callback.call(self, proxied_result) if after_hook_callback
72
+ AFTER_HOOK_CALL
73
+
74
+ class_eval_call = Proc.new do
75
+ eval <<-REDEF
76
+ alias :"#{hooked_method_name}" :"#{hooked_method}"
77
+ def #{hooked_method}(*args, &block)
78
+ #{before_hook_call}
79
+ proxied_result = if block_given?
80
+ __send__("#{hooked_method_name}", *args, &block)
81
+ else
82
+ __send__("#{hooked_method_name}", *args)
83
+ end
84
+ #{after_hook_call}
85
+ proxied_result
86
+ end
87
+ REDEF
88
+ end
89
+ klass.class_eval(&class_eval_call)
90
+ end
91
+
92
+ end
93
+
94
+ # Removes hook instance methods as well as returns the hooked methods to their original definition
95
+ def unhook_instances!(klass, methods = :all, hooks = :all)
96
+ methods = (methods == :all) ? INSTANCE_METHOD_HOOKS[klass].keys : (methods.is_a?(Symbol) ? [methods] : methods)
97
+ raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
98
+
99
+ # Validate all methods exist before doing anything
100
+ methods.each do |method|
101
+ raise UndefinedHookException.new("No hook defined for instance method #{method})") unless INSTANCE_METHOD_HOOKS[klass][method]
102
+ end
103
+
104
+ methods.each do |method|
105
+ if hooks == :all
106
+ klass.class_eval <<-REDEF_OLD_METHOD
107
+ alias :"#{method}" :"#{INSTANCE_HOOK_METHOD_PREFIX}#{method}"
108
+ undef :"#{INSTANCE_HOOK_METHOD_PREFIX}#{method}"
109
+ REDEF_OLD_METHOD
110
+ INSTANCE_METHOD_HOOKS[klass].delete(method)
111
+ else
112
+ unless INSTANCE_METHOD_HOOKS[klass][method][hooks]
113
+ raise UndefinedHookException.new("No hook defined for instance method #{method}) at #{hooks.inspect}")
114
+ end
115
+ INSTANCE_METHOD_HOOKS[klass][method].delete(hooks)
116
+ INSTANCE_METHOD_HOOKS[klass].delete(method) if INSTANCE_METHOD_HOOKS[klass][method].empty?
117
+ end
118
+ end
119
+
120
+ INSTANCE_METHOD_HOOKS.delete(klass) if methods == :all or (INSTANCE_METHOD_HOOKS[klass] || {}).empty?
121
+
122
+ end
123
+
124
+ def clear_all_instance_hooks!
125
+ INSTANCE_METHOD_HOOKS.each do |klass, hooked_methods|
126
+ hooked_methods.each do |hooked_method, callbacks|
127
+ redefined_method = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
128
+ klass.class_eval <<-UNDEF_EVAL
129
+ if method_defined?(:"#{redefined_method}")
130
+ alias :"#{hooked_method}" :"#{redefined_method}"
131
+ undef :"#{redefined_method}"
132
+ end
133
+ UNDEF_EVAL
134
+ end
135
+ end
136
+ INSTANCE_METHOD_HOOKS.clear
137
+ end
138
+
139
+ end
140
+
141
+ module InstanceMethods
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,165 @@
1
+ require 'peeping/hooks/hooking'
2
+
3
+ module Peeping
4
+ module SingletonMethodHooks
5
+
6
+ #--
7
+ # Extend class and instance methods to the base class
8
+ #++
9
+ def self.included(base) #:nodoc:
10
+ base.extend Peeping::SingletonMethodHooks::ClassMethods
11
+ base.class_eval do
12
+ include Peeping::SingletonMethodHooks::InstanceMethods
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ include Hooking
19
+
20
+ # Holds the hooks defined for instance methods
21
+ SINGLETON_METHOD_HOOKS = {}
22
+
23
+ # Returns true if hooks given for singleton methods of the class +klass+ exist
24
+ def is_hooked?(object)
25
+ SINGLETON_METHOD_HOOKS.has_key?(object)
26
+ end
27
+
28
+ # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
29
+ def hooked_singleton_methods_for(object)
30
+ SINGLETON_METHOD_HOOKS[object] || {}
31
+ end
32
+
33
+ # Adds +hooks+ hooks to the given +object+ singleton methods +hooked_methods+
34
+ # This parameter can be either a symbol or an array of symbols.
35
+ #
36
+ # Possible thrown exceptions:
37
+ # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is not a defined method for object +object+
38
+ # AlreadyDefinedHookException:: When a hook for this object and specified instance method exists.
39
+ # Note that this error *will not* be raised if an instance method hook exists for this
40
+ # method in the object's class. It will override it.
41
+ # InvalidHooksException:: When hooks contains keys other than :before and :after calls
42
+ def hook_object!(object, hooked_methods, hooks)
43
+ hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
44
+
45
+ validate_hooks(hooks) # Validate hooks
46
+
47
+ hooked_methods.each do |hooked_method| # Validate methods defined
48
+ validate_has_method_defined(object.metaclass, hooked_method)
49
+ end
50
+
51
+ hooked_methods.each do |hooked_method|
52
+ SINGLETON_METHOD_HOOKS[object] ||= {}
53
+ hooked_method_name = "#{SINGLETON_HOOK_METHOD_PREFIX}#{hooked_method}"
54
+
55
+ if SINGLETON_METHOD_HOOKS[object].has_key?(hooked_method)
56
+ raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{object.metaclass}")
57
+ end
58
+ if object.metaclass.method_defined?(hooked_method_name)
59
+ raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{where_to_eval}")
60
+ end
61
+
62
+ SINGLETON_METHOD_HOOKS[object][hooked_method] = {}
63
+
64
+ hooks.each do |where, callback|
65
+ SINGLETON_METHOD_HOOKS[object][hooked_method][where] = callback
66
+ end
67
+
68
+ hook_key = 'self'
69
+ before_hook_call = <<-BEFORE_HOOK_CALL
70
+
71
+ before_hook_callback = ((Peeping::Peep.hooked_singleton_methods_for(#{hook_key})||{})[:"#{hooked_method}"] || {})[:before]
72
+ instance_before_hook_callback = ((Peeping::Peep.hooked_instance_methods_for(self.class) || {})[:"#{hooked_method}"] || {})[:before]
73
+
74
+ before_hook_callback ||= instance_before_hook_callback
75
+ before_hook_callback.call(self, *args) if before_hook_callback
76
+ BEFORE_HOOK_CALL
77
+
78
+ after_hook_call = <<-AFTER_HOOK_CALL
79
+ after_hook_callback = ((Peeping::Peep.hooked_singleton_methods_for(#{hook_key})||{})[:"#{hooked_method}"] || {})[:after]
80
+ instance_after_hook_callback = ((Peeping::Peep.hooked_instance_methods_for(self.class) || {})[:"#{hooked_method}"] || {})[:after]
81
+
82
+ after_hook_callback ||= instance_after_hook_callback
83
+ after_hook_callback.call(self, proxied_result) if after_hook_callback
84
+ AFTER_HOOK_CALL
85
+
86
+ should_override_instance_call = true # TODO: Optionalize
87
+ class_eval_call = Proc.new do
88
+ eval <<-REDEF
89
+ if #{should_override_instance_call} and method_defined?(:"#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}")
90
+ alias :"#{hooked_method_name}" :"#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
91
+ else
92
+ alias :"#{hooked_method_name}" :"#{hooked_method}"
93
+ end
94
+ def #{hooked_method}(*args, &block)
95
+ #{before_hook_call}
96
+ proxied_result = if block_given?
97
+ __send__("#{hooked_method_name}", *args, &block)
98
+ else
99
+ __send__("#{hooked_method_name}", *args)
100
+ end
101
+ #{after_hook_call}
102
+ proxied_result
103
+ end
104
+ REDEF
105
+ end
106
+ object.metaclass.class_eval(&class_eval_call)
107
+ end
108
+ end
109
+
110
+ # Removes hook singleton methods as well as returns the hooked methods to their original definition
111
+ def unhook_object!(object, methods = :all, hooks = :all)
112
+ methods = (methods == :all) ? SINGLETON_METHOD_HOOKS[object].keys : (methods.is_a?(Symbol) ? [methods] : methods)
113
+ raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
114
+
115
+ # Validate all methods exist before doing anything
116
+ methods.each do |method|
117
+ raise UndefinedHookException.new("No hook defined for class method #{method})") unless SINGLETON_METHOD_HOOKS[object][method]
118
+ end
119
+
120
+ methods.each do |method|
121
+ if hooks == :all
122
+ object.metaclass.class_eval <<-REDEF_OLD_METHOD
123
+ alias :"#{method}" :"#{SINGLETON_HOOK_METHOD_PREFIX}#{method}"
124
+ undef :"#{SINGLETON_HOOK_METHOD_PREFIX}#{method}"
125
+ REDEF_OLD_METHOD
126
+ SINGLETON_METHOD_HOOKS[object].delete(method)
127
+ else
128
+ unless SINGLETON_METHOD_HOOKS[object][method][hooks]
129
+ raise UndefinedHookException.new("No hook defined for singleton method #{method}) at #{hooks.inspect}")
130
+ end
131
+ SINGLETON_METHOD_HOOKS[object][method].delete(hooks)
132
+ SINGLETON_METHOD_HOOKS[object].delete(method) if SINGLETON_METHOD_HOOKS[object][method].empty?
133
+ end
134
+ end
135
+
136
+ SINGLETON_METHOD_HOOKS.delete(object) if methods == :all or (SINGLETON_METHOD_HOOKS[object] || {}).empty?
137
+ end
138
+
139
+ def clear_all_singleton_hooks!
140
+ SINGLETON_METHOD_HOOKS.each do |object, hooked_methods|
141
+ hooked_methods.each do |hooked_method, callbacks|
142
+ redefined_singleton_method = "#{SINGLETON_HOOK_METHOD_PREFIX}#{hooked_method}"
143
+ redefined_instance_method = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
144
+ object.metaclass.class_eval <<-UNDEF_EVAL
145
+ if method_defined?(:"#{redefined_singleton_method}")
146
+ if method_defined?(:"#{redefined_instance_method}")
147
+ alias :"#{hooked_method}" :"#{redefined_instance_method}"
148
+ else
149
+ alias :"#{hooked_method}" :"#{redefined_singleton_method}"
150
+ end
151
+ undef :"#{redefined_singleton_method}"
152
+ end
153
+ UNDEF_EVAL
154
+ end
155
+ end
156
+ SINGLETON_METHOD_HOOKS.clear
157
+ end
158
+
159
+ end
160
+
161
+ module InstanceMethods
162
+ end
163
+
164
+ end
165
+ end
@@ -9,6 +9,10 @@ require 'peeping/util'
9
9
  require 'peeping/hook'
10
10
  require 'peeping/exceptions'
11
11
 
12
+ require 'peeping/hooks/class_hooks'
13
+ require 'peeping/hooks/instance_hooks'
14
+ require 'peeping/hooks/singleton_hooks'
15
+
12
16
  # Wraps classes and modules for the +peeping+ library
13
17
  module Peeping
14
18
 
@@ -16,299 +20,22 @@ module Peeping
16
20
  # hold the hook definitions
17
21
  class Peep
18
22
 
19
- #--
20
- ## ========== Holder variables section ==========
21
- #++
22
-
23
- # Holds the hooks declared for class methods
24
- CLASS_METHOD_HOOKS = {}
25
-
26
- # Holds the hooks defined for instance methods
27
- INSTANCE_METHOD_HOOKS = {}
28
-
29
- # Holds the hooks defined for singleton methods
30
- SINGLETON_METHOD_HOOKS = {}
31
-
32
- #--
33
- ## ===================================================
34
- ## =============== Class hooks section ===============
35
- ## ===================================================
36
- #++
37
-
38
- # Returns true if hooks given for class methods of the class +klass+ exist
39
- def self.is_class_hooked?(klass)
40
- CLASS_METHOD_HOOKS.has_key?(klass)
41
- end
42
-
43
- # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
44
- def self.hooked_class_methods_for(klass)
45
- CLASS_METHOD_HOOKS[klass] || {}
46
- end
47
-
48
- # Adds +hooks+ hooks to the given +klass+ class methods +hooked_methods+
49
- # This parameter can be either a symbol or an array of symbols.
50
- #
51
- # Possible thrown exceptions:
52
- # NotAClassException:: When the specified parameter +klass+ is not a class
53
- # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined class method
54
- # AlreadyDefinedHookException:: When a hook for this class and specified method exists
55
- # InvalidHooksException:: When hooks contains keys other than :before and :after calls
56
- def self.hook_class!(klass, hooked_methods, hooks)
57
- define_method_hooks!(klass, hooked_methods, hooks, :class, CLASS_METHOD_HOOKS)
58
- end
59
-
60
- # Removes hook class methods as well as returns the hooked methods to their original definition
61
- def self.unhook_class!(klass)
62
- klass.metaclass.class_eval do
63
- instance_methods.grep(/^__proxied_class_method_/).each do |proxied_method|
64
- proxied_method =~ (/^__proxied_class_method_(.*)$/)
65
- original = $1
66
- eval "alias #{original} #{proxied_method}"
67
- eval "undef #{proxied_method}"
68
- end
69
- end
70
- CLASS_METHOD_HOOKS.delete(klass)
71
- end
23
+ include ClassMethodHooks
24
+ include InstanceMethodHooks
25
+ include SingletonMethodHooks
72
26
 
73
27
  #--
74
- ## ======================================================
75
- ## =============== Instance hooks section ===============
76
- ## ======================================================
28
+ #===== GLOBAL FUNCTIONS =====
77
29
  #++
78
30
 
79
- # Returns true if hooks given for instance methods of the class +klass+ exist
80
- def self.are_instances_hooked?(klass)
81
- INSTANCE_METHOD_HOOKS.has_key?(klass)
82
- end
83
-
84
- # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
85
- def self.hooked_instance_methods_for(klass)
86
- INSTANCE_METHOD_HOOKS[klass] || {}
87
- end
88
-
89
- # Adds +hooks+ hooks to the given +klass+ instance methods +hooked_methods+
90
- # This parameter can be either a symbol or an array of symbols.
91
- #
92
- # Possible thrown exceptions:
93
- # NotAClassException:: When the specified parameter +klass+ is not a class
94
- # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined instance method
95
- # AlreadyDefinedHookException:: When a hook for this class and specified instance method exists
96
- # InvalidHooksException:: When hooks contains keys other than :before and :after calls
97
- def self.hook_instances!(klass, hooked_methods, hooks)
98
- define_method_hooks!(klass, hooked_methods, hooks, :instance, INSTANCE_METHOD_HOOKS)
99
- end
100
-
101
- # Removes hook instance methods as well as returns the hooked methods to their original definition
102
- def self.unhook_instances!(klass)
103
- klass.class_eval do
104
- instance_methods.grep(/^__proxied_instance_method_/).each do |proxied_method|
105
- proxied_method =~ (/^__proxied_instance_method_(.*)$/)
106
- original = $1
107
- eval "alias #{original} #{proxied_method}"
108
- eval "undef #{proxied_method}"
109
- end
110
- end
111
- INSTANCE_METHOD_HOOKS.delete(klass)
112
- end
113
-
114
- #--
115
- ## =======================================================
116
- ## =============== Singleton hooks section ===============
117
- ## =======================================================
118
- #++
119
-
120
- # Returns true if hooks given for singleton methods of the class +klass+ exist
121
- def self.is_hooked?(object)
122
- SINGLETON_METHOD_HOOKS.has_key?(object)
123
- end
124
-
125
- # Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
126
- def self.hooked_singleton_methods_for(object)
127
- SINGLETON_METHOD_HOOKS[object] || {}
128
- end
129
-
130
- # Adds +hooks+ hooks to the given +object+ singleton methods +hooked_methods+
131
- # This parameter can be either a symbol or an array of symbols.
132
- #
133
- # Possible thrown exceptions:
134
- # UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is not a defined method for object +object+
135
- # AlreadyDefinedHookException:: When a hook for this object and specified instance method exists.
136
- # Note that this error *will not* be raised if an instance method hook exists for this
137
- # method in the object's class. It will override it.
138
- # InvalidHooksException:: When hooks contains keys other than :before and :after calls
139
- def self.hook_object!(object, hooked_methods, hooks)
140
- define_method_hooks!(object, hooked_methods, hooks, :singleton, SINGLETON_METHOD_HOOKS)
141
- end
142
-
143
- # Removes hook singleton methods as well as returns the hooked methods to their original definition
144
- def self.unhook_object!(object)
145
- object.metaclass.class_eval do
146
- instance_methods.grep(/^__proxied_singleton_method_/).each do |proxied_method|
147
- proxied_method =~ (/^__proxied_singleton_method_(.*)$/)
148
- original = $1
149
- eval "alias #{original} #{proxied_method}"
150
- eval "undef #{proxied_method}"
151
- end
152
- end
153
- SINGLETON_METHOD_HOOKS.delete(object)
154
- end
155
-
156
- #--
157
- ## ========== Protected auxiliar methods section ==========
158
- class << self
159
- protected
160
-
161
- def define_method_hooks!(what, hooked_methods, hooks, mode, container)
162
- hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
163
-
164
- validate_hooks(hooks) # Validate hooks
165
- validate_is_class(what, mode) # Validate class
166
-
167
- where_to_eval = (mode == :class or mode == :singleton) ? what.metaclass : what
168
- hooked_methods.each do |hooked_method| # Validate methods defined
169
- validate_has_method_defined(where_to_eval, hooked_method, mode)
170
- end
171
-
172
- hooked_methods.each do |hooked_method|
173
- container[what] ||= {}
174
- hooked_method_name = "__proxied_#{mode}_method_#{hooked_method}"
175
-
176
- if container[what].has_key?(hooked_method)
177
- raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{where_to_eval}")
178
- end
179
- if what.respond_to?(hooked_method_name)
180
- raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{where_to_eval}")
181
- end
182
-
183
- container[what][hooked_method] = {}
184
-
185
- hooks.each do |where, callback|
186
- container[what][hooked_method][where] = callback
187
- end
188
-
189
- hook_key = (mode == :class or mode == :instance) ? what.name : 'self'
190
- before_hook_call = if hooks.include?(:before)
191
- "Peeping::Peep.hooked_#{mode}_methods_for(#{hook_key})[:\"#{hooked_method}\"][:before].call(self, *args)"
192
- end
193
- after_hook_call = if hooks.include?(:after)
194
- "Peeping::Peep.hooked_#{mode}_methods_for(#{hook_key})[:\"#{hooked_method}\"][:after].call(self, proxied_result)"
195
- end
196
- should_override_instance_call = (mode == :singleton).to_s
197
- class_eval_call = Proc.new do
198
- eval <<-REDEF
199
- if #{should_override_instance_call} and method_defined?(:"__proxied_instance_method_#{hooked_method}")
200
- alias :"#{hooked_method_name}" :"__proxied_instance_method_#{hooked_method}"
201
- else
202
- alias :"#{hooked_method_name}" :"#{hooked_method}"
203
- end
204
- def #{hooked_method}(*args, &block)
205
- #{before_hook_call}
206
- proxied_result = if block_given?
207
- __send__("#{hooked_method_name}", *args, &block)
208
- else
209
- __send__("#{hooked_method_name}", *args)
210
- end
211
- #{after_hook_call}
212
- proxied_result
213
- end
214
- REDEF
215
- end
216
- where_to_eval.class_eval(&class_eval_call)
217
- end
218
- end
219
-
220
- # Validates that the array consists only in the keys :before or/and :after
221
- #--
222
- # TODO: Add extensibility for possible keys. Ideas: warn_on_override, :dont_override, :execute_in_place
223
- #++
224
- def validate_hooks(hooks)
225
- raise InvalidHooksException.new("At least an :after or a :before hook are expected") if hooks.empty?
226
- unknown_keys = hooks.keys - [:before, :after]
227
- raise InvalidHooksException.new("Unknown keys #{unknown_keys.join(', ')}") unless unknown_keys.empty?
228
- end
229
-
230
- # Validates that the passed object is a class in :class and :instance modes
231
- def validate_is_class(what, mode)
232
- if mode == :class or mode == :instance
233
- raise NotAClassException.new("#{what} is not a Class") unless what.is_a? Class
234
- end
235
- end
236
-
237
- # Validates that the the method that will be hooked for the given object is defined.
238
- def validate_has_method_defined(holding_class, method, mode)
239
- unless holding_class.method_defined?(method)
240
- raise UndefinedMethodException.new("Undefined #{mode} method #{method.inspect} for #{holding_class}")
241
- end
242
- end
243
- end
244
- #++
245
-
246
- end
247
-
248
- ## ========== Auxiliar methods ==========
249
-
250
- class Peep
251
31
  # Completely removes all hooks for all defined classes and objects
252
32
  def self.clear_all_hooks!
253
- clear_all_class_hooks!
254
33
  clear_all_singleton_hooks!
255
34
  clear_all_instance_hooks!
35
+ clear_all_class_hooks!
256
36
  end
257
37
 
258
- #--
259
- class << self
260
- protected
261
-
262
- # TODO: Refactor lots of this
263
-
264
- def clear_all_class_hooks!
265
- Peep::CLASS_METHOD_HOOKS.each do |klass, hooked_methods|
266
- hooked_methods.each do |hooked_method, callbacks|
267
- klass.metaclass.class_eval <<-UNDEF_EVAL
268
- if method_defined?(:"__proxied_class_method_#{hooked_method}")
269
- alias :"#{hooked_method}" :"__proxied_class_method_#{hooked_method}"
270
- undef :"__proxied_class_method_#{hooked_method}"
271
- end
272
- UNDEF_EVAL
273
- end
274
- end
275
- Peep::CLASS_METHOD_HOOKS.clear
276
- end
277
-
278
- def clear_all_instance_hooks!
279
- Peep::INSTANCE_METHOD_HOOKS.each do |klass, hooked_methods|
280
- hooked_methods.each do |hooked_method, callbacks|
281
- klass.class_eval <<-UNDEF_EVAL
282
- if method_defined?(:"__proxied_instance_method_#{hooked_method}")
283
- alias :"#{hooked_method}" :"__proxied_instance_method_#{hooked_method}"
284
- undef :"__proxied_instance_method_#{hooked_method}"
285
- end
286
- UNDEF_EVAL
287
- end
288
- end
289
- Peep::INSTANCE_METHOD_HOOKS.clear
290
- end
291
-
292
- def clear_all_singleton_hooks!
293
- Peep::SINGLETON_METHOD_HOOKS.each do |object, hooked_methods|
294
- hooked_methods.each do |hooked_method, callbacks|
295
- object.metaclass.class_eval <<-UNDEF_EVAL
296
- if method_defined?(:"__proxied_singleton_method_#{hooked_method}")
297
- if method_defined?(:"__proxied_instance_method_#{hooked_method}")
298
- alias :"#{hooked_method}" :"__proxied_instance_method_#{hooked_method}"
299
- else
300
- alias :"#{hooked_method}" :"__proxied_singleton_method_#{hooked_method}"
301
- end
302
- undef :"__proxied_singleton_method_#{hooked_method}"
303
- end
304
- UNDEF_EVAL
305
- end
306
- end
307
- Peep::SINGLETON_METHOD_HOOKS.clear
308
- end
309
- end
310
- #++
38
+ end
311
39
 
312
- end
313
40
  end
314
41
 
data/lib/peeping.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  libdir = File.dirname(__FILE__)
2
2
  $:.unshift(libdir) unless $:.include?(libdir)
3
3
 
4
- require 'peeping/peeping'
5
-
4
+ require 'peeping/peeping'
data/peeping.gemspec ADDED
@@ -0,0 +1,62 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{peeping}
5
+ s.version = "1.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ruben Medellin"]
9
+ s.date = %q{2009-07-15}
10
+ s.description = %q{ Add, remove and manage hooks for class, instance and singleton method calls.
11
+ Intended to be not a full Aspect Oriented Programming framework, but a lightweight
12
+ simpler one.
13
+ }
14
+ s.email = %q{ruben.medellin.c@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.markdown",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/peeping.rb",
27
+ "lib/peeping/exceptions.rb",
28
+ "lib/peeping/hooks/class_hooks.rb",
29
+ "lib/peeping/hooks/hooking.rb",
30
+ "lib/peeping/hooks/instance_hooks.rb",
31
+ "lib/peeping/hooks/singleton_hooks.rb",
32
+ "lib/peeping/peeping.rb",
33
+ "lib/peeping/util.rb",
34
+ "peeping.gemspec",
35
+ "spec/helpers/test_helpers.rb",
36
+ "spec/hook_methods_spec.rb",
37
+ "spec/hooked_behavior_spec.rb",
38
+ "spec/selective_hook_management_spec.rb"
39
+ ]
40
+ s.has_rdoc = true
41
+ s.homepage = %q{http://github.com/chubas/peeping}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.requirements = ["none"]
45
+ s.rubygems_version = %q{1.3.2}
46
+ s.summary = %q{Lightweight AOP framework for managing method hooks}
47
+ s.test_files = [
48
+ "spec/hooked_behavior_spec.rb",
49
+ "spec/hook_methods_spec.rb",
50
+ "spec/selective_hook_management_spec.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ else
59
+ end
60
+ else
61
+ end
62
+ end
@@ -79,16 +79,17 @@ describe Peep, "when calling hook methods" do
79
79
 
80
80
  it "should always override instance method calls with singleton method calls" do
81
81
  scooby = Dog.new('scooby')
82
+ lassie = Dog.new('lassie')
83
+
82
84
  Peep.hook_object!(
83
85
  scooby,
84
86
  :lick_owner,
85
- :before => Proc.new{|object, *args| object.owner = "Shaggy" },
86
- :after => Proc.new{|object, result| result.should == "Shaggy is now all wet!" } )
87
- lassie = Dog.new('lassie')
87
+ :before => Proc.new{|object, *args| object.owner = "Shaggy" },
88
+ :after => Proc.new{|object, result| result.should == "Shaggy is now all wet!" } )
88
89
  Peep.hook_instances!(
89
90
  Dog,
90
91
  :lick_owner,
91
- :after => Proc.new{|objetc, result| result.should == "I have no owner to lick :(" } )
92
+ :after => Proc.new{|objetc, result| result.should == "I have no owner to lick :(" } )
92
93
  scooby.lick_owner
93
94
  lassie.lick_owner
94
95
  end
@@ -0,0 +1,190 @@
1
+ require 'spec'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'peeping')
3
+ require File.join(File.dirname(__FILE__), 'helpers', 'test_helpers')
4
+
5
+ include Peeping
6
+
7
+ describe Peep, "when adding and removing hooks" do
8
+
9
+ it_should_behave_like "a clean test"
10
+
11
+ it "should allow to remove class hook methods by method and location" do
12
+ before = Proc.new{}
13
+ after = Proc.new{}
14
+
15
+ Peep.hook_class!(Dog, [:train, :eats?], :before => before, :after => after)
16
+
17
+ hooked_class_methods = Peep.hooked_class_methods_for(Dog)
18
+ hooked_class_methods.should have(2).keys
19
+ hooked_class_methods.should include(:train)
20
+ hooked_class_methods.should include(:eats?)
21
+
22
+ hooked_class_methods[:train].should have(2).keys
23
+ hooked_class_methods[:train].should include(:before)
24
+ hooked_class_methods[:train].should include(:after)
25
+
26
+ Peep.unhook_class!(Dog, :train, :before)
27
+
28
+ hooked_class_methods = Peep.hooked_class_methods_for(Dog)
29
+ hooked_class_methods.should have(2).keys
30
+ hooked_class_methods.keys.should include(:train)
31
+ hooked_class_methods.keys.should include(:eats?)
32
+
33
+ hooked_class_methods[:train].should have(1).keys
34
+ hooked_class_methods[:train].should_not include(:before)
35
+ hooked_class_methods[:train].should include(:after)
36
+
37
+ Proc.new{ Peep.unhook_class! Dog, :no_such_hook }.should raise_error UndefinedHookException
38
+ Proc.new{ Peep.unhook_class! Dog, :train, :before }.should raise_error UndefinedHookException
39
+
40
+ Peep.unhook_class!(Dog, :train, :after)
41
+
42
+ hooked_class_methods = Peep.hooked_class_methods_for(Dog)
43
+ hooked_class_methods.should have(1).keys
44
+ hooked_class_methods.keys.should_not include(:train)
45
+ hooked_class_methods.keys.should include(:eats?)
46
+
47
+ Peep.unhook_class!(Dog)
48
+
49
+ hooked_class_methods.should have(0).keys
50
+ Peep.is_class_hooked?(Dog).should == false
51
+ end
52
+
53
+
54
+ it "should allow to remove instance hook methods by method and location" do
55
+ before = Proc.new{}
56
+ after = Proc.new{}
57
+
58
+ Peep.hook_instances!(Dog, [:bark, :lick_owner], :before => before, :after => after)
59
+
60
+ hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
61
+ hooked_instance_methods.should have(2).keys
62
+ hooked_instance_methods.should include(:bark)
63
+ hooked_instance_methods.should include(:lick_owner)
64
+
65
+ hooked_instance_methods[:bark].should have(2).keys
66
+ hooked_instance_methods[:bark].should include(:before)
67
+ hooked_instance_methods[:bark].should include(:after)
68
+
69
+ Peep.unhook_instances!(Dog, :bark, :before)
70
+
71
+ hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
72
+ hooked_instance_methods.should have(2).keys
73
+ hooked_instance_methods.keys.should include(:bark)
74
+ hooked_instance_methods.keys.should include(:lick_owner)
75
+
76
+ hooked_instance_methods[:bark].should have(1).keys
77
+ hooked_instance_methods[:bark].should_not include(:before)
78
+ hooked_instance_methods[:bark].should include(:after)
79
+
80
+ Proc.new{ Peep.unhook_instances! Dog, :no_such_hook }.should raise_error UndefinedHookException
81
+ Proc.new{ Peep.unhook_instances! Dog, :bark, :before }.should raise_error UndefinedHookException
82
+
83
+ Peep.unhook_instances!(Dog, :bark, :after)
84
+
85
+ hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
86
+ hooked_instance_methods.should have(1).keys
87
+ hooked_instance_methods.keys.should_not include(:bark)
88
+ hooked_instance_methods.keys.should include(:lick_owner)
89
+
90
+ Peep.unhook_instances!(Dog)
91
+
92
+ hooked_instance_methods.should have(0).keys
93
+ end
94
+
95
+ it "should allow to remove singleton hook methods by method and location" do
96
+ before = Proc.new{}
97
+ after = Proc.new{}
98
+ scooby = Dog.new('scooby')
99
+
100
+ Peep.hook_object!(scooby, [:bark, :lick_owner], :before => before, :after => after)
101
+
102
+ hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
103
+ hooked_singleton_methods.should have(2).keys
104
+ hooked_singleton_methods.should include(:bark)
105
+ hooked_singleton_methods.should include(:lick_owner)
106
+
107
+ hooked_singleton_methods[:bark].should have(2).keys
108
+ hooked_singleton_methods[:bark].should include(:before)
109
+ hooked_singleton_methods[:bark].should include(:after)
110
+
111
+ Peep.unhook_object!(scooby, :bark, :before)
112
+
113
+ hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
114
+ hooked_singleton_methods.should have(2).keys
115
+ hooked_singleton_methods.keys.should include(:bark)
116
+ hooked_singleton_methods.keys.should include(:lick_owner)
117
+
118
+ hooked_singleton_methods[:bark].should have(1).keys
119
+ hooked_singleton_methods[:bark].should_not include(:before)
120
+ hooked_singleton_methods[:bark].should include(:after)
121
+
122
+ Proc.new{ Peep.unhook_object! scooby, :no_such_hook }.should raise_error UndefinedHookException
123
+ Proc.new{ Peep.unhook_object! scooby, :bark, :before }.should raise_error UndefinedHookException
124
+
125
+ Peep.unhook_object!(scooby, :bark, :after)
126
+
127
+ hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
128
+ hooked_singleton_methods.should have(1).keys
129
+ hooked_singleton_methods.keys.should_not include(:bark)
130
+ hooked_singleton_methods.keys.should include(:lick_owner)
131
+
132
+ Peep.unhook_object!(scooby)
133
+
134
+ hooked_singleton_methods.should have(0).keys
135
+ end
136
+
137
+ it "should keep the instance hook method when removing any singleton hook method" do
138
+ scooby = Dog.new('scooby')
139
+ lassie = Dog.new('lassie')
140
+
141
+ before_instance_counter = 0
142
+ after_instance_counter = 0
143
+ before_object_counter = 0
144
+ after_object_counter = 0
145
+
146
+ counters_should_be = Proc.new do |bic, aic, boc, aoc|
147
+ before_instance_counter.should == bic
148
+ after_instance_counter.should == aic
149
+ before_object_counter.should == boc
150
+ after_object_counter.should == aoc
151
+ end
152
+
153
+ Peep.hook_instances!(Dog, :lick_owner,
154
+ :before => Proc.new{ before_instance_counter += 1},
155
+ :after => Proc.new{ after_instance_counter += 1} )
156
+ Peep.hook_object!(scooby, :lick_owner,
157
+ :before => Proc.new{ before_object_counter += 1},
158
+ :after => Proc.new{ after_object_counter += 1} )
159
+
160
+ scooby.lick_owner
161
+ counters_should_be[0, 0, 1, 1]
162
+
163
+ lassie.lick_owner
164
+ counters_should_be[1, 1, 1, 1]
165
+
166
+ Peep.unhook_object!(scooby, :lick_owner, :before)
167
+ scooby.lick_owner
168
+ counters_should_be[2, 1, 1, 2]
169
+ lassie.lick_owner
170
+ counters_should_be[3, 2, 1, 2]
171
+
172
+ Peep.unhook_instances!(Dog, :lick_owner, :after)
173
+ scooby.lick_owner
174
+ counters_should_be[4, 2, 1, 3]
175
+ lassie.lick_owner
176
+ counters_should_be[5, 2, 1, 3]
177
+
178
+ Peep.unhook_object!(scooby, :lick_owner, :after)
179
+ scooby.lick_owner
180
+ counters_should_be[6, 2, 1, 3]
181
+ lassie.lick_owner
182
+ counters_should_be[7, 2, 1, 3]
183
+
184
+ Peep.unhook_instances!(Dog, :lick_owner)
185
+ scooby.lick_owner
186
+ lassie.lick_owner
187
+ counters_should_be[7, 2, 1, 3]
188
+ end
189
+
190
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chubas-peeping
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruben Medellin
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-12 00:00:00 -07:00
12
+ date: 2009-07-15 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -31,12 +31,17 @@ files:
31
31
  - VERSION
32
32
  - lib/peeping.rb
33
33
  - lib/peeping/exceptions.rb
34
- - lib/peeping/hook.rb
34
+ - lib/peeping/hooks/class_hooks.rb
35
+ - lib/peeping/hooks/hooking.rb
36
+ - lib/peeping/hooks/instance_hooks.rb
37
+ - lib/peeping/hooks/singleton_hooks.rb
35
38
  - lib/peeping/peeping.rb
36
39
  - lib/peeping/util.rb
40
+ - peeping.gemspec
37
41
  - spec/helpers/test_helpers.rb
38
42
  - spec/hook_methods_spec.rb
39
43
  - spec/hooked_behavior_spec.rb
44
+ - spec/selective_hook_management_spec.rb
40
45
  has_rdoc: true
41
46
  homepage: http://github.com/chubas/peeping
42
47
  post_install_message:
@@ -66,3 +71,4 @@ summary: Lightweight AOP framework for managing method hooks
66
71
  test_files:
67
72
  - spec/hooked_behavior_spec.rb
68
73
  - spec/hook_methods_spec.rb
74
+ - spec/selective_hook_management_spec.rb
data/lib/peeping/hook.rb DELETED
@@ -1,8 +0,0 @@
1
- module Peeping
2
-
3
- # PENDING
4
- # This class will serve as a proxy for dynamically managing hooked methods
5
- class Hook < Hash
6
- end
7
-
8
- end