lazy_methods 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +33 -0
- data/Rakefile +30 -28
- data/VERSION +1 -0
- data/lazy_methods.gemspec +58 -0
- data/lib/lazy_methods.rb +1 -1
- data/spec/lazy_method_spec.rb +20 -24
- data/spec/method_tester.rb +1 -1
- data/spec/spec_helper.rb +26 -0
- metadata +68 -22
- data/README +0 -37
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
== LazyMethods
|
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.
|
4
|
+
|
5
|
+
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
|
+
|
7
|
+
=== A simple example:
|
8
|
+
|
9
|
+
The business logic:
|
10
|
+
|
11
|
+
def my_action
|
12
|
+
@records = MyResource.find(params[:name])
|
13
|
+
end
|
14
|
+
|
15
|
+
And in the view:
|
16
|
+
|
17
|
+
<% cache(params[:names]) -%>
|
18
|
+
<% @records.each do |record| -%>
|
19
|
+
<div><%=record.title%></div>
|
20
|
+
<% end -%>
|
21
|
+
<% end -%>
|
22
|
+
|
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.
|
24
|
+
|
25
|
+
def my_action
|
26
|
+
@records = MyResource.lazy_find(params[:name])
|
27
|
+
end
|
28
|
+
|
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.
|
30
|
+
|
31
|
+
== Testing
|
32
|
+
|
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.
|
data/Rakefile
CHANGED
@@ -1,43 +1,45 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'rake/rdoctask'
|
4
|
-
require 'rake/gempackagetask'
|
5
|
-
require 'spec/rake/spectask'
|
6
4
|
|
7
5
|
desc 'Default: run unit tests.'
|
8
6
|
task :default => :test
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
begin
|
9
|
+
require 'spec/rake/spectask'
|
10
|
+
desc 'Test the gem.'
|
11
|
+
Spec::Rake::SpecTask.new(:test) do |t|
|
12
|
+
t.spec_files = FileList.new('spec/**/*_spec.rb')
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
tast :test do
|
16
|
+
STDERR.puts "You must have rspec >= 1.3.0 to run the tests"
|
17
|
+
end
|
13
18
|
end
|
14
19
|
|
15
|
-
desc 'Generate documentation for the
|
20
|
+
desc 'Generate documentation for the gem.'
|
16
21
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
22
|
rdoc.rdoc_dir = 'rdoc'
|
18
|
-
rdoc.options << '--title' << '
|
19
|
-
rdoc.rdoc_files.include('README')
|
23
|
+
rdoc.options << '--title' << 'Lazy Methods' << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc'
|
24
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
25
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
26
|
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
s.email = 'brian@embellishedvisions.com'
|
38
|
-
end
|
39
|
-
|
40
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
41
|
-
pkg.need_tar = true
|
42
|
-
end
|
28
|
+
begin
|
29
|
+
require 'jeweler'
|
30
|
+
Jeweler::Tasks.new do |gem|
|
31
|
+
gem.name = "lazy_methods"
|
32
|
+
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.}
|
33
|
+
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.)
|
34
|
+
gem.email = "brian@embellishedvisions.com"
|
35
|
+
gem.homepage = "http://github.com/bdurand/acts_as_revisionable"
|
36
|
+
gem.authors = ["Brian Durand"]
|
37
|
+
gem.rdoc_options = ["--charset=UTF-8", "--main", "README.rdoc"]
|
38
|
+
|
39
|
+
gem.add_development_dependency('rspec', '>= 1.3.0')
|
40
|
+
gem.add_development_dependency('jeweler')
|
41
|
+
end
|
43
42
|
|
43
|
+
Jeweler::GemcutterTasks.new
|
44
|
+
rescue LoadError
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.3
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{lazy_methods}
|
8
|
+
s.version = "1.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Brian Durand"]
|
12
|
+
s.date = %q{2010-06-22}
|
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.}
|
14
|
+
s.email = %q{brian@embellishedvisions.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"lazy_methods.gemspec",
|
25
|
+
"lib/lazy_methods.rb",
|
26
|
+
"lib/lazy_methods/lazy_methods.rb",
|
27
|
+
"spec/lazy_method_spec.rb",
|
28
|
+
"spec/method_tester.rb",
|
29
|
+
"spec/spec_helper.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/bdurand/acts_as_revisionable}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8", "--main", "README.rdoc"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.rubygems_version = %q{1.3.7}
|
35
|
+
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.test_files = [
|
37
|
+
"spec/lazy_method_spec.rb",
|
38
|
+
"spec/method_tester.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
|
48
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<rspec>, [">= 1.3.0"])
|
51
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<rspec>, [">= 1.3.0"])
|
55
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
data/lib/lazy_methods.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require 'lazy_methods/lazy_methods'
|
1
|
+
require File.expand_path('../lazy_methods/lazy_methods', __FILE__)
|
2
2
|
Object.send(:include, LazyMethods::InstanceMethods) unless Object.include?(LazyMethods::InstanceMethods)
|
data/spec/lazy_method_spec.rb
CHANGED
@@ -4,18 +4,16 @@ require File.expand_path(File.dirname(__FILE__) + '/method_tester')
|
|
4
4
|
|
5
5
|
context "LazyMethods InstanceMethods" do
|
6
6
|
|
7
|
-
|
8
|
-
@object = MethodTester.new
|
9
|
-
end
|
7
|
+
let(:object) { MethodTester.new }
|
10
8
|
|
11
9
|
specify "should inject lazy method handling" do
|
12
|
-
proxy =
|
10
|
+
proxy = object.lazy_test("arg")
|
13
11
|
proxy.to_s.should == "ARG"
|
14
12
|
proxy.__proxy_loaded__.should == true
|
15
13
|
end
|
16
14
|
|
17
15
|
specify "should return a proxy object that has not been invoked yet" do
|
18
|
-
proxy =
|
16
|
+
proxy = object.lazy_test("arg")
|
19
17
|
proxy.__proxy_loaded__.should == false
|
20
18
|
end
|
21
19
|
|
@@ -23,37 +21,35 @@ end
|
|
23
21
|
|
24
22
|
context "LazyMethods Proxy" do
|
25
23
|
|
26
|
-
|
27
|
-
@object = MethodTester.new
|
28
|
-
end
|
24
|
+
let(:object) { MethodTester.new }
|
29
25
|
|
30
26
|
specify "should be able to wrap a method without executing it" do
|
31
|
-
proxy =
|
32
|
-
|
27
|
+
proxy = object.lazy_test("arg")
|
28
|
+
object.test_called.should == 0
|
33
29
|
end
|
34
30
|
|
35
31
|
specify "should execute the wrapped method when it needs to" do
|
36
|
-
proxy =
|
32
|
+
proxy = object.lazy_test("arg")
|
37
33
|
proxy.to_s
|
38
|
-
|
34
|
+
object.test_called.should == 1
|
39
35
|
end
|
40
36
|
|
41
37
|
specify "should only execute the wrapped method once" do
|
42
|
-
proxy =
|
38
|
+
proxy = object.lazy_test("arg")
|
43
39
|
proxy.to_s
|
44
40
|
proxy.to_s
|
45
|
-
|
41
|
+
object.test_called.should == 1
|
46
42
|
end
|
47
43
|
|
48
44
|
specify "should allow nil as a valid proxied value" do
|
49
|
-
proxy =
|
45
|
+
proxy = object.lazy_test(nil)
|
50
46
|
proxy.should_not
|
51
|
-
|
47
|
+
object.test_called.should == 1
|
52
48
|
end
|
53
49
|
|
54
50
|
specify "should allow blocks in the lazy method" do
|
55
51
|
n = 1
|
56
|
-
proxy =
|
52
|
+
proxy = object.lazy_test("arg") do
|
57
53
|
n = 2
|
58
54
|
end
|
59
55
|
n.should == 1
|
@@ -62,7 +58,7 @@ context "LazyMethods Proxy" do
|
|
62
58
|
end
|
63
59
|
|
64
60
|
specify "should be indistinguishable from the real object" do
|
65
|
-
proxy =
|
61
|
+
proxy = object.lazy_test("arg")
|
66
62
|
proxy.class.should == String
|
67
63
|
proxy.kind_of?(String).should == true
|
68
64
|
end
|
@@ -73,13 +69,13 @@ context "LazyMethods Proxy" do
|
|
73
69
|
end
|
74
70
|
|
75
71
|
specify "should proxy missing methods" do
|
76
|
-
proxy =
|
72
|
+
proxy = object.lazy_find_test
|
77
73
|
proxy.to_s.should == "FINDER"
|
78
74
|
end
|
79
75
|
|
80
76
|
specify "should allow blocks in the lazy missing methods" do
|
81
77
|
n = 1
|
82
|
-
proxy =
|
78
|
+
proxy = object.lazy_find_test do
|
83
79
|
n = 2
|
84
80
|
end
|
85
81
|
n.should == 1
|
@@ -88,14 +84,14 @@ context "LazyMethods Proxy" do
|
|
88
84
|
end
|
89
85
|
|
90
86
|
specify "should not interfere with the proxied object's method_missing" do
|
91
|
-
real =
|
87
|
+
real = object.find_test
|
92
88
|
real.to_s.should == "FINDER"
|
93
89
|
end
|
94
90
|
|
95
91
|
specify "should not interfere with real methods that begin with lazy_" do
|
96
|
-
|
97
|
-
|
98
|
-
|
92
|
+
object.lazy_real_method_called.should == false
|
93
|
+
object.lazy_real_method
|
94
|
+
object.lazy_real_method_called.should == true
|
99
95
|
end
|
100
96
|
|
101
97
|
end
|
data/spec/method_tester.rb
CHANGED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
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
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lazy_methods
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 17
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 1.0.3
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Brian Durand
|
@@ -9,56 +15,96 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2010-06-22 00:00:00 -05:00
|
13
19
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 27
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
version: 1.3.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: jeweler
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
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.
|
17
52
|
email: brian@embellishedvisions.com
|
18
53
|
executables: []
|
19
54
|
|
20
55
|
extensions: []
|
21
56
|
|
22
57
|
extra_rdoc_files:
|
23
|
-
- README
|
58
|
+
- README.rdoc
|
24
59
|
files:
|
25
|
-
-
|
26
|
-
- lib/lazy_methods.rb
|
27
|
-
- lib/lazy_methods/lazy_methods.rb
|
60
|
+
- .gitignore
|
28
61
|
- MIT-LICENSE
|
62
|
+
- README.rdoc
|
29
63
|
- Rakefile
|
30
|
-
-
|
64
|
+
- VERSION
|
65
|
+
- lazy_methods.gemspec
|
66
|
+
- lib/lazy_methods.rb
|
67
|
+
- lib/lazy_methods/lazy_methods.rb
|
68
|
+
- spec/lazy_method_spec.rb
|
69
|
+
- spec/method_tester.rb
|
70
|
+
- spec/spec_helper.rb
|
31
71
|
has_rdoc: true
|
32
|
-
homepage: http://
|
72
|
+
homepage: http://github.com/bdurand/acts_as_revisionable
|
73
|
+
licenses: []
|
74
|
+
|
33
75
|
post_install_message:
|
34
76
|
rdoc_options:
|
35
|
-
- --
|
36
|
-
- LazyMethods
|
37
|
-
- --line-numbers
|
38
|
-
- --inline-source
|
77
|
+
- --charset=UTF-8
|
39
78
|
- --main
|
40
|
-
- README
|
79
|
+
- README.rdoc
|
41
80
|
require_paths:
|
42
81
|
- lib
|
43
82
|
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
44
84
|
requirements:
|
45
85
|
- - ">="
|
46
86
|
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
47
90
|
version: "0"
|
48
|
-
version:
|
49
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
50
93
|
requirements:
|
51
94
|
- - ">="
|
52
95
|
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
53
99
|
version: "0"
|
54
|
-
version:
|
55
100
|
requirements: []
|
56
101
|
|
57
|
-
rubyforge_project:
|
58
|
-
rubygems_version: 1.
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.7
|
59
104
|
signing_key:
|
60
|
-
specification_version:
|
61
|
-
summary:
|
105
|
+
specification_version: 3
|
106
|
+
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.
|
62
107
|
test_files:
|
63
108
|
- spec/lazy_method_spec.rb
|
64
109
|
- spec/method_tester.rb
|
110
|
+
- spec/spec_helper.rb
|
data/README
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
== LazyMethods
|
2
|
-
|
3
|
-
So your Rails application is successful beyond your wildest dreams and now your meager servers are straining under the load. You figure you'll add some caching to your views and all will be well again. Unfortunately, your controller actions load up all sorts of instance variables with records from the database. The caching won't do you much good if your database is still getting slammed. You could move your query logic to the views behind the caching layer, but that just feels icky. Besides, that will break all your tests and you really don't feel like fixing them.
|
4
|
-
|
5
|
-
== Enter LazyMethods.
|
6
|
-
|
7
|
-
This plugin 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. This way you can continue to set up the business logic in your controller and have it only actually executed only as needed.
|
8
|
-
|
9
|
-
If you add fragment caching to your views and the cache returns a value and bypasses your view code, the method will never be invoked. Thanks to the magic of Ruby the proxy object will even act like the class it is proxying in class to class and kind_of?
|
10
|
-
|
11
|
-
A simple example:
|
12
|
-
|
13
|
-
The normal way to do it (at least according to every tutorial):
|
14
|
-
|
15
|
-
def index
|
16
|
-
@records = MyRecord.find(:all, :conditions => {:name => params[:name]})
|
17
|
-
end
|
18
|
-
|
19
|
-
And in the view:
|
20
|
-
|
21
|
-
<% cache(params[:names]) -%>
|
22
|
-
<% @records.each do |record| -%>
|
23
|
-
<div><%=record.title%></div>
|
24
|
-
<% end -%>
|
25
|
-
<% end -%>
|
26
|
-
|
27
|
-
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 code from the action and simple add it back into the view. However, this just feels wrong and is inherently harder to test. Instead just use a lazy method.
|
28
|
-
|
29
|
-
def index
|
30
|
-
@records = MyRecord.lazy_find(:all, :conditions => {:name => params[:name]})
|
31
|
-
end
|
32
|
-
|
33
|
-
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.
|
34
|
-
|
35
|
-
== Testing
|
36
|
-
|
37
|
-
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.
|