lazy_methods 1.0.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - rbx
5
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'rspec', '>=2.6.0'
@@ -0,0 +1,20 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ rake (0.9.2)
6
+ rspec (2.6.0)
7
+ rspec-core (~> 2.6.0)
8
+ rspec-expectations (~> 2.6.0)
9
+ rspec-mocks (~> 2.6.0)
10
+ rspec-core (2.6.4)
11
+ rspec-expectations (2.6.0)
12
+ diff-lcs (~> 1.1.2)
13
+ rspec-mocks (2.6.0)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ rake
20
+ rspec (>= 2.6.0)
@@ -1,33 +1,71 @@
1
- == LazyMethods
1
+ = LazyMethods
2
2
 
3
- This gem adds a virtual lazy version of every method on every class. Lazy methods have the same name as the original method name but prefixed with +lazy_+. A lazy method will return a proxy object that looks and acts just like the result from calling the actual method. The trick is that the actual method will not be called until a method is invoked on the proxy object.
3
+ This adds the ability to declare lazy loading or asynchronous methods.
4
+
5
+ == Lazy Loading Methods
6
+
7
+ Lazy methods will return immediately with a proxy object. The proxy object will look and act just like the result from calling the method. However, the original method will only be called when a method is invoked on the proxy object.
4
8
 
5
9
  This pattern works great with caching. You can have your business logic invoke lazy methods and your view logic sitting behind a cache. If the cache is hit, you won't actually end up invoking any of your business logic.
6
10
 
7
- === A simple example:
11
+ To define a lazy method, simply include the LazyMethods module in your class definition and then call +define_lazy_methods+ or +define_lazy_class_methods+ with the names of one or more methods defined for your class. This will create lazy versions of the methods with the names prefixed with +lazy_+.
8
12
 
9
- The business logic:
13
+ === Example
10
14
 
11
- def my_action
12
- @records = MyResource.find(params[:name])
15
+ class MyModel
16
+ include LazyMethods
17
+ define_lazy_methods :calculate_value
18
+ define_lazy_class_methods :find
13
19
  end
20
+
21
+ object = MyModel.lazy_find # The find method will not be called yet
22
+ object.to_s # The find will will be called here
23
+ value = object.lazy_calculate_value # The calculate_value method will not be called yet
24
+ sum = value + 1 # The calculate_value method will be called here
25
+
26
+ == Asynchronous Methods
14
27
 
15
- And in the view:
28
+ Asynchronouse methods are similar to lazy methods. The method call will return immediately with a proxy object. The difference is that the original method will be invoked immediately in a separate thread. When a method is called on the proxy object, the thread will block until the result is returned. This can be useful if you have methods that wait on IO like web service calls.
16
29
 
17
- <% cache(params[:names]) -%>
18
- <% @records.each do |record| -%>
19
- <div><%=record.title%></div>
20
- <% end -%>
21
- <% end -%>
30
+ === Example
31
+
32
+ class MyModel
33
+ include LazyMethods
34
+ define_async_methods :get_related_info
35
+ define_async_class_methods :find
36
+ end
37
+
38
+ objects = MyModel.async_find # The find method will be called in a new thread
39
+ objects.size # Execution will wait until the find thread has finished
40
+ objects.each do |obj|
41
+ obj.async_get_related_info # A new thread will be started for each object to call get_related_info
42
+ end
22
43
 
23
- Now even if you cache the fragments in your view that use @records, the database will still be hit to select and instantiate all the records. You could remove the resource call from the business logic and add it to the view. However, this just feels wrong and is inherently harder to test. Instead just use a lazy method.
44
+ == Usage Notes
24
45
 
25
- def my_action
26
- @records = MyResource.lazy_find(params[:name])
27
- end
46
+ Since lazy methods are not invoked at the time when you call them, there is a chance for the state of you application to change between the time when you invoke a method and when it is actually called.
47
+
48
+ # In this example, the value of object.value will be 2 when the do_something method is called.
49
+ object.value = 1
50
+ value = object.lazy_do_somthing
51
+ object.value = 2
52
+ value.to_s
53
+
54
+ Similarly, all asynchronous methods must make sure they are thread safe.
55
+
56
+ Exception handling can also be tricky since exceptions will not be immediately thrown.
57
+
58
+ value = nil
59
+ begin
60
+ value = object.lazy_do_something
61
+ rescue => e
62
+ # Exceptions thrown by do_something won't be caught here.
63
+ end
64
+
65
+ value.to_s # Exceptions thrown by do_something will be thrown here instead.
28
66
 
