chubas-peeping 1.0.0 → 1.1.0

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