method_decorators 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .rvmrc
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
data/Contributors.md ADDED
@@ -0,0 +1,4 @@
1
+ Thanks to everyone who's contributed.
2
+
3
+ - Micah Frost - @mfrost
4
+ - Isaac Sanders - @isaacsanders
data/History.md ADDED
@@ -0,0 +1,8 @@
1
+ 0.9.1
2
+ =====
3
+ - Added Memoize, Retry, and Precondition [@mfrost]
4
+
5
+
6
+ 0.9.0
7
+ =====
8
+ - Initial release!
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 SlowMath
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,11 @@
1
+ class Memoize < MethodDecorator
2
+ def initialize
3
+ @cache ||= {}
4
+ super
5
+ end
6
+
7
+ def call(orig, *args, &blk)
8
+ return @cache[args] if @cache.has_key?(args)
9
+ @cache[args] = orig.call(*args, &blk)
10
+ end
11
+ end
@@ -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,16 @@
1
+ class Retry < MethodDecorator
2
+ def initialize(max)
3
+ @max = max
4
+ end
5
+
6
+ def call(orig, *args, &blk)
7
+ attempts = 0
8
+ begin
9
+ attempts += 1
10
+ orig.call(*args, &blk)
11
+ rescue
12
+ retry if attempts < @max
13
+ raise
14
+ end
15
+ end
16
+ end
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + '/decorators/*.rb'].each {|file| require file }
@@ -1,3 +1,3 @@
1
1
  module MethodDecorators
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -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
- decorators.reduce(orig_method.bind(self)) do |callable, decorator|
18
- lambda{|*a, &b| decorator.call(callable, *a, &b) }
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
- decorators.reduce(orig_method) do |callable, decorator|
37
- lambda{|*a, &b| decorator.call(callable, *a, &b) }
38
- end.call(*args, &blk)
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
@@ -1,9 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- class Base
4
- extend MethodDecorators
5
- end
6
-
7
3
  describe MethodDecorators do
8
4
  subject { klass.new }
9
5
 
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
- hash: 59
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
- hash: 3
60
- segments:
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
- hash: 3
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