29
- Now, as long as no methods are invoked on @records, the original find method will never be called. As soon as the first method is called, the original find method will be called. It will never be called more than once. You can even pass in a block to the lazy method.
67
+ == Upgrade Notes
30
68
 
31
- == Testing
69
+ In version 1.x of this gem, lazy methods were automatically available for every method on every object. You must now define which methods should be defined as lazy methods. This change was made so that the code is less intrusive and the lazy method invocations are slightly faster.
32
70
 
33
- Since the proxy object looks and acts just like the real result object, all your view tests should still pass. Your controller tests should pass will little or no tweaking.
71
+ In addition, version 2.x of this gem includes the functionality of the async_methods 1.x gem. That gem is no longer being maintained.
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'rake/rdoctask'
4
3
 
5
4
  desc 'Default: run unit tests.'
6
5
  task :default => :test
@@ -16,20 +15,12 @@ rescue LoadError
16
15
  end
17
16
  end
18
17
 
19
- desc 'Generate documentation for the gem.'
20
- Rake::RDocTask.new(:rdoc) do |rdoc|
21
- rdoc.rdoc_dir = 'rdoc'
22
- rdoc.options << '--title' << 'Lazy Methods' << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc'
23
- rdoc.rdoc_files.include('README.rdoc')
24
- rdoc.rdoc_files.include('lib/**/*.rb')
25
- end
26
-
27
18
  begin
28
19
  require 'jeweler'
29
20
  Jeweler::Tasks.new do |gem|
30
21
  gem.name = "lazy_methods"
31
- gem.summary = %Q{Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed.}
32
- gem.description = %Q(Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed. This pattern is useful when used with caching systems.)
22
+ gem.summary = %Q{Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.}
23
+ gem.description = %Q{Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.}
33
24
  gem.email = "brian@embellishedvisions.com"
34
25
  gem.homepage = "http://github.com/bdurand/lazy_methods"
35
26
  gem.authors = ["Brian Durand"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 2.0.0
@@ -5,36 +5,40 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{lazy_methods}
8
- s.version = "1.0.6"
8
+ s.version = "2.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Durand"]
12
- s.date = %q{2011-01-24}
13
- s.description = %q{Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed. This pattern is useful when used with caching systems.}
12
+ s.date = %q{2011-08-29}
13
+ s.description = %q{Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.}
14
14
  s.email = %q{brian@embellishedvisions.com}
15
15
  s.extra_rdoc_files = [
16
16
  "README.rdoc"
17
17
  ]
18
18
  s.files = [
19
+ ".travis.yml",
20
+ "Gemfile",
21
+ "Gemfile.lock",
19
22
  "MIT-LICENSE",
20
23
  "README.rdoc",
21
24
  "Rakefile",
22
25
  "VERSION",
23
26
  "lazy_methods.gemspec",
24
27
  "lib/lazy_methods.rb",
25
- "lib/lazy_methods/lazy_methods.rb",
26
28
  "spec/lazy_method_spec.rb",
27
29
  "spec/method_tester.rb",
30
+ "spec/proxy_spec.rb",
28
31
  "spec/spec_helper.rb"
29
32
  ]
