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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ef93b03d163368d4323b3b457a104fbcbf8dde4
4
- data.tar.gz: c3efe779604f230849918900543f1a4a2d68d6f7
3
+ metadata.gz: 246d1c1ca42b79e8fedf494f8b9ef5620a88e998
4
+ data.tar.gz: 591190d78158bb4a97db932ac0481bad656cb35c
5
5
  SHA512:
6
- metadata.gz: 384930d2ac61d25b48cd269db52679d8d0860ef0bfa5c13621c55ba26af30bb83b12aeb5f2ad0f58065d9c1d71dada4d5a784d876c09374e3ed6c17de75f8ec1
7
- data.tar.gz: 2bf8dd100317b5a6ec541ddd514288eae869b4b0930d3fe6a28ef08bda302837e8c84e32f1722fd1021edec6c100de846a46cd4a53213d128e900a7fdf2c90ee
6
+ metadata.gz: b3b639f1aa8bc684b30b8c1b16026bf2406c98872563ff10aadad02177f47715c8f9eec270409fdbbd9213087323cebd5b38b566308cb12e5b02c9fbf0585321
7
+ data.tar.gz: b05353aee06b2b7ad93347ad7fdf5284b46529b984ddc58411a10b374437a2e91907d91cd925cda6aba614671bbdaf55fbb42e20e796f26320d4e22a335749cc
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.0.0-p195
1
+ ruby-2.0.0-p247
data/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - rbx-19mode
4
+ - jruby-19mode
3
5
  - 1.9.3
4
6
  - 2.0
5
- - jruby-19mode
6
- - rbx-19mode
7
- script: bundle exec rspec spec
7
+ script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -2,3 +2,10 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in after_do.gemspec
4
4
  gemspec
5
+
6
+ gem 'bundler', '~> 1.3'
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'guard-rspec'
10
+ gem 'simplecov'
11
+ gem 'coveralls'
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
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 `MyClass.after :some_method do puts 'whatever you want?' end`
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
- This shall not be done to to alter behavior or something but rather to fight cross-cutting concerns such as logging. E.g. with logging you litter all your code wit logging statements - that concern is spread over many files. With AfterDo you could put all the logging in one file.
5
+ ```
6
+ MyClass.after :some_method do whatever_you_want end
7
+ ```
6
8
 
7
- AfterDo has no external runtime dependencies and the code is just a bit more than 60 lines (blank lines included).
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
- With AfterDo you can do simple things like putting something out everything a method is called as in this example:
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
- No not yet, I didn't have a use case for it yet. If you have one please let me know, it is relatively easy to add.
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
@@ -1,3 +1,3 @@
1
1
  module AfterDo
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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 _after_do_callbacks
5
- @_after_do_callbacks
4
+ def self.extended(klazz)
5
+ klazz.send(:include, AfterDo::Instance)
6
+ klazz.send(:extend, AfterDo::Class)
6
7
  end
7
8
 
8
- def after(*methods, &block)
9
- @_after_do_callbacks ||= Hash.new([])
10
- methods.flatten! #in case someone used an Array
11
- if methods.empty?
12
- raise ArgumentError, 'after takes at least one method name!'
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
- methods.each do |method|
15
- if _after_do_method_already_renamed?(method)
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
- def remove_all_callbacks
23
- if @_after_do_callbacks
24
- @_after_do_callbacks.keys.each do |key| @_after_do_callbacks[key] = [] end
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
- private
29
- def _after_do_make_after_do_version_of_method(method)
30
- @_after_do_callbacks[method] = []
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
- def _after_do_aliased_name(symbol)
37
- (ALIAS_PREFIX + symbol.to_s).to_sym
38
- end
68
+ def _after_do_aliased_name(symbol)
69
+ (ALIAS_PREFIX + symbol.to_s).to_sym
70
+ end
39
71
 
