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 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