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.
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -0
- data/README.rdoc +57 -19
- data/Rakefile +2 -11
- data/VERSION +1 -1
- data/lazy_methods.gemspec +16 -6
- data/lib/lazy_methods.rb +141 -2
- data/spec/lazy_method_spec.rb +78 -99
- data/spec/method_tester.rb +21 -17
- data/spec/proxy_spec.rb +99 -0
- data/spec/spec_helper.rb +2 -26
- metadata +37 -30
- data/lib/lazy_methods/lazy_methods.rb +0 -88
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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)
|
data/README.rdoc
CHANGED
@@ -1,33 +1,71 @@
|
|
1
|
-
|
1
|
+
= LazyMethods
|
2
2
|
|
3
|
-
This
|
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
|
-
|
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
|
-
|
13
|
+
=== Example
|
10
14
|
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
44
|
+
== Usage Notes
|
24
45
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
67
|
+
== Upgrade Notes
|
30
68
|
|
31
|
-
|
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
|
-
|
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
|
32
|
-
gem.description = %Q
|
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
|
+
2.0.0
|
data/lazy_methods.gemspec
CHANGED
@@ -5,36 +5,40 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{lazy_methods}
|
8
|
-
s.version = "
|
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-
|
13
|
-
s.description = %q{Gem that adds lazy method
|
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.
|
34
|
-
s.summary = %q{Gem that adds lazy method
|
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
|
data/lib/lazy_methods.rb
CHANGED
@@ -1,2 +1,141 @@
|
|
1
|
-
require
|
2
|
-
|
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
|
data/spec/lazy_method_spec.rb
CHANGED
@@ -1,107 +1,86 @@
|
|
1
|
-
require
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
102
|
-
object.
|
103
|
-
|
104
|
-
|
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
|
data/spec/method_tester.rb
CHANGED
@@ -1,31 +1,35 @@
|
|
1
|
-
# This class is used for testing the lazy_methods
|
1
|
+
# This class is used for testing the lazy_methods functionality.
|
2
2
|
|
3
|
-
class
|
3
|
+
class LazyMethods::Tester
|
4
|
+
include LazyMethods
|
4
5
|
|
5
|
-
attr_reader :
|
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
|
-
@
|
14
|
+
@test_method_called = 0
|
9
15
|
@lazy_real_method_called = false
|
10
16
|
end
|
11
17
|
|
12
|
-
def
|
13
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
else
|
27
|
-
super
|
32
|
+
arg.upcase if arg
|
28
33
|
end
|
29
|
-
end
|
30
|
-
|
34
|
+
end
|
31
35
|
end
|
data/spec/proxy_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
CHANGED
@@ -1,26 +1,2 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
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-
|
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
|
-
|
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
|
-
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
37
49
|
- !ruby/object:Gem::Dependency
|
38
50
|
name: jeweler
|
39
|
-
|
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
|
-
|
51
|
-
|
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.
|
108
|
+
rubygems_version: 1.6.2
|
103
109
|
signing_key:
|
104
110
|
specification_version: 3
|
105
|
-
summary: Gem that adds lazy method
|
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
|