40
- def _after_do_rename_old_method(old_name, new_name)
41
- class_eval do
42
- alias_method new_name, old_name
43
- private new_name
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
- def _after_do_redefine_method_with_callback(method, alias_name)
48
- class_eval do
49
- define_method method do |*args|
50
- return_value = send(alias_name, *args)
51
- self.class._after_do_callbacks[method].each do |block|
52
- block.call *args, self
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
- def _after_do_method_already_renamed?(method)
60
- !private_method_defined? _after_do_aliased_name(method)
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 File.expand_path('../../lib/after_do', __FILE__)
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
- dog.eat
21
- dog2.bark
16
+ dog2.bark
@@ -0,0 +1,12 @@
1
+ require 'after_do'
2
+
3
+ class A
4
+ def work
5
+ # ..
6
+ end
7
+ end
8
+
9
+ A.extend AfterDo
10
+ A.after :work do 1/0 end
11
+
12
+ A.new.work
@@ -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,16 @@
1
+ require 'after_do'
2
+
3
+ class A
4
+ def a
5
+ # ...
6
+ end
7
+ end
8
+
9
+ class B < A
10
+ end
11
+
12
+ A.extend AfterDo
13
+ A.after :a do puts 'a was called' end
14
+
15
+ b = B.new
16
+ b.a #prints out: a was called
@@ -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'
@@ -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
- it 'responds to after' do
30
- @dummy_class.should respond_to :after
31
- end
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
- it 'calls a method on the injected mockie' do
34
- mockie.should_receive :call_method
35
- @dummy_class.after :zero do mockie.call_method end
36
- dummy_instance.zero
37
- end
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
- it 'does not change the return value' do
40
- before = dummy_instance.zero
41
- @dummy_class.after :zero do 42 end
42
- after = dummy_instance.zero
43
- after.should eq before
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
- it 'marks the copied method as private' do
47
- @dummy_class.after :zero do end
48
- copied_method_name = (AfterDo::ALIAS_PREFIX + 'zero').to_sym
49
- dummy_instance.respond_to?(copied_method_name).should be_false
50
- end
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
- it 'can add multiple call backs' do
53
- mockie.should_receive :call_method
54
- mock2 = double
55
- mock2.should_receive :call_another_method
56
- mock3 = double
57
- mock3.should_receive :bla
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
- it 'can remove all callbacks' do
65
- mockie.should_not_receive :call_method
66
- @dummy_class.after :zero do mockie.call_method end
67
- @dummy_class.remove_all_callbacks
68
- dummy_instance.zero
69
- end
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
- describe 'with parameters' do
79
+ describe 'errors in callbacks' do
72
80
 
73
- before :each do
74
- mockie.should_receive :call_method
75
- end
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
- it 'can handle methods with a parameter' do
78
- @dummy_class.after :one do mockie.call_method end
79
- dummy_instance.one 5
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
- it 'can handle methods with 2 parameters' do
83
- @dummy_class.after :two do mockie.call_method end
84
- dummy_instance.two 5, 8
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
- describe 'with parameters for the given block' do
89
- it 'can handle one block parameter' do
90
- mockie.should_receive(:call_method).with(5)
91
- @dummy_class.after :one do |i| mockie.call_method i end
92
- dummy_instance.one 5
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
- it 'can handle two block parameters' do
96
- mockie.should_receive(:call_method).with(5, 8)
97
- @dummy_class.after :two do |i, j| mockie.call_method i, j end
98
- dummy_instance.two 5, 8
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
- describe 'multiple methods' do
103
- def call_all_3_methods
104
- dummy_instance.zero
105
- dummy_instance.one 4
106
- dummy_instance.two 4, 5
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
- it 'can take multiple method names as arguments' do
110
- mockie.should_receive(:call_method).exactly(3).times
111
- @dummy_class.after :zero, :one, :two do mockie.call_method end
112
- call_all_3_methods
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
- it 'can get the methods as an Array' do
116
- mockie.should_receive(:call_method).exactly(3).times
117
- @dummy_class.after [:zero, :one, :two] do mockie.call_method end
118
- call_all_3_methods
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 'raises an error when no method is specified' do
122
- expect do
123
- @dummy_class.after do mockie.call_method end
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
- describe 'it can get a hold of self, if needbe' do
129
- it 'works for a method without arguments' do
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 'works for a method with 2 arguments' do
136
- mockie.should_receive(:call_method).with(1, 2, dummy_instance)
137
- @dummy_class.after :two do |first, second, object|
138
- mockie.call_method(first, second, object)
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
@@ -4,5 +4,9 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
4
  Coveralls::SimpleCov::Formatter,
5
5
  SimpleCov::Formatter::HTMLFormatter
6
6
  ]
7
- SimpleCov.start
8
- require 'after_do'
7
+
8
+ SimpleCov.start do
9
+ add_filter '/spec/'
10
+ end
11
+
12
+ require 'after_do'
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.1.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-07-10 00:00:00.000000000 Z
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.3
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