method_decorators 0.9.0 → 0.9.1
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/.gitignore +1 -0
- data/Contributors.md +4 -0
- data/History.md +8 -0
- data/README.md +26 -2
- data/lib/method_decorators/decorators/memoize.rb +11 -0
- data/lib/method_decorators/decorators/precondition.rb +18 -0
- data/lib/method_decorators/decorators/retry.rb +16 -0
- data/lib/method_decorators/decorators.rb +1 -0
- data/lib/method_decorators/version.rb +1 -1
- data/lib/method_decorators.rb +14 -10
- data/spec/decorators/memoize_spec.rb +53 -0
- data/spec/decorators/precondition_spec.rb +45 -0
- data/spec/decorators/retry_spec.rb +40 -0
- data/spec/method_decorators_spec.rb +0 -4
- data/spec/spec_helper.rb +4 -0
- metadata +31 -38
data/.gitignore
CHANGED
data/Contributors.md
ADDED
data/History.md
ADDED
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Python's function decorators for Ruby.
|
|
11
11
|
Extend MethodDecorators in a class where you want to use them, and then stick `+DecoratorName` before your method declaration to use it.
|
12
12
|
|
13
13
|
```ruby
|
14
|
-
class
|
14
|
+
class Math
|
15
15
|
extend MethodDecorators
|
16
16
|
|
17
17
|
+Memoized
|
@@ -25,6 +25,27 @@ class SlowMath
|
|
25
25
|
end
|
26
26
|
```
|
27
27
|
|
28
|
+
You can also decorate with an instance of a decorator, rather than the class. This is useful for configuring specific options for the decorator.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class ExternalService
|
32
|
+
extend MethodDecorators
|
33
|
+
|
34
|
+
+Retry.new(3)
|
35
|
+
def request
|
36
|
+
...
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
### Included decorators
|
42
|
+
|
43
|
+
Include these with `require 'method_decorators/decorators/name_of_decorator'`, or all at once with `require 'method_decorators/decorators'`.
|
44
|
+
|
45
|
+
- Memoize - caches the result of the method for each arg combination it's called with
|
46
|
+
- Retry - retries the method up to n (passed in to the constructor) times if the method errors
|
47
|
+
- Precondition - raises an error if the precondition (passed as a block) is not met
|
48
|
+
|
28
49
|
### Defining a decorator
|
29
50
|
|
30
51
|
```ruby
|
@@ -35,4 +56,7 @@ class Transactional < MethodDecorator
|
|
35
56
|
end
|
36
57
|
end
|
37
58
|
end
|
38
|
-
```
|
59
|
+
```
|
60
|
+
|
61
|
+
## License
|
62
|
+
MethodDecorators is available under the MIT license and is freely available for all use, including personal, commercial, and academic. See LICENSE for details.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Precondition < MethodDecorator
|
2
|
+
def initialize(&blk)
|
3
|
+
@block = blk
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(orig, *args, &blk)
|
7
|
+
unless passes?(orig.receiver, *args)
|
8
|
+
raise ArgumentError, "failed precondition"
|
9
|
+
end
|
10
|
+
orig.call(*args, &blk)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def passes?(context, *args)
|
16
|
+
context.instance_exec(*args, &@block)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/decorators/*.rb'].each {|file| require file }
|
data/lib/method_decorators.rb
CHANGED
@@ -5,18 +5,17 @@ module MethodDecorators
|
|
5
5
|
super
|
6
6
|
orig_method = instance_method(name)
|
7
7
|
|
8
|
+
decorators = MethodDecorator.current_decorators
|
9
|
+
return if decorators.empty?
|
10
|
+
|
8
11
|
if private_method_defined?(name); visibility = :private
|
9
12
|
elsif protected_method_defined?(name); visibility = :protected
|
10
13
|
else visibility = :public
|
11
14
|
end
|
12
15
|
|
13
|
-
decorators = MethodDecorator.current_decorators
|
14
|
-
return if decorators.empty?
|
15
|
-
|
16
16
|
define_method(name) do |*args, &blk|
|
17
|
-
|
18
|
-
|
19
|
-
end.call(*args, &blk)
|
17
|
+
decorated = MethodDecorators.decorate_callable(orig_method.bind(self), decorators)
|
18
|
+
decorated.call(*args, &blk)
|
20
19
|
end
|
21
20
|
|
22
21
|
case visibility
|
@@ -33,9 +32,14 @@ module MethodDecorators
|
|
33
32
|
return if decorators.empty?
|
34
33
|
|
35
34
|
MethodDecorators.define_others_singleton_method(self, name) do |*args, &blk|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
decorated = MethodDecorators.decorate_callable(orig_method, decorators)
|
36
|
+
decorated.call(*args, &blk)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.decorate_callable(orig, decorators)
|
41
|
+
decorators.reduce(orig) do |callable, decorator|
|
42
|
+
lambda{ |*a, &b| decorator.call(callable, *a, &b) }
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -66,4 +70,4 @@ class MethodDecorator
|
|
66
70
|
def +@
|
67
71
|
@@current_decorators.unshift(self)
|
68
72
|
end
|
69
|
-
end
|
73
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'method_decorators/decorators/memoize'
|
3
|
+
|
4
|
+
describe Memoize do
|
5
|
+
describe "#call" do
|
6
|
+
let(:method) { double(:method, :call => :calculation) }
|
7
|
+
subject { Memoize.new }
|
8
|
+
|
9
|
+
it "calculates the value the first time the arguments are supplied" do
|
10
|
+
method.should_receive(:call)
|
11
|
+
subject.call(method, 10)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "stores the value of the method call" do
|
15
|
+
method.stub(:call).and_return(:foo, :bar)
|
16
|
+
subject.call(method, 10).should == :foo
|
17
|
+
subject.call(method, 10).should == :foo
|
18
|
+
end
|
19
|
+
|
20
|
+
it "memoizes the return value and skips the call the second time" do
|
21
|
+
subject.call(method, 10)
|
22
|
+
method.should_not_receive(:call)
|
23
|
+
subject.call(method, 10)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "memoizes different values for different arguments" do
|
27
|
+
method.stub(:call).with(10).and_return(:foo, :bar)
|
28
|
+
method.stub(:call).with(20).and_return(:bar, :foo)
|
29
|
+
subject.call(method, 10).should == :foo
|
30
|
+
subject.call(method, 10).should == :foo
|
31
|
+
subject.call(method, 20).should == :bar
|
32
|
+
subject.call(method, 20).should == :bar
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "acceptance" do
|
37
|
+
let(:klass) do
|
38
|
+
Class.new Base do
|
39
|
+
+Memoize
|
40
|
+
def count
|
41
|
+
@count ||= 0
|
42
|
+
@count += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
subject { klass.new }
|
47
|
+
|
48
|
+
it "memoizes calls to the method" do
|
49
|
+
subject.count.should == 1
|
50
|
+
subject.count.should == 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'method_decorators/decorators/precondition'
|
3
|
+
|
4
|
+
describe Precondition do
|
5
|
+
let(:receiver) { double(:receiver) }
|
6
|
+
let(:method) { double(:method, :call => :secret, :receiver => receiver) }
|
7
|
+
let(:block) { proc { |arg| true } }
|
8
|
+
subject { Precondition.new(&block) }
|
9
|
+
|
10
|
+
describe "#call" do
|
11
|
+
it "raises when the precondition fails" do
|
12
|
+
subject.stub(:passes?){ false }
|
13
|
+
expect{ subject.call(method) }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "executes the method when authorization succeeds" do
|
17
|
+
subject.stub(:passes?){ true }
|
18
|
+
subject.call(method).should == :secret
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "acceptance" do
|
23
|
+
let(:klass) do
|
24
|
+
Class.new Base do
|
25
|
+
def initialize(x)
|
26
|
+
@x = x
|
27
|
+
end
|
28
|
+
|
29
|
+
+Precondition.new{ |a| a + @x < 10 }
|
30
|
+
def multiply(a)
|
31
|
+
a * @x
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
subject { klass.new(3) }
|
36
|
+
|
37
|
+
it "calls the method if the precondition passes" do
|
38
|
+
subject.multiply(2).should == 6
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises if the precondition passes" do
|
42
|
+
expect{ subject.multiply(8) }.to raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'method_decorators/decorators/retry'
|
3
|
+
|
4
|
+
describe Retry do
|
5
|
+
let(:method) { double(:method, :call => false) }
|
6
|
+
subject { Retry.new(3) }
|
7
|
+
|
8
|
+
describe "#call" do
|
9
|
+
it "executes the method again if the first time failed " do
|
10
|
+
method.stub(:call){ raise }
|
11
|
+
|
12
|
+
method.should_receive(:call).exactly(3).times
|
13
|
+
expect{ subject.call(method) }.to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "does not retry the method if it succeeds" do
|
17
|
+
method.should_receive(:call).once.and_return(3)
|
18
|
+
subject.call(method).should == 3
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "acceptance" do
|
23
|
+
let(:klass) do
|
24
|
+
Class.new Base do
|
25
|
+
+Retry.new(3)
|
26
|
+
def do_it(magic_number)
|
27
|
+
@times ||= 0
|
28
|
+
@times += 1
|
29
|
+
raise if @times == magic_number
|
30
|
+
@times
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
subject { klass.new }
|
35
|
+
|
36
|
+
it "memoizes calls to the method" do
|
37
|
+
subject.do_it(1).should == 2
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,10 @@ require 'support/stringify'
|
|
4
4
|
require 'support/add_n'
|
5
5
|
require 'support/reverse'
|
6
6
|
|
7
|
+
class Base
|
8
|
+
extend MethodDecorators
|
9
|
+
end
|
10
|
+
|
7
11
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
8
12
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
9
13
|
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
metadata
CHANGED
@@ -1,43 +1,42 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: method_decorators
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.1
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 0
|
10
|
-
version: 0.9.0
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Michael Fairley
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2012-04-24 00:00:00 Z
|
12
|
+
date: 2012-05-01 00:00:00.000000000 Z
|
19
13
|
dependencies: []
|
20
|
-
|
21
14
|
description: Python's function decorators for Ruby
|
22
|
-
email:
|
15
|
+
email:
|
23
16
|
- michaelfairley@gmail.com
|
24
17
|
executables: []
|
25
|
-
|
26
18
|
extensions: []
|
27
|
-
|
28
19
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
20
|
+
files:
|
31
21
|
- .gitignore
|
32
22
|
- .rspec
|
33
23
|
- .travis.yml
|
24
|
+
- Contributors.md
|
34
25
|
- Gemfile
|
26
|
+
- History.md
|
35
27
|
- LICENSE
|
36
28
|
- README.md
|
37
29
|
- Rakefile
|
38
30
|
- lib/method_decorators.rb
|
31
|
+
- lib/method_decorators/decorators.rb
|
32
|
+
- lib/method_decorators/decorators/memoize.rb
|
33
|
+
- lib/method_decorators/decorators/precondition.rb
|
34
|
+
- lib/method_decorators/decorators/retry.rb
|
39
35
|
- lib/method_decorators/version.rb
|
40
36
|
- method_decorators.gemspec
|
37
|
+
- spec/decorators/memoize_spec.rb
|
38
|
+
- spec/decorators/precondition_spec.rb
|
39
|
+
- spec/decorators/retry_spec.rb
|
41
40
|
- spec/method_decorators_spec.rb
|
42
41
|
- spec/spec_helper.rb
|
43
42
|
- spec/support/add_n.rb
|
@@ -45,38 +44,32 @@ files:
|
|
45
44
|
- spec/support/stringify.rb
|
46
45
|
homepage: http://github.com/michaelfairley/method_decorators
|
47
46
|
licenses: []
|
48
|
-
|
49
47
|
post_install_message:
|
50
48
|
rdoc_options: []
|
51
|
-
|
52
|
-
require_paths:
|
49
|
+
require_paths:
|
53
50
|
- lib
|
54
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
52
|
none: false
|
56
|
-
requirements:
|
57
|
-
- -
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
|
60
|
-
|
61
|
-
- 0
|
62
|
-
version: "0"
|
63
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
58
|
none: false
|
65
|
-
requirements:
|
66
|
-
- -
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
|
69
|
-
segments:
|
70
|
-
- 0
|
71
|
-
version: "0"
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
72
63
|
requirements: []
|
73
|
-
|
74
64
|
rubyforge_project:
|
75
65
|
rubygems_version: 1.8.17
|
76
66
|
signing_key:
|
77
67
|
specification_version: 3
|
78
68
|
summary: Python's function decorators for Ruby
|
79
|
-
test_files:
|
69
|
+
test_files:
|
70
|
+
- spec/decorators/memoize_spec.rb
|
71
|
+
- spec/decorators/precondition_spec.rb
|
72
|
+
- spec/decorators/retry_spec.rb
|
80
73
|
- spec/method_decorators_spec.rb
|
81
74
|
- spec/spec_helper.rb
|
82
75
|
- spec/support/add_n.rb
|