after_do 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +3 -3
- data/Gemfile +7 -0
- data/Guardfile +9 -0
- data/README.md +230 -12
- data/after_do.gemspec +0 -6
- data/lib/after_do/version.rb +1 -1
- data/lib/after_do.rb +97 -39
- data/samples/before.rb +19 -0
- data/samples/dog.rb +2 -7
- data/samples/error_in_callback.rb +12 -0
- data/samples/getting_a_hold.rb +33 -0
- data/samples/inheritance.rb +16 -0
- data/samples/within_class.rb +29 -0
- data/spec/after_do_spec.rb +195 -85
- data/spec/spec_helper.rb +6 -2
- metadata +10 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 246d1c1ca42b79e8fedf494f8b9ef5620a88e998
|
4
|
+
data.tar.gz: 591190d78158bb4a97db932ac0481bad656cb35c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3b639f1aa8bc684b30b8c1b16026bf2406c98872563ff10aadad02177f47715c8f9eec270409fdbbd9213087323cebd5b38b566308cb12e5b02c9fbf0585321
|
7
|
+
data.tar.gz: b05353aee06b2b7ad93347ad7fdf5284b46529b984ddc58411a10b374437a2e91907d91cd925cda6aba614671bbdaf55fbb42e20e796f26320d4e22a335749cc
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.0.0-
|
1
|
+
ruby-2.0.0-p247
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
# AfterDo [![Build Status](https://travis-ci.org/PragTob/after_do.png?branch=master)](https://travis-ci.org/PragTob/after_do)[![Code Climate](https://codeclimate.com/github/PragTob/after_do.png)](https://codeclimate.com/github/PragTob/after_do)
|
1
|
+
# AfterDo [![Gem Version](https://badge.fury.io/rb/after_do.png)](http://badge.fury.io/rb/after_do)[![Build Status](https://travis-ci.org/PragTob/after_do.png?branch=master)](https://travis-ci.org/PragTob/after_do)[![Code Climate](https://codeclimate.com/github/PragTob/after_do.png)](https://codeclimate.com/github/PragTob/after_do)[![Coverage Status](https://coveralls.io/repos/PragTob/after_do/badge.png)](https://coveralls.io/r/PragTob/after_do)
|
2
2
|
|
3
|
-
AfterDo is simple gem, that allows you to execute a specified block after specified method of a class are called. If the class extends `AfterDo` you can simply do this by
|
3
|
+
AfterDo is simple gem, that allows you to execute a specified block after specified method of a class are called. If the class extends `AfterDo` you can simply do this by
|
4
4
|
|
5
|
-
|
5
|
+
```
|
6
|
+
MyClass.after :some_method do whatever_you_want end
|
7
|
+
```
|
6
8
|
|
7
|
-
|
9
|
+
Why would you want to do this? Well to fight cross-cutting concerns such as logging. E.g. there are concerns in an applications that apply to multiple objects (e.g. they cross-cut). A popular example is logging - you might want to log multiple actions but logging is not the primary concern of the class in question. With logging you litter all your code with logging statements - that concern is spread over many files. With AfterDo you could put all the logging in one file. Other use cases include gathering business statistics or redrawing timing of elements. Personally I extracted this gem from a project where I wanted to decouple my domain objects from the way they are saved (for fun and profit!).
|
10
|
+
This should generally not be done to alter behavior of the class and its instances - this makes programs more confusing rather than easier to understand.
|
11
|
+
|
12
|
+
AfterDo has no external runtime dependencies and the code is around 120 lines of code (blank lines included) with lots of small methods. So simplecov reports there are not even 70 relevant lines.
|
8
13
|
|
9
14
|
## Installation
|
10
15
|
|
@@ -22,17 +27,24 @@ Or install it yourself as:
|
|
22
27
|
|
23
28
|
## Usage
|
24
29
|
|
25
|
-
|
30
|
+
This section is dedicated to show of the general usage and effects of AfterDo. You can also check out the samples directory for some samples or the specs in the spec folder.
|
31
|
+
|
32
|
+
### General usage
|
33
|
+
|
34
|
+
In order to use AfterDo the class/module you want to use it with first has to extend the `AfterDo` module. You can do this right in the class definition or afterwards like this: `MyClass.extend AfterDo`.
|
35
|
+
With this setup you can add a callback to `method` like this:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
MyClass.after :method do magic end
|
39
|
+
```
|
40
|
+
|
41
|
+
With AfterDo you can do simple things like printing something out every time a method is called as in this example:
|
26
42
|
|
27
43
|
```ruby
|
28
44
|
class Dog
|
29
45
|
def bark
|
30
46
|
puts 'Woooof'
|
31
47
|
end
|
32
|
-
|
33
|
-
def eat
|
34
|
-
puts 'yummie!'
|
35
|
-
end
|
36
48
|
end
|
37
49
|
|
38
50
|
Dog.extend AfterDo
|
@@ -42,26 +54,232 @@ dog = Dog.new
|
|
42
54
|
dog2 = Dog.new
|
43
55
|
|
44
56
|
dog.bark
|
45
|
-
dog.eat
|
46
57
|
dog2.bark
|
47
58
|
|
48
59
|
# Output is:
|
49
60
|
# Woooof
|
50
61
|
# I just heard a dog bark!
|
51
|
-
# yummie!
|
52
62
|
# Woooof
|
53
63
|
# I just heard a dog bark!
|
54
64
|
|
55
65
|
```
|
56
66
|
|
67
|
+
### How does it work?
|
68
|
+
|
69
|
+
When you attach a callback to a method with AfterDo what it basically does is it creates a copy of that method and then redefines the method to basically look like this (pseudo code):
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
execute_before_callbacks
|
73
|
+
return_value = original_method
|
74
|
+
execute_after_callbacks
|
75
|
+
return_value
|
76
|
+
```
|
77
|
+
|
78
|
+
To do this some helper methods are defined in the AfterDo module. As classes have to extend the AfterDo module all the methods that you are not supposed to call yourself are prefixed with `_after_do_` to minimize the risk of method name clashes. The only not prefixed method are `after`, `before` and `remove_all_callbacks`.
|
79
|
+
|
80
|
+
### Getting a hold of the method arguments and the object
|
81
|
+
|
82
|
+
With AfterDo both the arguments to the method you are attaching the callback to and the object for which the callback is executed are passed into the callback block.
|
83
|
+
|
84
|
+
So if you have a method that takes two arguments you can get those like this:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
MyClass.after :two_arg_method do |argument_one, argument_2| something end
|
88
|
+
```
|
89
|
+
|
90
|
+
The object itself is passed in as the last block argument, so if you just care about the object you can do:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
MyClass.after :two_arg_method do |*, obj| fancy_stuff(obj) something end
|
94
|
+
```
|
95
|
+
|
96
|
+
Of course you can get a hold of the method arguments and the object:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
MyClass.after :two_arg_method do |arg1, arg2, obj| something(arg1, arg2, obj) end
|
100
|
+
```
|
101
|
+
|
102
|
+
If you do not want to get a hold of the method arguments or the object, then you can just don't care about the block parameters :-)
|
103
|
+
|
104
|
+
Here is an example showcasing all of these:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class Example
|
108
|
+
def zero
|
109
|
+
# ...
|
110
|
+
end
|
111
|
+
|
112
|
+
def two(a, b)
|
113
|
+
# ...
|
114
|
+
end
|
115
|
+
|
116
|
+
def value
|
117
|
+
'some value'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Example.extend AfterDo
|
122
|
+
|
123
|
+
Example.after :zero do puts 'Hello!' end
|
124
|
+
Example.after :zero do |obj| puts obj.value end
|
125
|
+
Example.after :two do |first, second| puts first + ' ' + second end
|
126
|
+
Example.after :two do |a, b, obj| puts a + ' ' + b + ' ' + obj.value end
|
127
|
+
Example.after :two do |*, obj| puts 'just ' + obj.value end
|
128
|
+
|
129
|
+
e = Example.new
|
130
|
+
e.zero
|
131
|
+
e.two 'one', 'two'
|
132
|
+
# prints:
|
133
|
+
# Hello!
|
134
|
+
# some value
|
135
|
+
# one two
|
136
|
+
# one two some value
|
137
|
+
# just some value
|
138
|
+
```
|
139
|
+
|
140
|
+
### Attaching a callback to multiple methods
|
141
|
+
|
142
|
+
In AfterDo you can attach a callback to multiple methods by just listing them:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
SomeClass.after :one_method, :another_method do something end
|
146
|
+
```
|
147
|
+
|
148
|
+
Or you could pass in an Array of method names:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
SomeClass.after [:one_method, :another_method] do something end
|
152
|
+
```
|
153
|
+
|
154
|
+
So for example if you have an activity and want the activity to be saved every time you change it, but you don't want to mix that persistence concern with what the activity actually does you could do something like this:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
persistor = FilePersistor.new
|
158
|
+
Activity.extend AfterDo
|
159
|
+
Activity.after :start, :pause, :finish, :resurrect,
|
160
|
+
:do_today, :do_another_day do |activity|
|
161
|
+
persistor.save activity
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
Doesn't that seem a lot drier then calling some save method manually after each of those in addition to separating the concerns?
|
166
|
+
|
167
|
+
### Attaching multiple callbacks to the same method
|
168
|
+
|
169
|
+
A method can have as many callbacks as a Ruby Array can handle (although I do not recommend you to have many callbacks around). So this works perfectly fine:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
MyClass.after :method do something end
|
173
|
+
MyClass.after :method do another_thing end
|
174
|
+
```
|
175
|
+
|
176
|
+
The callbacks are executed in the order in which they were added.
|
177
|
+
|
178
|
+
### Working with inheritance
|
179
|
+
|
180
|
+
AfterDo also works with inheritance. E.g. if you attach a callback to a method in a super class and that method is called in a sub class the callback is still executed.
|
181
|
+
|
182
|
+
See this sample:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class A
|
186
|
+
def a
|
187
|
+
# ...
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class B < A
|
192
|
+
end
|
193
|
+
|
194
|
+
A.extend AfterDo
|
195
|
+
A.after :a do puts 'a was called' end
|
196
|
+
|
197
|
+
b = B.new
|
198
|
+
b.a #prints out: a was called
|
199
|
+
```
|
200
|
+
|
201
|
+
### Removing callbacks
|
202
|
+
|
203
|
+
You can remove all callbacks you added to a class by doing:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
MyClass.remove_all_callbacks
|
207
|
+
```
|
208
|
+
|
209
|
+
### Errors
|
210
|
+
|
211
|
+
There are some custom errors that AfterDo throws. When you try to add a callback to a method which that class does not understand it will throw `AfterDo::NonExistingMethodError`.
|
212
|
+
|
213
|
+
When an error occurs during one of the callbacks that are attached to a method it will throw `AfterDo::CallbackError` with information about the original error and where the block/callback causing this error was defined to help pinpoint the error.
|
214
|
+
|
215
|
+
|
57
216
|
## Is there a before method?
|
58
217
|
|
59
|
-
|
218
|
+
Yes. It works just like the `after` method, but the callbacks are executed before the original method is called. You can also mix and match before and after calls.
|
219
|
+
|
220
|
+
Before for me is a far less common use case, that's why it was only added later (in the 0.2 release).
|
221
|
+
|
222
|
+
Here is a small sample:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
require 'after_do'
|
226
|
+
|
227
|
+
class MyClass
|
228
|
+
attr_accessor :value
|
229
|
+
end
|
230
|
+
|
231
|
+
MyClass.extend AfterDo
|
232
|
+
MyClass.after :value= do |*, obj| puts 'after: ' + obj.value.to_s end
|
233
|
+
MyClass.before :value= do |*, obj| puts 'before: ' + obj.value.to_s end
|
234
|
+
|
235
|
+
m = MyClass.new
|
236
|
+
m.value = 'Hello'
|
237
|
+
m.value = 'new value'
|
238
|
+
|
239
|
+
# Output is:
|
240
|
+
# before:
|
241
|
+
# after: Hello
|
242
|
+
# before: Hello
|
243
|
+
# after: new value
|
244
|
+
```
|
245
|
+
|
246
|
+
### Method granularity
|
247
|
+
|
248
|
+
AfterDo works on the granularity of methods. That means that you can only attach callbacks to methods. This is no problem however, since if it's your code you can always define new methods. E.g. you want to attach callbacks to the end of some operation that happens in the middle of a method just define a new method for that piece of code.
|
249
|
+
|
250
|
+
I sometimes do this for evaluating the block, as I want to do something when that block finished evaluating so I define a method `eval_block` wherein I just evaluate the block.
|
251
|
+
|
252
|
+
## Is this a good idea?
|
253
|
+
|
254
|
+
Always depends on what you are doing with it. As many things out there it has its use cases but can easily be misused.
|
255
|
+
|
256
|
+
### Advantages
|
257
|
+
|
258
|
+
- Get cross cutting concerns packed together in one file - don't have them scattered all over your code base obfuscating what the real responsibility of that class is
|
259
|
+
- Don't repeat yourself, define what is happening when in one file
|
260
|
+
- I feel like it helps the Single Responsibility principle, as it enables classes to focus on what their main responsibility is and not deal with other stuff. I initially wrote this gem when I wanted to decouple an object of my domain from the way it is saved.
|
261
|
+
|
262
|
+
### Drawbacks
|
263
|
+
|
264
|
+
- You lose clarity. With callbacks after a method it is not immediately visible what happens when a method is called as some behavior might be defined elsewhere.
|
265
|
+
- You could use this to modify the behaviour of classes everywhere. Don't. Use it for what it is meant to be used for - a concern that is not the primary concern of the class you are adding the callback to but that class is still involved with.
|
266
|
+
|
267
|
+
A use case I feel this is particularly made for is redrawing. That's what we use it for over at [shoes4](https://github.com/shoes/shoes4). E.g. we have multiple objects and different actions on these objects may trigger a redraw (such changing the position of a circle). This concern could be littered all over the code base. Or nicely packed into one file where you don't repeat yourself for similar redrawing scenarios and you see all the redraws at one glance. Furthermore it makes it easier to do things like "Just do one redraw every 1/30s" (not yet implemented).
|
268
|
+
|
269
|
+
## Does it work with Ruby interpreter X?
|
270
|
+
|
271
|
+
Thanks to the awesome [travis CI](https://travis-ci.org/) the specs are run with MRI 1.9.3, 2.0, the latest jruby and rubinius releases in 1.9 mode. So in short, this should work with all of them and is aimed at doing so :-)
|
60
272
|
|
61
273
|
## Contributing
|
62
274
|
|
275
|
+
Contributions are very welcome. Whether it's an issue or even a pull request. For pull requests the you can use the following flow:
|
276
|
+
|
63
277
|
1. Fork it
|
64
278
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
279
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
66
280
|
4. Push to the branch (`git push origin my-new-feature`)
|
67
281
|
5. Create new Pull Request
|
282
|
+
|
283
|
+
I'd also really appreciate spec only pull requests or bug reports with a failing spec/minimal example as this makes fixing it a lot easier =)
|
284
|
+
|
285
|
+
Thanks in advance for all contributions of any kind!
|
data/after_do.gemspec
CHANGED
@@ -16,10 +16,4 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
|
-
|
20
|
-
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
-
spec.add_development_dependency "rake"
|
22
|
-
spec.add_development_dependency "rspec"
|
23
|
-
spec.add_development_dependency "simplecov"
|
24
|
-
spec.add_development_dependency "coveralls"
|
25
19
|
end
|
data/lib/after_do/version.rb
CHANGED
data/lib/after_do.rb
CHANGED
@@ -1,62 +1,120 @@
|
|
1
1
|
module AfterDo
|
2
2
|
ALIAS_PREFIX = '__after_do_orig_'
|
3
3
|
|
4
|
-
def
|
5
|
-
|
4
|
+
def self.extended(klazz)
|
5
|
+
klazz.send(:include, AfterDo::Instance)
|
6
|
+
klazz.send(:extend, AfterDo::Class)
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
class NonExistingMethodError < StandardError ; end
|
10
|
+
class CallbackError < StandardError ; end
|
11
|
+
|
12
|
+
module Class
|
13
|
+
|
14
|
+
def _after_do_callbacks
|
15
|
+
@_after_do_callbacks || _after_do_basic_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def after(*methods, &block)
|
19
|
+
_after_do_define_callback(:after, methods, block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def before(*methods, &block)
|
23
|
+
_after_do_define_callback(:before, methods, block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_all_callbacks
|
27
|
+
@_after_do_callbacks = _after_do_basic_hash if @_after_do_callbacks
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def _after_do_define_callback(type, methods, block)
|
32
|
+
@_after_do_callbacks ||= _after_do_basic_hash
|
33
|
+
methods.flatten! #in case someone used an Array
|
34
|
+
_after_do_raise_no_method_specified(type) if methods.empty?
|
35
|
+
methods.each do |method|
|
36
|
+
_after_do_add_callback_to_method(type, method, block)
|
37
|
+
end
|
13
38
|
end
|
14
|
-
|
15
|
-
|
39
|
+
|
40
|
+
def _after_do_raise_no_method_specified(type)
|
41
|
+
raise ArgumentError, "#{type} takes at least one method name!"
|
42
|
+
end
|
43
|
+
|
44
|
+
def _after_do_basic_hash
|
45
|
+
{before: {}, after: {}}
|
46
|
+
end
|
47
|
+
|
48
|
+
def _after_do_add_callback_to_method(type, method, block)
|
49
|
+
unless _after_do_method_already_renamed?(method)
|
16
50
|
_after_do_make_after_do_version_of_method(method)
|
17
51
|
end
|
18
|
-
@_after_do_callbacks[method] << block
|
52
|
+
@_after_do_callbacks[type][method] << block
|
19
53
|
end
|
20
|
-
end
|
21
54
|
|
22
|
-
|
23
|
-
|
24
|
-
@_after_do_callbacks
|
55
|
+
def _after_do_make_after_do_version_of_method(method)
|
56
|
+
_after_do_raise_no_method_error(method) unless method_defined? method
|
57
|
+
@_after_do_callbacks[:before][method] = []
|
58
|
+
@_after_do_callbacks[:after][method] = []
|
59
|
+
alias_name = _after_do_aliased_name method
|
60
|
+
_after_do_rename_old_method(method, alias_name)
|
61
|
+
_after_do_redefine_method_with_callback(method, alias_name)
|
25
62
|
end
|
26
|
-
end
|
27
63
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
alias_name = _after_do_aliased_name method
|
32
|
-
_after_do_rename_old_method(method, alias_name)
|
33
|
-
_after_do_redefine_method_with_callback(method, alias_name)
|
34
|
-
end
|
64
|
+
def _after_do_raise_no_method_error(method)
|
65
|
+
raise NonExistingMethodError, "There is no method #{method} on #{self} to attach a block to with AfterDo"
|
66
|
+
end
|
35
67
|
|
36
|
-
|
37
|
-
|
38
|
-
|
68
|
+
def _after_do_aliased_name(symbol)
|
69
|
+
(ALIAS_PREFIX + symbol.to_s).to_sym
|
70
|
+
end
|
39
71
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
72
|
+
def _after_do_rename_old_method(old_name, new_name)
|
73
|
+
class_eval do
|
74
|
+
alias_method new_name, old_name
|
75
|
+
private new_name
|
76
|
+
end
|
44
77
|
end
|
45
|
-
end
|
46
78
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
79
|
+
def _after_do_redefine_method_with_callback(method, alias_name)
|
80
|
+
class_eval do
|
81
|
+
define_method method do |*args|
|
82
|
+
_after_do_execute_callbacks :before, method, *args
|
83
|
+
return_value = send(alias_name, *args)
|
84
|
+
_after_do_execute_callbacks :after, method, *args
|
85
|
+
return_value
|
53
86
|
end
|
54
|
-
return_value
|
55
87
|
end
|
56
88
|
end
|
89
|
+
|
90
|
+
def _after_do_method_already_renamed?(method)
|
91
|
+
private_method_defined? _after_do_aliased_name(method)
|
92
|
+
end
|
57
93
|
end
|
58
94
|
|
59
|
-
|
60
|
-
|
95
|
+
module Instance
|
96
|
+
def _after_do_execute_callbacks(type, method, *args)
|
97
|
+
callback_classes = self.class.ancestors.select do |klazz|
|
98
|
+
_after_do_has_callback_for?(klazz, type, method)
|
99
|
+
end
|
100
|
+
callback_classes.each do |klazz|
|
101
|
+
klazz._after_do_callbacks[type][method].each do |block|
|
102
|
+
_after_do_execute_callback(block, method, *args)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _after_do_has_callback_for?(klazz, type, method)
|
108
|
+
klazz.respond_to?(:_after_do_callbacks) &&
|
109
|
+
klazz._after_do_callbacks[type][method]
|
110
|
+
end
|
111
|
+
|
112
|
+
def _after_do_execute_callback(block, method, *args)
|
113
|
+
begin
|
114
|
+
block.call *args, self
|
115
|
+
rescue Exception => error
|
116
|
+
raise CallbackError, "A callback block for method #{method} on the instance #{self} with the following arguments: #{args.join(', ')} defined in the file #{block.source_location[0]} in line #{block.source_location[1]} resulted in the following error: #{error.class}: #{error.message} and this backtrace:\n #{error.backtrace.join("\n")}"
|
117
|
+
end
|
118
|
+
end
|
61
119
|
end
|
62
120
|
end
|
data/samples/before.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'after_do'
|
2
|
+
|
3
|
+
class MyClass
|
4
|
+
attr_accessor :value
|
5
|
+
end
|
6
|
+
|
7
|
+
MyClass.extend AfterDo
|
8
|
+
MyClass.after :value= do |*, obj| puts 'after: ' + obj.value.to_s end
|
9
|
+
MyClass.before :value= do |*, obj| puts 'before: ' + obj.value.to_s end
|
10
|
+
|
11
|
+
m = MyClass.new
|
12
|
+
m.value = 'Hello'
|
13
|
+
m.value = 'new value'
|
14
|
+
|
15
|
+
# Output is:
|
16
|
+
# before:
|
17
|
+
# after: Hello
|
18
|
+
# before: Hello
|
19
|
+
# after: new value
|
data/samples/dog.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require 'after_do'
|
2
2
|
|
3
3
|
class Dog
|
4
4
|
def bark
|
5
5
|
puts 'Woooof'
|
6
6
|
end
|
7
|
-
|
8
|
-
def eat
|
9
|
-
puts 'yummie!'
|
10
|
-
end
|
11
7
|
end
|
12
8
|
|
13
9
|
Dog.extend AfterDo
|
@@ -17,5 +13,4 @@ dog = Dog.new
|
|
17
13
|
dog2 = Dog.new
|
18
14
|
|
19
15
|
dog.bark
|
20
|
-
|
21
|
-
dog2.bark
|
16
|
+
dog2.bark
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'after_do'
|
2
|
+
|
3
|
+
class Example
|
4
|
+
def zero
|
5
|
+
# ...
|
6
|
+
end
|
7
|
+
|
8
|
+
def two(a, b)
|
9
|
+
# ...
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
'some value'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Example.extend AfterDo
|
18
|
+
|
19
|
+
Example.after :zero do puts 'Hello!' end
|
20
|
+
Example.after :zero do |obj| puts obj.value end
|
21
|
+
Example.after :two do |first, second| puts first + ' ' + second end
|
22
|
+
Example.after :two do |a, b, obj| puts a + ' ' + b + ' ' + obj.value end
|
23
|
+
Example.after :two do |*, obj| puts 'just ' + obj.value end
|
24
|
+
|
25
|
+
e = Example.new
|
26
|
+
e.zero
|
27
|
+
e.two 'one', 'two'
|
28
|
+
# prints:
|
29
|
+
# Hello!
|
30
|
+
# some value
|
31
|
+
# one two
|
32
|
+
# one two some value
|
33
|
+
# just some value
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'after_do'
|
2
|
+
|
3
|
+
class Team
|
4
|
+
extend AfterDo
|
5
|
+
|
6
|
+
def add_member(member)
|
7
|
+
# ...
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove_member(member)
|
11
|
+
# ..
|
12
|
+
end
|
13
|
+
|
14
|
+
def change_name(new_name)
|
15
|
+
# ..
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
# ..
|
20
|
+
puts 'saving...'
|
21
|
+
end
|
22
|
+
|
23
|
+
after :add_member, :remove_member, :change_name do |*args, team| team.save end
|
24
|
+
end
|
25
|
+
|
26
|
+
team = Team.new
|
27
|
+
team.add_member 'Maren'
|
28
|
+
team.change_name 'Ruby Cherries'
|
29
|
+
team.remove_member 'Guilia'
|
data/spec/after_do_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AfterDo do
|
4
|
+
|
4
5
|
let(:dummy_instance) {@dummy_class.new}
|
5
6
|
let(:mockie) {double}
|
6
7
|
|
@@ -25,119 +26,228 @@ describe AfterDo do
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
29
|
+
shared_examples_for 'calling callbacks' do |callback_adder|
|
30
|
+
it 'responds to before/after' do
|
31
|
+
@dummy_class.should respond_to callback_adder
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
34
|
+
it 'calls a method on the injected mockie' do
|
35
|
+
@dummy_class.send callback_adder, :zero do mockie.call_method end
|
36
|
+
mockie.should_receive :call_method
|
37
|
+
dummy_instance.zero
|
38
|
+
end
|
32
39
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
it 'does not change the return value' do
|
41
|
+
before_return_value = dummy_instance.zero
|
42
|
+
@dummy_class.send callback_adder, :zero do 42 end
|
43
|
+
after_return_value = dummy_instance.zero
|
44
|
+
after_return_value.should eq before_return_value
|
45
|
+
end
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
47
|
+
it 'marks the copied method as private' do
|
48
|
+
@dummy_class.send callback_adder, :zero do end
|
49
|
+
copied_method_name = (AfterDo::ALIAS_PREFIX + 'zero').to_sym
|
50
|
+
dummy_instance.respond_to?(copied_method_name).should be_false
|
51
|
+
end
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
it 'can add multiple call backs' do
|
54
|
+
mockie.should_receive :call_method
|
55
|
+
mock2 = double
|
56
|
+
mock2.should_receive :call_another_method
|
57
|
+
mock3 = double
|
58
|
+
mock3.should_receive :bla
|
59
|
+
@dummy_class.send callback_adder, :zero do mockie.call_method end
|
60
|
+
@dummy_class.send callback_adder, :zero do mock2.call_another_method end
|
61
|
+
@dummy_class.send callback_adder, :zero do mock3.bla end
|
62
|
+
dummy_instance.zero
|
63
|
+
end
|
51
64
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@dummy_class.after :zero do mockie.call_method end
|
59
|
-
@dummy_class.after :zero do mock2.call_another_method end
|
60
|
-
@dummy_class.after :zero do mock3.bla end
|
61
|
-
dummy_instance.zero
|
62
|
-
end
|
65
|
+
it 'can remove all callbacks' do
|
66
|
+
mockie.should_not_receive :call_method
|
67
|
+
@dummy_class.send callback_adder, :zero do mockie.call_method end
|
68
|
+
@dummy_class.remove_all_callbacks
|
69
|
+
dummy_instance.zero
|
70
|
+
end
|
63
71
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
describe 'errors' do
|
73
|
+
it 'throws an error when you try to add a hook to a non existing method' do
|
74
|
+
expect do
|
75
|
+
@dummy_class.send callback_adder, :non_existing_method do ; end
|
76
|
+
end.to raise_error(AfterDo::NonExistingMethodError)
|
77
|
+
end
|
70
78
|
|
71
|
-
|
79
|
+
describe 'errors in callbacks' do
|
72
80
|
|
73
|
-
|
74
|
-
|
75
|
-
|
81
|
+
def expect_call_back_error(matcher = nil)
|
82
|
+
expect do
|
83
|
+
dummy_instance.zero
|
84
|
+
end.to raise_error AfterDo::CallbackError, matcher
|
85
|
+
end
|
86
|
+
|
87
|
+
before :each do
|
88
|
+
@dummy_class.send callback_adder, :zero do raise StandardError, 'silly message' end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'raises a CallbackError' do
|
92
|
+
expect_call_back_error
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'mentions the error raised' do
|
96
|
+
expect_call_back_error /StandardError/
|
97
|
+
end
|
76
98
|
|
77
|
-
|
78
|
-
|
79
|
-
|
99
|
+
it 'mentions the method called' do
|
100
|
+
expect_call_back_error /zero/
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'mentions the file the error was raised in' do
|
104
|
+
expect_call_back_error Regexp.new __FILE__
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'mentions the original error message' do
|
108
|
+
expect_call_back_error /silly message/
|
109
|
+
end
|
110
|
+
end
|
80
111
|
end
|
81
112
|
|
82
|
-
|
83
|
-
|
84
|
-
|
113
|
+
describe 'with parameters' do
|
114
|
+
|
115
|
+
before :each do
|
116
|
+
mockie.should_receive :call_method
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'can handle methods with a parameter' do
|
120
|
+
@dummy_class.send callback_adder, :one do mockie.call_method end
|
121
|
+
dummy_instance.one 5
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'can handle methods with 2 parameters' do
|
125
|
+
@dummy_class.send callback_adder, :two do mockie.call_method end
|
126
|
+
dummy_instance.two 5, 8
|
127
|
+
end
|
85
128
|
end
|
86
|
-
end
|
87
129
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
130
|
+
describe 'with parameters for the given block' do
|
131
|
+
it 'can handle one block parameter' do
|
132
|
+
mockie.should_receive(:call_method).with(5)
|
133
|
+
@dummy_class.send callback_adder, :one do |i| mockie.call_method i end
|
134
|
+
dummy_instance.one 5
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'can handle two block parameters' do
|
138
|
+
mockie.should_receive(:call_method).with(5, 8)
|
139
|
+
@dummy_class.send callback_adder, :two do |i, j| mockie.call_method i, j end
|
140
|
+
dummy_instance.two 5, 8
|
141
|
+
end
|
93
142
|
end
|
94
143
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
144
|
+
describe 'multiple methods' do
|
145
|
+
def call_all_3_methods
|
146
|
+
dummy_instance.zero
|
147
|
+
dummy_instance.one 4
|
148
|
+
dummy_instance.two 4, 5
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'can take multiple method names as arguments' do
|
152
|
+
mockie.should_receive(:call_method).exactly(3).times
|
153
|
+
@dummy_class.send callback_adder, :zero, :one, :two do mockie.call_method end
|
154
|
+
call_all_3_methods
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'can get the methods as an Array' do
|
158
|
+
mockie.should_receive(:call_method).exactly(3).times
|
159
|
+
@dummy_class.send callback_adder, [:zero, :one, :two] do mockie.call_method end
|
160
|
+
call_all_3_methods
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'raises an error when no method is specified' do
|
164
|
+
expect do
|
165
|
+
@dummy_class.send callback_adder do mockie.call_method end
|
166
|
+
end.to raise_error ArgumentError
|
167
|
+
end
|
99
168
|
end
|
100
|
-
end
|
101
169
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
170
|
+
describe 'it can get a hold of self, if needbe' do
|
171
|
+
it 'works for a method without arguments' do
|
172
|
+
mockie.should_receive(:call_method).with(dummy_instance)
|
173
|
+
@dummy_class.send callback_adder, :zero do |object| mockie.call_method(object) end
|
174
|
+
dummy_instance.zero
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'works for a method with 2 arguments' do
|
178
|
+
mockie.should_receive(:call_method).with(1, 2, dummy_instance)
|
179
|
+
@dummy_class.send callback_adder, :two do |first, second, object|
|
180
|
+
mockie.call_method(first, second, object)
|
181
|
+
end
|
182
|
+
dummy_instance.two 1, 2
|
183
|
+
end
|
107
184
|
end
|
108
185
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
186
|
+
describe 'inheritance' do
|
187
|
+
let(:inherited_instance) {@inherited_class.new}
|
188
|
+
|
189
|
+
before :each do
|
190
|
+
define_inherited_class
|
191
|
+
end
|
192
|
+
|
193
|
+
def define_inherited_class
|
194
|
+
@inherited_class = Class.new @dummy_class
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'class knows about the after/before method' do
|
198
|
+
@inherited_class.should respond_to callback_adder
|
199
|
+
end
|
200
|
+
|
201
|
+
describe 'callback on parent class' do
|
202
|
+
before :each do
|
203
|
+
@dummy_class.send callback_adder, :zero do mockie.call end
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'works when we have a callback on the parent class' do
|
207
|
+
mockie.should_receive :call
|
208
|
+
inherited_instance.zero
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'remove_callbacks does not remove the callbacks on parent class' do
|
212
|
+
mockie.should_receive :call
|
213
|
+
@inherited_class.remove_all_callbacks
|
214
|
+
inherited_instance.zero
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'remove_callbacks on the parent does remove the callbacks' do
|
218
|
+
mockie.should_not_receive :call
|
219
|
+
@dummy_class.remove_all_callbacks
|
220
|
+
inherited_instance.zero
|
221
|
+
end
|
222
|
+
end
|
113
223
|
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it_behaves_like 'calling callbacks', :after
|
227
|
+
it_behaves_like 'calling callbacks', :before
|
114
228
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
229
|
+
describe 'before and after behaviour' do
|
230
|
+
let(:callback){double 'callback', before_call: nil, after_call: nil}
|
231
|
+
|
232
|
+
before :each do
|
233
|
+
@dummy_class.before :zero do callback.before_call end
|
234
|
+
@dummy_class.after :zero do callback.after_call end
|
119
235
|
end
|
120
236
|
|
121
|
-
it '
|
122
|
-
|
123
|
-
|
124
|
-
end.to raise_error ArgumentError
|
237
|
+
it 'calls the before callback' do
|
238
|
+
callback.should_receive :before_call
|
239
|
+
dummy_instance.zero
|
125
240
|
end
|
126
|
-
end
|
127
241
|
|
128
|
-
|
129
|
-
|
130
|
-
mockie.should_receive(:call_method).with(dummy_instance)
|
131
|
-
@dummy_class.after :zero do |object| mockie.call_method(object) end
|
242
|
+
it 'calls the after callback' do
|
243
|
+
callback.should_receive :after_call
|
132
244
|
dummy_instance.zero
|
133
245
|
end
|
134
246
|
|
135
|
-
it '
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
140
|
-
dummy_instance.two 1, 2
|
247
|
+
it 'receives the calls in the right order' do
|
248
|
+
callback.should_receive(:before_call).ordered
|
249
|
+
callback.should_receive(:after_call).ordered
|
250
|
+
dummy_instance.zero
|
141
251
|
end
|
142
252
|
end
|
143
253
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,85 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: after_do
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Pfeiffer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ~>
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.3'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ~>
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.3'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - '>='
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - '>='
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - '>='
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - '>='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: simplecov
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - '>='
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: coveralls
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - '>='
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - '>='
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
11
|
+
date: 2013-11-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
83
13
|
description: after_do is a gem that let's you execute a block of your choice after
|
84
14
|
a specific method was called on a class.
|
85
15
|
email:
|
@@ -93,13 +23,19 @@ files:
|
|
93
23
|
- .ruby-version
|
94
24
|
- .travis.yml
|
95
25
|
- Gemfile
|
26
|
+
- Guardfile
|
96
27
|
- LICENSE.txt
|
97
28
|
- README.md
|
98
29
|
- Rakefile
|
99
30
|
- after_do.gemspec
|
100
31
|
- lib/after_do.rb
|
101
32
|
- lib/after_do/version.rb
|
33
|
+
- samples/before.rb
|
102
34
|
- samples/dog.rb
|
35
|
+
- samples/error_in_callback.rb
|
36
|
+
- samples/getting_a_hold.rb
|
37
|
+
- samples/inheritance.rb
|
38
|
+
- samples/within_class.rb
|
103
39
|
- spec/after_do_spec.rb
|
104
40
|
- spec/spec_helper.rb
|
105
41
|
homepage: https://github.com/PragTob/after_do
|
@@ -122,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
58
|
version: '0'
|
123
59
|
requirements: []
|
124
60
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.0.
|
61
|
+
rubygems_version: 2.0.6
|
126
62
|
signing_key:
|
127
63
|
specification_version: 4
|
128
64
|
summary: after_do allows you to do simple after hooks on methods
|