30
33
  s.homepage = %q{http://github.com/bdurand/lazy_methods}
31
34
  s.rdoc_options = ["--charset=UTF-8", "--main", "README.rdoc"]
32
35
  s.require_paths = ["lib"]
33
- s.rubygems_version = %q{1.4.1}
34
- s.summary = %q{Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed.}
36
+ s.rubygems_version = %q{1.6.2}
37
+ s.summary = %q{Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.}
35
38
  s.test_files = [
36
39
  "spec/lazy_method_spec.rb",
37
40
  "spec/method_tester.rb",
41
+ "spec/proxy_spec.rb",
38
42
  "spec/spec_helper.rb"
39
43
  ]
40
44
 
@@ -42,13 +46,19 @@ Gem::Specification.new do |s|
42
46
  s.specification_version = 3
43
47
 
44
48
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
50
+ s.add_runtime_dependency(%q<rspec>, [">= 2.6.0"])
45
51
  s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
46
52
  s.add_development_dependency(%q<jeweler>, [">= 0"])
47
53
  else
54
+ s.add_dependency(%q<rake>, [">= 0"])
55
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
48
56
  s.add_dependency(%q<rspec>, [">= 2.0.0"])
49
57
  s.add_dependency(%q<jeweler>, [">= 0"])
50
58
  end
51
59
  else
60
+ s.add_dependency(%q<rake>, [">= 0"])
61
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
52
62
  s.add_dependency(%q<rspec>, [">= 2.0.0"])
53
63
  s.add_dependency(%q<jeweler>, [">= 0"])
54
64
  end
@@ -1,2 +1,141 @@
1
- require File.expand_path('../lazy_methods/lazy_methods', __FILE__)
2
- Object.send(:include, LazyMethods::InstanceMethods) unless Object.include?(LazyMethods::InstanceMethods)
1
+ require 'thread'
2
+
3
+ # Include this module in classes you wish to add lazy or asynchronous methods to.
4
+ #
5
+ # To define a lazy or asynchronous methods methods:
6
+ #
7
+ # class MyClass
8
+ # include LazyMethods
9
+ #
10
+ # define_lazy_methods :method_1, :method_2
11
+ # define_async_methods :method_3, :method_4
12
+ #
13
+ # define_lazy_class_methods :class_method_1
14
+ # define_async_class_methods :class_method_2
15
+ #
16
+ # ...
17
+ # end
18
+ #
19
+ # This will allow you to call the methods as
20
+ #
21
+ # obj = MyClass.new
22
+ #
23
+ # obj.lazy_method_1
24
+ # obj.lazy_method_2
25
+ #
26
+ # obj.async_method_3
27
+ # obj.async_method_4
28
+ #
29
+ # MyClass.lasy_class_method_1
30
+ # MyClass.async_class_method_2
31
+ module LazyMethods
32
+
33
+ def self.included(base)
34
+ base.extend(ClassMethods)
35
+ end
36
+
37
+ module ClassMethods
38
+ def define_lazy_methods(*method_names)
39
+ method_names.flatten.each do |method_name|
40
+ class_eval <<-EOS
41
+ def lazy_#{method_name}(*args, &block)
42
+ LazyProxy.new{ #{method_name}(*args, &block) }
43
+ end
44
+ EOS
45
+ end
46
+ end
47
+
48
+ def define_async_methods(*method_names)
49
+ method_names.flatten.each do |method_name|
50
+ class_eval <<-EOS
51
+ def async_#{method_name}(*args, &block)
52
+ AsyncProxy.new{ #{method_name}(*args, &block) }
53
+ end
54
+ EOS
55
+ end
56
+ end
57
+
58
+ def define_lazy_class_methods(*method_names)
59
+ method_names.flatten.each do |method_name|
60
+ class_eval <<-EOS
61
+ def self.lazy_#{method_name}(*args, &block)
62
+ LazyProxy.new{ #{method_name}(*args, &block) }
63
+ end
64
+ EOS
65
+ end
66
+ end
67
+
68
+ def define_async_class_methods(*method_names)
69
+ method_names.flatten.each do |method_name|
70
+ class_eval <<-EOS
71
+ def self.async_#{method_name}(*args, &block)
72
+ AsyncProxy.new{ #{method_name}(*args, &block) }
73
+ end
74
+ EOS
75
+ end
76
+ end
77
+ end
78
+
79
+ # The proxy object does all the heavy lifting.
80
+ class Proxy #:nodoc:
81
+ # These methods we don't want to override. All other existing methods will be redefined.
82
+ PROTECTED_METHODS = %w(initialize method_missing __proxy_result__ __proxy_loaded__ __send__ __id__ object_id)
83
+
84
+ # Override already defined methods on Object to proxy them to the result object
85
+ instance_methods.each do |m|
86
+ undef_method(m) unless PROTECTED_METHODS.include?(m.to_s)
87
+ end
88
+
89
+ # All missing methods are proxied to the original result object.
90
+ def method_missing(method, *args, &block)
91
+ __proxy_result__.send(method, *args, &block)
92
+ end
93
+ end
94
+
95
+ class LazyProxy < Proxy #:nodoc:
96
+ def initialize(&block)
97
+ @method_call = block
98
+ end
99
+
100
+ # Get the result of the original method call. The original method must only be called once.
101
+ def __proxy_result__
102
+ @proxy_result = @method_call.call unless defined?(@proxy_result)
103
+ @proxy_result
104
+ end
105
+
106
+ # Helper method that indicates if the proxy has loaded the original method results yet.
107
+ def __proxy_loaded__
108
+ !!defined?(@proxy_result)
109
+ end
110
+ end
111
+
112
+ class AsyncProxy < Proxy #:nodoc:
113
+ def initialize(&block)
114
+ @proxy_result = nil
115
+ @proxy_exception = nil
116
+ if defined?(Thread.critical) && Thread.critical
117
+ @proxy_result = block.call
118
+ else
119
+ @thread = Thread.new do
120
+ begin
121
+ @proxy_result = block.call
122
+ rescue Exception => e
123
+ @proxy_exception = e
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # Get the result of the original method call. The original method will only be called once.
130
+ def __proxy_result__
131
+ @thread.join if @thread && @thread.alive?
132
+ @thread = nil
133
+ raise @proxy_exception if @proxy_exception
134
+ return @proxy_result
135
+ end
136
+
137
+ def __proxy_loaded__
138
+ !(@thread && @thread.alive?)
139
+ end
140
+ end
141
+ end
@@ -1,107 +1,86 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'lazy_methods', 'lazy_methods'))
2
- Object.send(:include, LazyMethods::InstanceMethods) unless Object.include?(LazyMethods::InstanceMethods)
3
- require File.expand_path(File.dirname(__FILE__) + '/method_tester')
1
+ require 'spec_helper'
4
2
 
5
- describe LazyMethods::InstanceMethods do
6
-
7
- let(:object) { MethodTester.new }
8
-
9
- it "should inject lazy method handling" do
10
- proxy = object.lazy_test("arg")
11
- proxy.to_s.should == "ARG"
12
- proxy.__proxy_loaded__.should == true
13
- end
14
-
15
- it "should return a proxy object that has not been invoked yet" do
16
- proxy = object.lazy_test("arg")
17
- proxy.__proxy_loaded__.should == false
18
- end
19
-
20
- it "should remove itself from stacktraces thrown in method_missing" do
21
- begin
22
- "object".call_a_missing_method_that_does_not_exist
23
- raise "should not get here"
24
- rescue => e
25
- async_source_file = File.expand_path("../../lib/lazy_methods/lazy_methods.rb", __FILE__)
26
- File.exist?(async_source_file).should == true
27
- e.backtrace.join("\n").should_not include(async_source_file)
3
+ describe LazyMethods do
4
+ context "lazy methods" do
5
+ let(:object){ LazyMethods::Tester.new }
6
+
7
+ it "should define a lazy version of a method" do
8
+ proxy = object.lazy_test_method("woo")
9
+ object.test_method_called.should == 0
10
+ proxy.should == "WOO"
11
+ object.test_method_called.should == 1
12
+ proxy.to_s
13
+ object.test_method_called.should == 1
28
14
  end
29
- end
30
- end
31
-
32
- describe LazyMethods::Proxy do
33
-
34
- let(:object) { MethodTester.new }
35
-
36
- it "should be able to wrap a method without executing it" do
37
- proxy = object.lazy_test("arg")
38
- object.test_called.should == 0
39
- end
40
-
41
- it "should execute the wrapped method when it needs to" do
42
- proxy = object.lazy_test("arg")
43
- proxy.to_s
44
- object.test_called.should == 1
45
- end
46
-
47
- it "should only execute the wrapped method once" do
48
- proxy = object.lazy_test("arg")
49
- proxy.to_s
50
- proxy.to_s
51
- object.test_called.should == 1
52
- end
53
-
54
- it "should allow nil as a valid proxied value" do
55
- proxy = object.lazy_test(nil)
56
- proxy.should_not
57
- object.test_called.should == 1
58
- end
59
-
60
- it "should allow blocks in the lazy method" do
61
- n = 1
62
- proxy = object.lazy_test("arg") do
63
- n = 2
15
+
16
+ it "should define a lazy version of a method that takes a block" do
17
+ block_evaled = false
18
+ proxy = object.lazy_test_method("woo"){ block_evaled = !block_evaled }
19
+ object.test_method_called.should == 0
20
+ block_evaled.should == false
21
+ proxy.should == "WOO"
22
+ object.test_method_called.should == 1
23
+ block_evaled.should == true
24
+ proxy.to_s
25
+ object.test_method_called.should == 1
26
+ block_evaled.should == true
64
27
  end
65
- n.should == 1
66
- proxy.to_s
67
- n.should == 2
68
- end
69
-
70
- it "should be indistinguishable from the real object" do
71
- proxy = object.lazy_test("arg")
72
- proxy.class.should == String
73
- proxy.kind_of?(String).should == true
74
- end
75
-
76
- it "should proxy core methods on Object" do
77
- proxy = "xxx".lazy_to_s
78
- proxy.should == "xxx"
79
- end
80
-
81
- it "should proxy missing methods" do
82
- proxy = object.lazy_find_test
83
- proxy.to_s.should == "FINDER"
84
- end
85
-
86
- it "should allow blocks in the lazy missing methods" do
87
- n = 1
88
- proxy = object.lazy_find_test do
89
- n = 2
28
+
29
+ it "should define a lazy version of a class method" do
30
+ LazyMethods::Tester.test_class_method_called = 0
31
+ block_evaled = false
32
+ proxy = LazyMethods::Tester.lazy_test_class_method("woo"){ block_evaled = !block_evaled }
33
+ LazyMethods::Tester.test_class_method_called.should == 0
34
+ block_evaled.should == false
35
+ proxy.should == "WOO"
36
+ LazyMethods::Tester.test_class_method_called.should == 1
37
+ block_evaled.should == true
38
+ proxy.to_s
39
+ LazyMethods::Tester.test_class_method_called.should == 1
40
+ block_evaled.should == true
90
41
  end
91
- n.should == 1
92
- proxy.to_s
93
- n.should == 2
94
- end
95
-
96
- it "should not interfere with the proxied object's method_missing" do
97
- real = object.find_test
98
- real.to_s.should == "FINDER"
99
42
  end
100
43
 
101
- it "should not interfere with real methods that begin with lazy_" do
102
- object.lazy_real_method_called.should == false
103
- object.lazy_real_method
104
- object.lazy_real_method_called.should == true
44
+ context "async methods" do
45
+ let(:object){ LazyMethods::Tester.new }
46
+
47
+ it "should define an asynchronous version of a method" do
48
+ proxy = object.async_test_method("woo")
49
+ object.test_method_called.should == 0
50
+ sleep(0.2)
51
+ object.test_method_called.should == 1
52
+ proxy.should == "WOO"
53
+ proxy.to_s
54
+ object.test_method_called.should == 1
55
+ end
56
+
57
+ it "should define an asynchronous version of a method that takes a block" do
58
+ block_evaled = false
59
+ proxy = object.async_test_method("woo"){ block_evaled = !block_evaled }
60
+ object.test_method_called.should == 0
61
+ block_evaled.should == false
62
+ sleep(0.2)
63
+ object.test_method_called.should == 1
64
+ block_evaled.should == true
65
+ proxy.should == "WOO"
66
+ proxy.to_s
67
+ object.test_method_called.should == 1
68
+ block_evaled.should == true
69
+ end
70
+
71
+ it "should define an asynchronous version of a class method" do
72
+ LazyMethods::Tester.test_class_method_called = 0
73
+ block_evaled = false
74
+ proxy = LazyMethods::Tester.async_test_class_method("woo"){ block_evaled = !block_evaled }
75
+ LazyMethods::Tester.test_class_method_called.should == 0
76
+ block_evaled.should == false
77
+ sleep(0.2)
78
+ LazyMethods::Tester.test_class_method_called.should == 1
79
+ block_evaled.should == true
80
+ proxy.should == "WOO"
81
+ proxy.to_s
82
+ LazyMethods::Tester.test_class_method_called.should == 1
83
+ block_evaled.should == true
84
+ end
105
85
  end
106
-
107
86
  end
@@ -1,31 +1,35 @@
1
- # This class is used for testing the lazy_methods plugin functionality.
1
+ # This class is used for testing the lazy_methods functionality.
2
2
 
3
- class MethodTester
3
+ class LazyMethods::Tester
4
+ include LazyMethods
4
5
 
5
- attr_reader :test_called, :lazy_real_method_called
6
+ attr_reader :test_method_called
7
+
8
+ define_lazy_methods :test_method, :to_s
9
+ define_async_methods :test_method, :to_s
10
+ define_lazy_class_methods :test_class_method, :to_s
11
+ define_async_class_methods :test_class_method, :to_s
6
12
 
7
13
  def initialize
8
- @test_called = 0
14
+ @test_method_called = 0
9
15
  @lazy_real_method_called = false
10
16
  end
11
17
 
12
- def test (arg)
13
- @test_called += 1
18
+ def test_method(arg)
19
+ sleep(0.02)
20
+ @test_method_called += 1
14
21
  yield if block_given?
15
22
  arg.upcase if arg
16
23
  end
17
24
 
18
- def lazy_real_method
19
- @lazy_real_method_called = true
20
- end
21
-
22
- def method_missing (method, *args, &block)
23
- if method.to_s[0, 5] == 'find_'
25
+ class << self
26
+ attr_accessor :test_class_method_called
27
+
28
+ def test_class_method(arg)
29
+ sleep(0.02)
30
+ @test_class_method_called = (self.test_class_method_called || 0) + 1
24
31
  yield if block_given?
25
- "FINDER"
26
- else
27
- super
32
+ arg.upcase if arg
28
33
  end
29
- end
30
-
34
+ end
31
35
  end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe LazyMethods::LazyProxy do
4
+ it "should wrap a block and proxy all methods to the result" do
5
+ proxy = LazyMethods::LazyProxy.new{ "xyz" }
6
+ proxy.size.should == 3
7
+ proxy.to_sym.should == :xyz
8
+ proxy.is_a?(String).should == true
9
+ proxy.should == "xyz"
10
+ end
11
+
12
+ it "should not evaluate the block until a method is called on it" do
13
+ value = "test"
14
+ proxy = LazyMethods::LazyProxy.new{ value << " called" }
15
+ value.should == "test"
16
+ proxy.should == "test called"
17
+ value.should == "test called"
18
+ end
19
+
20
+ it "should only evaluate the block once" do
21
+ value = "test"
22
+ proxy = LazyMethods::LazyProxy.new{ value << " called" }
23
+ value.should == "test"
24
+ proxy.should == "test called"
25
+ proxy.should == "test called"
26
+ proxy.should == "test called"
27
+ end
28
+
29
+ it "should be able to wrap nil" do
30
+ proxy = LazyMethods::LazyProxy.new{ nil }
31
+ proxy.should == nil
32
+ proxy.nil?.should == true
33
+ end
34
+
35
+ it "should tell if the block has been evaluated" do
36
+ proxy = LazyMethods::LazyProxy.new{ "xyz" }
37
+ proxy.__proxy_loaded__.should == false
38
+ proxy.to_s
39
+ proxy.__proxy_loaded__.should == true
40
+ end
41
+ end
42
+
43
+ describe LazyMethods::AsyncProxy do
44
+ it "should wrap a block and proxy all methods to the result" do
45
+ proxy = LazyMethods::AsyncProxy.new{ "xyz" }
46
+ proxy.size.should == 3
47
+ proxy.to_sym.should == :xyz
48
+ proxy.is_a?(String).should == true
49
+ proxy.should == "xyz"
50
+ end
51
+
52
+ it "should not block on evaluating the block until a method is called on it" do
53
+ value = "test"
54
+ proxy = LazyMethods::AsyncProxy.new{ sleep(0.25); value << " called" }
55
+ proxy.__proxy_loaded__.should == false
56
+ value.should == "test"
57
+ proxy.should == "test called"
58
+ value.should == "test called"
59
+ end
60
+
61
+ it "should only evaluate the block once" do
62
+ value = "test"
63
+ proxy = LazyMethods::AsyncProxy.new{ sleep(0.25); value << " called" }
64
+ value.should == "test"
65
+ proxy.should == "test called"
66
+ proxy.should == "test called"
67
+ proxy.should == "test called"
68
+ end
69
+
70
+ it "should be able to wrap nil" do
71
+ proxy = LazyMethods::AsyncProxy.new{ nil }
72
+ proxy.should == nil
73
+ proxy.nil?.should == true
74
+ end
75
+
76
+ it "should tell if the block has been evaluated" do
77
+ proxy = LazyMethods::AsyncProxy.new{ sleep(0.25); "xyz" }
78
+ proxy.__proxy_loaded__.should == false
79
+ proxy.to_s
80
+ proxy.__proxy_loaded__.should == true
81
+ end
82
+
83
+ if defined?(Thread.critical)
84
+ it "should not open a new thread if Thread.critical is true" do
85
+ Thread.should_receive(:new)
86
+ proxy = LazyMethods::AsyncProxy.new{ "abc" }
87
+ proxy.to_s
88
+
89
+ begin
90
+ Thread.critical = true
91
+ Thread.should_not_receive(:new)
92
+ proxy = LazyMethods::AsyncProxy.new{ "xyz" }
93
+ proxy.should == "xyz"
94
+ ensure
95
+ Thread.critical = false
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,26 +1,2 @@
1
- # This file is copied to ~/spec when you run 'ruby script/generate rspec'
2
- # from the project root directory.
3
- ENV["RAILS_ENV"] ||= "test"
4
- require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
5
- require 'spec/rails'
6
-
7
- Spec::Runner.configure do |config|
8
- config.use_transactional_fixtures = true
9
- config.use_instantiated_fixtures = false
10
- config.fixture_path = RAILS_ROOT + '/spec/fixtures'
11
- config.before(:each, :behaviour_type => :controller) do
12
- raise_controller_errors
13
- end
14
-
15
- # You can declare fixtures for each behaviour like this:
16
- # describe "...." do
17
- # fixtures :table_a, :table_b
18
- #
19
- # Alternatively, if you prefer to declare them only once, you can
20
- # do so here, like so ...
21
- #
22
- # config.global_fixtures = :table_a, :table_b
23
- #
24
- # If you declare global fixtures, be aware that they will be declared
25
- # for all of your examples, even those that don't use them.
26
- end
1
+ require File.expand_path("../../lib/lazy_methods.rb", __FILE__)
2
+ require File.expand_path("../method_tester.rb", __FILE__)
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_methods
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
5
4
  prerelease:
6
- segments:
7
- - 1
8
- - 0
9
- - 6
10
- version: 1.0.6
5
+ version: 2.0.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - Brian Durand
@@ -15,40 +10,54 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-01-24 00:00:00 -06:00
13
+ date: 2011-08-29 00:00:00 -05:00
19
14
  default_executable:
20
15
  dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rake
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *id001
21
27
  - !ruby/object:Gem::Dependency
22
28
  name: rspec
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.6.0
35
+ type: :runtime
23
36
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ requirement: &id003 !ruby/object:Gem::Requirement
25
41
  none: false
26
42
  requirements:
27
43
  - - ">="
28
44
  - !ruby/object:Gem::Version
29
- hash: 15
30
- segments:
31
- - 2
32
- - 0
33
- - 0
34
45
  version: 2.0.0
35
46
  type: :development
36
- version_requirements: *id001
47
+ prerelease: false
48
+ version_requirements: *id003
37
49
  - !ruby/object:Gem::Dependency
38
50
  name: jeweler
39
- prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
41
52
  none: false
42
53
  requirements:
43
54
  - - ">="
44
55
  - !ruby/object:Gem::Version
45
- hash: 3
46
- segments:
47
- - 0
48
56
  version: "0"
49
57
  type: :development
50
- version_requirements: *id002
51
- description: Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed. This pattern is useful when used with caching systems.
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ description: Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.
52
61
  email: brian@embellishedvisions.com
53
62
  executables: []
54
63
 
@@ -57,15 +66,18 @@ extensions: []
57
66
  extra_rdoc_files:
58
67
  - README.rdoc
59
68
  files:
69
+ - .travis.yml
70
+ - Gemfile
71
+ - Gemfile.lock
60
72
  - MIT-LICENSE
61
73
  - README.rdoc
62
74
  - Rakefile
63
75
  - VERSION
64
76
  - lazy_methods.gemspec
65
77
  - lib/lazy_methods.rb
66
- - lib/lazy_methods/lazy_methods.rb
67
78
  - spec/lazy_method_spec.rb
68
79
  - spec/method_tester.rb
80
+ - spec/proxy_spec.rb
69
81
  - spec/spec_helper.rb
70
82
  has_rdoc: true
71
83
  homepage: http://github.com/bdurand/lazy_methods
@@ -83,27 +95,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
95
  requirements:
84
96
  - - ">="
85
97
  - !ruby/object:Gem::Version
86
- hash: 3
87
- segments:
88
- - 0
89
98
  version: "0"
90
99
  required_rubygems_version: !ruby/object:Gem::Requirement
91
100
  none: false
92
101
  requirements:
93
102
  - - ">="
94
103
  - !ruby/object:Gem::Version
95
- hash: 3
96
- segments:
97
- - 0
98
104
  version: "0"
99
105
  requirements: []
100
106
 
101
107
  rubyforge_project:
102
- rubygems_version: 1.4.1
108
+ rubygems_version: 1.6.2
103
109
  signing_key:
104
110
  specification_version: 3
105
- summary: Gem that adds lazy method wrapping to every object. Preceding any method with lazy_ will defer the invocation until the result is actually needed.
111
+ summary: Gem that adds lazy method delegation methods. Using this gem you can easily define lazy loading or asynchronous versions of specific methods. Lazy loading is useful when used with caching systems while asynchronous methods can improve throughput on I/O bound processes like making several HTTP calls in row.
106
112
  test_files:
107
113
  - spec/lazy_method_spec.rb
108
114
  - spec/method_tester.rb
115
+ - spec/proxy_spec.rb
109
116
  - spec/spec_helper.rb
@@ -1,88 +0,0 @@
1
- # Including this module will provide the lazy method handling for the class where any method can be
2
- # prefixed with lazy_ to defer execution. By default, the plugin includes it in Object so it is universally
3
- # available.
4
- module LazyMethods
5
-
6
- module InstanceMethods
7
- def self.included (base)
8
- base.send :alias_method, :method_missing_without_lazy, :method_missing
9
- base.send :alias_method, :method_missing, :method_missing_with_lazy
10
- end
11
-
12
- # Override missing method to add the lazy method handling
13
- def method_missing_with_lazy (method, *args, &block)
14
- method = method.to_s
15
- if method[0, 5] == 'lazy_'
16
- method = method.to_s
17
- called_method = method[5, method.length]
18
- return Proxy.new(self, called_method, args, &block)
19
- else
20
- # Keep track of the current missing method calls to keep out of an infinite loop
21
- stack = Thread.current[:lazy_method_missing_methods] ||= []
22
- sig = MethodSignature.new(self, method)
23
- raise NoMethodError.new("undefined method `#{method}' for #{self}") if stack.include?(sig)
24
- begin
25
- stack.push(sig)
26
- return method_missing_without_lazy(method, *args, &block)
27
- rescue Exception => e
28
- # Strip this method from the stack trace as it adds confusion
29
- e.backtrace.reject!{|line| line.include?(__FILE__)}
30
- raise e
31
- ensure
32
- stack.pop
33
- end
34
- end
35
- end
36
- end
37
-
38
- # This class is used to keep track of methods being called.
39
- class MethodSignature
40
-
41
- attr_reader :object, :method
42
-
43
- def initialize (obj, method)
44
- @object = obj
45
- @method = method
46
- end
47
-
48
- def eql? (sig)
49
- sig.kind_of(MethodSignature) && sig.object == @object && sig.method == @method
50
- end
51
-
52
- end
53
-
54
- # The proxy object does all the heavy lifting.
55
- class Proxy
56
- # These methods we don't want to override. All other existing methods will be redefined.
57
- PROTECTED_METHODS = %w(initialize method_missing __proxy_result__ __proxy_loaded__ __send__ __id__ object_id)
58
-
59
- # Override already defined methods on Object to proxy them to the result object
60
- instance_methods.each do |m|
61
- undef_method(m) unless PROTECTED_METHODS.include?(m.to_s)
62
- end
63
-
64
- def initialize (obj, method, args = nil, &block)
65
- @object = obj
66
- @method = method
67
- @args = args || []
68
- @block = block
69
- end
70
-
71
- # Get the result of the original method call. The original method will only be called once.
72
- def __proxy_result__
73
- @proxy_result = @object.send(@method, *@args, &@block) unless defined?(@proxy_result)
74
- @proxy_result
75
- end
76
-
77
- # Helper method that indicates if the proxy has loaded the original method results yet.
78
- def __proxy_loaded__
79
- !!defined?(@proxy_result)
80
- end
81
-
82
- # All missing methods are proxied to the original result object.
83
- def method_missing (method, *args, &block)
84
- __proxy_result__.send(method, *args, &block)
85
- end
86
- end
87
-
88
- end