chubas-peeping 1.0.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .idea
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ruben Medellin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,74 @@
1
+ # peeping
2
+
3
+ Like aspect-oriented, but cooler!
4
+
5
+ ## Motivation
6
+
7
+ This library intends to provide a simple yet flexible way for defining hook calls for your methods,
8
+ without trying to be a complete aspect-oriented programming framework implementation.
9
+
10
+ ## Installation
11
+
12
+ Just point to the root directory of the library in your require path and include `peeping.rb` (Gem packaging soon)
13
+
14
+ ## Usage
15
+
16
+ For defining hooks, use the methods of `Peeping::Peep` class
17
+
18
+ include Peeping
19
+
20
+ class Foo
21
+ def some_instance_method
22
+ puts "Hi there!"
23
+ end
24
+ def self.some_class_method
25
+ puts "Cool!"
26
+ end
27
+ end
28
+
29
+ foo = Foo.new
30
+
31
+ Peep.hook_class!(Foo, :some_class_method,
32
+ :before => Proc.new{ puts "Before class method" },
33
+ :after => Proc.new{ puts "After class method" })
34
+ Peep.hook_instances!(Foo, :some_instance_method,
35
+ :before => Proc.new{ puts "Before instance method" },
36
+ :after => Proc.new{ puts "After instance method" })
37
+ Peep.hook_object!(foo, :some_instance_method,
38
+ :before => Proc.new{ puts "Before singleton instance method" },
39
+ :after => Proc.new{ puts "After singleton instance method" })
40
+
41
+ Foo.some_class_method
42
+ Foo.new.some_instance_method
43
+ foo.some_instance_method
44
+
45
+ produces output:
46
+
47
+ Before class method
48
+ Cool!
49
+ After class method
50
+ Before instance method
51
+ Hi there!
52
+ After instance method
53
+ Before singleton instance method
54
+ Hi there!
55
+ After singleton instance method
56
+
57
+
58
+ [See documentation online][1] and TODO notes for more info
59
+
60
+ ## Tests
61
+
62
+ The library comes with its rspec test suite, located in folder _tests_
63
+
64
+ ## Updates
65
+
66
+ ## TODO
67
+
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
73
+
74
+ [1]: http://rdoc.info/projects/chubas/peeping
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+
8
+ gem.name = "peeping"
9
+ gem.email = "ruben.medellin.c@gmail.com"
10
+ gem.summary = %Q{Lightweight AOP framework for managing method hooks}
11
+ gem.description = <<-DESCRIPTION
12
+ Add, remove and manage hooks for class, instance and singleton method calls.
13
+ Intended to be not a full Aspect Oriented Programming framework, but a lightweight
14
+ simpler one.
15
+ DESCRIPTION
16
+
17
+ gem.homepage = "http://github.com/chubas/peeping"
18
+ gem.authors = ["Ruben Medellin"]
19
+
20
+ gem.requirements << 'none'
21
+ gem.require_path = 'lib'
22
+
23
+ gem.has_rdoc = true
24
+ gem.test_files = Dir.glob('spec/*.rb')
25
+
26
+ end
27
+
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
30
+ end
31
+
32
+ task :default => :test
33
+ require 'rake/rdoctask'
34
+ Rake::RDocTask.new do |rdoc|
35
+ if File.exist?('VERSION.yml')
36
+ config = YAML.load(File.read('VERSION.yml'))
37
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
38
+ elsif File.exist?('VERSION')
39
+ version = File.read('VERSION')
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "peeping #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/peeping.rb ADDED
@@ -0,0 +1,5 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $:.unshift(libdir) unless $:.include?(libdir)
3
+
4
+ require 'peeping/peeping'
5
+
@@ -0,0 +1,6 @@
1
+ module Peeping
2
+ class InvalidHooksException < Exception; end
3
+ class UndefinedMethodException < Exception; end
4
+ class NotAClassException < Exception; end
5
+ class AlreadyDefinedHookException < Exception; end
6
+ end
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,314 @@
1
+ # Peeping is sort of an aspect-oriented programming module -- just without the fancy name
2
+ # It allows to hook class, instance and singleton methods for adding -and nicely managing-
3
+ # before and after procedures.
4
+ #
5
+ # Author:: Ruben Medellin (mailto:ruben.medellin.c@gmail.com)
6
+ # License:: Distributes under the same terms as Ruby
7
+
8
+ require 'peeping/util'
9
+ require 'peeping/hook'
10
+ require 'peeping/exceptions'
11
+
12
+ # Wraps classes and modules for the +peeping+ library
13
+ module Peeping
14
+
15
+ # This class defines the methods for hooking and unhooking methods, as well as contains the variables that
16
+ # hold the hook definitions
17
+ class Peep
18
+
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
72
+
73
+ #--
74
+ ## ======================================================
75
+ ## =============== Instance hooks section ===============
76
+ ## ======================================================
77
+ #++
78
+
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
+ # Completely removes all hooks for all defined classes and objects
252
+ def self.clear_all_hooks!
253
+ clear_all_class_hooks!
254
+ clear_all_singleton_hooks!
255
+ clear_all_instance_hooks!
256
+ end
257
+
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
+ #++
311
+
312
+ end
313
+ end
314
+
@@ -0,0 +1,13 @@
1
+ #--
2
+ # Utility methods
3
+
4
+ class Object
5
+
6
+ # In case it has not been defined before
7
+ def metaclass
8
+ class << self
9
+ self
10
+ end
11
+ end unless method_defined?(:metaclass)
12
+ end
13
+ #++
@@ -0,0 +1,42 @@
1
+ require 'spec'
2
+
3
+ shared_examples_for 'a clean test' do
4
+ before{ Peeping::Peep.clear_all_hooks! }
5
+ after { Peeping::Peep.clear_all_hooks! }
6
+ end
7
+
8
+ #--
9
+ # Dummy class, used for testing
10
+ class Dog
11
+
12
+ attr_accessor :name, :trained, :owner
13
+ attr_accessor :a, :b
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ end
18
+
19
+ def bark(times)
20
+ (["woof"] * times).join(' ') + "!"
21
+ end
22
+
23
+ def lick_owner
24
+ if @owner
25
+ "#{@owner} is now all wet!"
26
+ else
27
+ "I have no owner to lick :("
28
+ end
29
+ end
30
+
31
+ def self.train(dog)
32
+ dog.trained = true
33
+ dog
34
+ end
35
+
36
+ def self.eats?(thing)
37
+ %w{meat bones milk}.include?(thing)
38
+ end
39
+
40
+ end
41
+ #++
42
+
@@ -0,0 +1,139 @@
1
+ require 'spec'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'peeping')
3
+ require File.join(File.dirname(__FILE__), 'helpers', 'test_helpers')
4
+
5
+
6
+ include Peeping
7
+
8
+ describe Peep, "determining if objects are hooked, and get hooks for class or object" do
9
+
10
+ it_should_behave_like "a clean test"
11
+
12
+ it "should allow me to determine wether a class is hooked" do
13
+ include Peeping
14
+ Peep.should respond_to(:is_class_hooked?)
15
+ Peep.should respond_to(:hooked_class_methods_for)
16
+ end
17
+
18
+ it "should allow me to determine if all instances of a class are hooked" do
19
+ Peep.should respond_to(:are_instances_hooked?)
20
+ Peep.should respond_to(:hooked_instance_methods_for)
21
+ end
22
+
23
+ it "should allow me to determine if a single object is hooked" do
24
+ Peep.should respond_to(:is_hooked?)
25
+ Peep.should respond_to(:hooked_singleton_methods_for)
26
+ end
27
+
28
+ end
29
+
30
+ describe Peep, "when hooking and unhooking objects" do
31
+
32
+ it_should_behave_like "a clean test"
33
+
34
+ it "should allow to me to hook and unhook classes" do
35
+ Peep.is_class_hooked?(Dog).should == false
36
+ Peep.hooked_class_methods_for(Dog).should be_empty
37
+
38
+ after_call = Proc.new{|klass, result| result}
39
+ Peep.hook_class!(Dog, :train, :after => after_call)
40
+
41
+ Peep.is_class_hooked?(Dog).should == true
42
+ Peep.hooked_class_methods_for(Dog).should_not be_empty
43
+ Peep.hooked_class_methods_for(Dog).should have(1).keys
44
+ Peep.hooked_class_methods_for(Dog)[:train][:after].should == after_call
45
+
46
+ Peep.unhook_class!(Dog)
47
+
48
+ Peep.is_class_hooked?(Dog).should == false
49
+ Peep.hooked_class_methods_for(Dog).should be_empty
50
+ end
51
+
52
+ it "should allow me to hook and unhook instance methods" do
53
+ Peep.are_instances_hooked?(Dog).should == false
54
+ Peep.hooked_instance_methods_for(Dog).should be_empty
55
+
56
+ after_call = Proc.new{|klass, result| result}
57
+ Peep.hook_instances!(Dog, :bark, :after => after_call)
58
+
59
+ Peep.are_instances_hooked?(Dog).should == true
60
+ Peep.hooked_instance_methods_for(Dog).should_not be_empty
61
+ Peep.hooked_instance_methods_for(Dog).should have(1).keys
62
+ Peep.hooked_instance_methods_for(Dog)[:bark][:after].should == after_call
63
+
64
+ Peep.unhook_instances!(Dog)
65
+
66
+ Peep.are_instances_hooked?(Dog).should == false
67
+ Peep.hooked_instance_methods_for(Dog).should be_empty
68
+ end
69
+
70
+ it "should allow me to hook and unhook single objects" do
71
+ scooby = Dog.new("scooby")
72
+ Peep.is_hooked?(scooby).should == false
73
+
74
+ after_call = Proc.new{|klass, result| result}
75
+ Peep.hook_object!(scooby, :bark, :after => after_call)
76
+
77
+ Peep.is_hooked?(scooby).should == true
78
+ Peep.hooked_singleton_methods_for(scooby).should_not be_empty
79
+ Peep.hooked_singleton_methods_for(scooby).should have(1).keys
80
+ Peep.hooked_singleton_methods_for(scooby)[:bark][:after].should == after_call
81
+
82
+ Peep.are_instances_hooked?(Dog).should == false
83
+ Peep.hooked_instance_methods_for(Dog).should be_empty
84
+
85
+ Peep.unhook_object!(scooby)
86
+
87
+ Peep.is_hooked?(scooby).should == false
88
+ Peep.hooked_singleton_methods_for(scooby).should be_empty
89
+ end
90
+ end
91
+
92
+ describe Peep, "when sending valid and unvalid parameters" do
93
+
94
+ it_should_behave_like "a clean test"
95
+
96
+ it "should only accept valid parameters for hooks" do
97
+ scooby = Dog.new("scooby")
98
+ invalid_keys = { :invalid => :key }
99
+
100
+ Proc.new{ Peep.hook_class! Dog, :hello, invalid_keys }.should raise_error InvalidHooksException
101
+ Proc.new{ Peep.hook_class! Dog, :hello, {} }.should raise_error InvalidHooksException
102
+
103
+ Proc.new{ Peep.hook_instances! Dog, :hello, invalid_keys }.should raise_error InvalidHooksException
104
+ Proc.new{ Peep.hook_instances! Dog, :hello, {} }.should raise_error InvalidHooksException
105
+
106
+ Proc.new{ Peep.hook_object! scooby, :hello, invalid_keys }.should raise_error InvalidHooksException
107
+ Proc.new{ Peep.hook_object! scooby, :hello, {} }.should raise_error InvalidHooksException
108
+ end
109
+
110
+ it "should validate that methods are defined" do
111
+ p = Proc.new{|klass, result| result }
112
+ scooby = Dog.new("scooby")
113
+
114
+ Proc.new{ Peep.hook_class! Dog, :dance, :after => p }.should raise_error UndefinedMethodException
115
+ Proc.new{ Peep.hook_instances! Dog, :dance, :after => p }.should raise_error UndefinedMethodException
116
+ Proc.new{ Peep.hook_object! scooby, :dance, :after => p }.should raise_error UndefinedMethodException
117
+ end
118
+
119
+ it "should validate that passed objects are classes for class and instance hooks" do
120
+ p = Proc.new{|klass, result| result}
121
+ Proc.new{ Peep.hook_class! :not_a_class, :hello, :after => p }.should raise_error NotAClassException
122
+ Proc.new{ Peep.hook_instances! :not_a_class, :hello, :after => p }.should raise_error NotAClassException
123
+ end
124
+
125
+ it "should rise an error if a hook is declared twice" do
126
+ p = Proc.new{|klass, result| result}
127
+ Peep.hook_class! Dog, :train, :after => p
128
+ Proc.new{ Peep.hook_class! Dog, :train, :after => p }.should raise_error AlreadyDefinedHookException
129
+
130
+ Peep.hook_instances! Dog, :bark, :after => p
131
+ Proc.new{ Peep.hook_instances! Dog, :bark, :after => p }.should raise_error AlreadyDefinedHookException
132
+
133
+ scooby = Dog.new("scooby")
134
+ Peep.hook_object! scooby, :bark, :after => p
135
+ Proc.new{ Peep.hook_object! scooby, :bark, :after => p }.should raise_error AlreadyDefinedHookException
136
+ end
137
+
138
+ end
139
+
@@ -0,0 +1,165 @@
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 calling hook methods" do
8
+
9
+ it_should_behave_like "a clean test"
10
+
11
+ it "should evaluate class proc calls" do
12
+ before_class_hook_called = false
13
+ after_class_hook_called = false
14
+ Peep.hook_class!(
15
+ Dog,
16
+ :eats?,
17
+ :before => Proc.new{|*args| before_class_hook_called = true },
18
+ :after => Proc.new{|*args| after_class_hook_called = true } )
19
+ Dog.eats?('french fries')
20
+ before_class_hook_called.should == true
21
+ after_class_hook_called.should == true
22
+ end
23
+
24
+ it "should evaluate instance proc calls" do
25
+ before_instance_hook_called = false
26
+ after_instance_hook_called = false
27
+ Peep.hook_instances!(
28
+ Dog,
29
+ :bark,
30
+ :before => Proc.new{|*args| before_instance_hook_called = true},
31
+ :after => Proc.new{|*args| after_instance_hook_called = true} )
32
+ Dog.new('scooby').bark(10)
33
+ before_instance_hook_called.should == true
34
+ after_instance_hook_called.should == true
35
+ end
36
+
37
+ it "should evaluate singleton instance proc calls" do
38
+ before_singleton_hook_called = false
39
+ after_singleton_hook_called = false
40
+ scooby = Dog.new('scooby')
41
+ Peep.hook_object!(
42
+ scooby,
43
+ :bark,
44
+ :before => Proc.new{|*args| before_singleton_hook_called = true},
45
+ :after => Proc.new{|*args| after_singleton_hook_called = true} )
46
+ scooby.bark(10)
47
+ before_singleton_hook_called.should == true
48
+ after_singleton_hook_called.should == true
49
+ end
50
+
51
+ it "should evaluate singleton hooks for singleton methods instead of instance methods" do
52
+ scooby = Dog.new('scooby')
53
+ class << scooby
54
+ def eat_scooby_snacks(how_many)
55
+ if how_many > 5
56
+ @mood = :happy
57
+ "Scooby doobee doo!"
58
+ else
59
+ @mood = :hungry
60
+ "I'm hungry, give me more!"
61
+ end
62
+ end
63
+ end
64
+
65
+ Peep.hook_object!(
66
+ scooby,
67
+ :eat_scooby_snacks,
68
+ :before => (Proc.new do |object, *params|
69
+ object.instance_variable_get(:@mood).should == nil
70
+ params.should == [10]
71
+ end),
72
+ :after => (Proc.new do |object, result|
73
+ object.should == scooby
74
+ result.should == "Scooby doobee doo!"
75
+ object.instance_variable_get(:@mood).should == :happy
76
+ end))
77
+ scooby.eat_scooby_snacks(10)
78
+ end
79
+
80
+ it "should always override instance method calls with singleton method calls" do
81
+ scooby = Dog.new('scooby')
82
+ Peep.hook_object!(
83
+ scooby,
84
+ :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')
88
+ Peep.hook_instances!(
89
+ Dog,
90
+ :lick_owner,
91
+ :after => Proc.new{|objetc, result| result.should == "I have no owner to lick :(" } )
92
+ scooby.lick_owner
93
+ lassie.lick_owner
94
+ end
95
+
96
+ end
97
+
98
+ describe Peep, "when using parameters in block calls" do
99
+
100
+ it_should_behave_like "a clean test"
101
+
102
+ it "should accept the same parameters in before hooks as the hooked class methods" do
103
+ Peep.hook_class!(
104
+ Dog,
105
+ :eats?,
106
+ :before => (Proc.new do |klass, *params|
107
+ klass.should == Dog
108
+ params.should == ['meat']
109
+ end))
110
+ Dog.eats?('meat')
111
+ end
112
+
113
+ it "should accept the same parameters in before hooks as the hooked instance methods" do
114
+ scooby = Dog.new('scooby')
115
+ Peep.hook_instances!(
116
+ Dog,
117
+ :bark,
118
+ :before => (Proc.new do |object, *params|
119
+ object.should == scooby
120
+ params.should == [3]
121
+ end))
122
+ scooby.bark(3)
123
+ end
124
+
125
+ it "should accept the same parameters in before hooks as the hooked singleton method" do
126
+ scooby = Dog.new('scooby')
127
+ Peep.hook_object!(
128
+ scooby,
129
+ :bark,
130
+ :before => (Proc.new do |object, *params|
131
+ params.should == [4]
132
+ object.should == scooby
133
+ end))
134
+ scooby.bark(4)
135
+ end
136
+
137
+ it "should return the same object after a hooked class method call" do
138
+ Peep.hook_class!(
139
+ Dog,
140
+ :eats?,
141
+ :after => Proc.new{|klass, result| result.should == true } )
142
+ Dog.eats?('bones')
143
+ end
144
+
145
+ it "hould return the same object after a hooked instance method call" do
146
+ scooby = Dog.new('scooby')
147
+ Peep.hook_instances!(
148
+ Dog,
149
+ :bark,
150
+ :after => Proc.new{|klass, result| result.should == "woof woof woof!" } )
151
+ scooby.bark(3)
152
+ end
153
+
154
+ it "should return the same object after a hooked singleton method call" do
155
+ scooby = Dog.new('scooby')
156
+ Peep.hook_object!(
157
+ scooby,
158
+ :lick_owner,
159
+ :before => Proc.new{|object, *args| object.owner = "John" },
160
+ :after => Proc.new{|object, result| result.should == "John is now all wet!" })
161
+ scooby.lick_owner
162
+ end
163
+
164
+ end
165
+
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chubas-peeping
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ruben Medellin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Add, remove and manage hooks for class, instance and singleton method calls. Intended to be not a full Aspect Oriented Programming framework, but a lightweight simpler one.
17
+ email: ruben.medellin.c@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.markdown
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.markdown
30
+ - Rakefile
31
+ - VERSION
32
+ - lib/peeping.rb
33
+ - lib/peeping/exceptions.rb
34
+ - lib/peeping/hook.rb
35
+ - lib/peeping/peeping.rb
36
+ - lib/peeping/util.rb
37
+ - spec/helpers/test_helpers.rb
38
+ - spec/hook_methods_spec.rb
39
+ - spec/hooked_behavior_spec.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/chubas/peeping
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements:
60
+ - none
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Lightweight AOP framework for managing method hooks
66
+ test_files:
67
+ - spec/hooked_behavior_spec.rb
68
+ - spec/hook_methods_spec.rb