lazy_methods 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Brian Durand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,37 @@
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.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'spec/rake/spectask'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => :test
9
+
10
+ desc 'Test the lazy_methods plugin.'
11
+ Spec::Rake::SpecTask.new(:test) do |t|
12
+ t.spec_files = 'spec/**/*_spec.rb'
13
+ end
14
+
15
+ desc 'Generate documentation for the lazy_methods plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.options << '--title' << 'LazyMethods' << '--line-numbers' << '--inline-source' << '--main' << 'README'
19
+ rdoc.rdoc_files.include('README')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ spec = Gem::Specification.new do |s|
24
+ s.name = "lazy_methods"
25
+ s.version = "1.0.2"
26
+ s.author = "Brian Durand"
27
+ s.platform = Gem::Platform::RUBY
28
+ s.summary = "Provide lazy method calls for all methods on every object to aid in caching."
29
+ s.files = FileList["lib/**/*", "MIT-LICENSE", 'Rakefile'].to_a
30
+ s.require_path = "lib"
31
+ s.test_files = FileList["{spec}/**/*.rb"].to_a
32
+ s.has_rdoc = true
33
+ s.rdoc_options << '--title' << 'LazyMethods' << '--line-numbers' << '--inline-source' << '--main' << 'README'
34
+ s.extra_rdoc_files = ["README"]
35
+ s.homepage = "http://lazymethods.rubyforge.org"
36
+ s.rubyforge_project = "lazymethods"
37
+ s.email = 'brian@embellishedvisions.com'
38
+ end
39
+
40
+ Rake::GemPackageTask.new(spec) do |pkg|
41
+ pkg.need_tar = true
42
+ end
43
+
@@ -0,0 +1,82 @@
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
+ if method.to_s[0, 5] == 'lazy_'
15
+ method = method.to_s
16
+ return Proxy.new(self, method[5, method.length].to_sym, args, &block)
17
+ else
18
+ # Keep track of the current missing method calls to keep out of an infinite loop
19
+ stack = Thread.current[:lazy_method_missing_methods] ||= []
20
+ sig = MethodSignature.new(self, method)
21
+ raise NoMethodError.new("undefined method `#{method}' for #{self}") if stack.include?(sig)
22
+ begin
23
+ stack.push(sig)
24
+ return method_missing_without_lazy(method, *args, &block)
25
+ ensure
26
+ stack.pop
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ # This class is used to keep track of methods being called.
33
+ class MethodSignature
34
+
35
+ attr_reader :object, :method
36
+
37
+ def initialize (obj, method)
38
+ @object = obj
39
+ @method = method
40
+ end
41
+
42
+ def eql? (sig)
43
+ sig.kind_of(MethodSignature) and sig.object == @object and sig.method == @method
44
+ end
45
+
46
+ end
47
+
48
+ # The proxy object does all the heavy lifting.
49
+ class Proxy
50
+ # These methods we don't want to override. All other existing methods will be redefined.
51
+ PROTECTED_METHODS = %w(initialize __proxy_result__ __proxy_loaded__ method_missing)
52
+
53
+ def initialize (obj, method, args = nil, &block)
54
+ @object = obj
55
+ @method = method
56
+ @args = args || []
57
+ @block = block
58
+
59
+ # Override already defined methods on Object to proxy them to the result object
60
+ methods.each do |m|
61
+ eval "def self.#{m} (*args, &block); __proxy_result__.send(:#{m}, *args, &block); end" unless PROTECTED_METHODS.include?(m)
62
+ end
63
+ end
64
+
65
+ # Get the result of the original method call. The original method will only be called once.
66
+ def __proxy_result__
67
+ @proxy_result = @object.send(@method, *@args, &@block) unless defined?(@proxy_result)
68
+ @proxy_result
69
+ end
70
+
71
+ # Helper method that indicates if the proxy has loaded the original method results yet.
72
+ def __proxy_loaded__
73
+ !!defined?(@proxy_result)
74
+ end
75
+
76
+ # All missing methods are proxied to the original result object.
77
+ def method_missing (method, *args, &block)
78
+ __proxy_result__.send(method, *args, &block)
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,2 @@
1
+ require 'lazy_methods/lazy_methods'
2
+ Object.send(:include, LazyMethods::InstanceMethods) unless Object.include?(LazyMethods::InstanceMethods)
@@ -0,0 +1,101 @@
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')
4
+
5
+ context "LazyMethods InstanceMethods" do
6
+
7
+ setup do
8
+ @object = MethodTester.new
9
+ end
10
+
11
+ specify "should inject lazy method handling" do
12
+ proxy = @object.lazy_test("arg")
13
+ proxy.to_s.should == "ARG"
14
+ proxy.__proxy_loaded__.should == true
15
+ end
16
+
17
+ specify "should return a proxy object that has not been invoked yet" do
18
+ proxy = @object.lazy_test("arg")
19
+ proxy.__proxy_loaded__.should == false
20
+ end
21
+
22
+ end
23
+
24
+ context "LazyMethods Proxy" do
25
+
26
+ setup do
27
+ @object = MethodTester.new
28
+ end
29
+
30
+ specify "should be able to wrap a method without executing it" do
31
+ proxy = @object.lazy_test("arg")
32
+ @object.test_called.should == 0
33
+ end
34
+
35
+ specify "should execute the wrapped method when it needs to" do
36
+ proxy = @object.lazy_test("arg")
37
+ proxy.to_s
38
+ @object.test_called.should == 1
39
+ end
40
+
41
+ specify "should only execute the wrapped method once" do
42
+ proxy = @object.lazy_test("arg")
43
+ proxy.to_s
44
+ proxy.to_s
45
+ @object.test_called.should == 1
46
+ end
47
+
48
+ specify "should allow nil as a valid proxied value" do
49
+ proxy = @object.lazy_test(nil)
50
+ proxy.should_not
51
+ @object.test_called.should == 1
52
+ end
53
+
54
+ specify "should allow blocks in the lazy method" do
55
+ n = 1
56
+ proxy = @object.lazy_test("arg") do
57
+ n = 2
58
+ end
59
+ n.should == 1
60
+ proxy.to_s
61
+ n.should == 2
62
+ end
63
+
64
+ specify "should be indistinguishable from the real object" do
65
+ proxy = @object.lazy_test("arg")
66
+ proxy.class.should == String
67
+ proxy.kind_of?(String).should == true
68
+ end
69
+
70
+ specify "should proxy core methods on Object" do
71
+ proxy = "xxx".lazy_to_s
72
+ proxy.should == "xxx"
73
+ end
74
+
75
+ specify "should proxy missing methods" do
76
+ proxy = @object.lazy_find_test
77
+ proxy.to_s.should == "FINDER"
78
+ end
79
+
80
+ specify "should allow blocks in the lazy missing methods" do
81
+ n = 1
82
+ proxy = @object.lazy_find_test do
83
+ n = 2
84
+ end
85
+ n.should == 1
86
+ proxy.to_s
87
+ n.should == 2
88
+ end
89
+
90
+ specify "should not interfere with the proxied object's method_missing" do
91
+ real = @object.find_test
92
+ real.to_s.should == "FINDER"
93
+ end
94
+
95
+ specify "should not interfere with real methods that begin with lazy_" do
96
+ @object.lazy_real_method_called.should == false
97
+ @object.lazy_real_method
98
+ @object.lazy_real_method_called.should == true
99
+ end
100
+
101
+ end
@@ -0,0 +1,31 @@
1
+ # This class is used for testing the lazy_methods plugin functionality.
2
+
3
+ class MethodTester
4
+
5
+ attr_reader :test_called, :lazy_real_method_called
6
+
7
+ def initialize
8
+ @test_called = 0
9
+ @lazy_real_method_called = false
10
+ end
11
+
12
+ def test (arg)
13
+ @test_called += 1
14
+ yield if block_given?
15
+ arg.upcase if arg
16
+ end
17
+
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.starts_with?('find_')
24
+ yield if block_given?
25
+ "FINDER"
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lazy_methods
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Brian Durand
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-01-17 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: brian@embellishedvisions.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - lib/lazy_methods
26
+ - lib/lazy_methods.rb
27
+ - lib/lazy_methods/lazy_methods.rb
28
+ - MIT-LICENSE
29
+ - Rakefile
30
+ - README
31
+ has_rdoc: true
32
+ homepage: http://lazymethods.rubyforge.org
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --title
36
+ - LazyMethods
37
+ - --line-numbers
38
+ - --inline-source
39
+ - --main
40
+ - README
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project: lazymethods
58
+ rubygems_version: 1.0.1
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: Provide lazy method calls for all methods on every object to aid in caching.
62
+ test_files:
63
+ - spec/lazy_method_spec.rb
64
+ - spec/method_tester.rb