async_methods 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 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.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ == AsyncMethods
2
+
3
+ For those times you application is bound on I/O...
4
+
5
+ == Enter AsyncMethods.
6
+
7
+ This plugin adds a virtual asynchronous version of every method on every class. Asynchronouse methods have the same name as the original method name but prefixed with "async_". An asynchronous method will invoke to original method in a new thread and immediately return a proxy object that looks and acts just like the result from calling the actual method. When this proxy object is accessed for the first time, it will only then wait for the thread executing the method to finish.
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:
14
+
15
+ def index
16
+ @record_1 = MyResource.load(:first, :conditions => {:name => params[:name_1]})
17
+ @record_2 = MyResource.load(:first, :conditions => {:name => params[:name_2]})
18
+ @record_3 = MyResource.load(:first, :conditions => {:name => params[:name_3]})
19
+ end
20
+
21
+ If the calls to load the resource each takes an average of 0.1 seconds, loading all three will take 0.3 seconds. In a web application this can start adding up quickly, especially under a heavy load. However, by using asynchronous methods like this, all three calls will happen in parallel and they should all complete in 0.1 seconds.
22
+
23
+ def index
24
+ @record_1 = MyResource.async_load(:first, :conditions => {:name => params[:name_1]})
25
+ @record_2 = MyResource.async_load(:first, :conditions => {:name => params[:name_2]})
26
+ @record_3 = MyResource.async_load(:first, :conditions => {:name => params[:name_3]})
27
+ end
28
+
29
+ == Be Careful
30
+
31
+ Because underneath it all new threads are being spawned, you must be careful to make sure that the code you are calling is thread safe. For example, the default configuration for ActiveRecord is not thread safe so loading several calls from the database at once would cause you problems. This plugin was originally written to speed up parallel loads of ActiveResource records, so that should be safe to do.
32
+
33
+ == Testing
34
+
35
+ 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,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
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
18
+ end
19
+
20
+ desc 'Generate documentation for the gem.'
21
+ Rake::RDocTask.new(:rdoc) do |rdoc|
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.options << '--title' << 'Async Methods' << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc'
24
+ rdoc.rdoc_files.include('README.rdoc')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
27
+
28
+ begin
29
+ require 'jeweler'
30
+ Jeweler::Tasks.new do |gem|
31
+ gem.name = "async_methods"
32
+ gem.summary = %Q{Gem that adds asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes.}
33
+ gem.description = %Q(Gem that adds asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes. This is intended to improve throughput on I/O bound processes like making several HTTP calls in row.)
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
42
+
43
+ Jeweler::GemcutterTasks.new
44
+ rescue LoadError
45
+ end
46
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -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{async_methods}
8
+ s.version = "1.0.0"
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 asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes. This is intended to improve throughput on I/O bound processes like making several HTTP calls in row.}
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
+ "async_methods.gemspec",
25
+ "lib/async_methods.rb",
26
+ "lib/async_methods/async_methods.rb",
27
+ "spec/async_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 asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes.}
36
+ s.test_files = [
37
+ "spec/async_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
+
@@ -0,0 +1,95 @@
1
+ # Including this module will provide the asynchronous method handling for the class where any method can be
2
+ # prefixed with async_ to continue execution without waiting for results. By default, the plugin includes
3
+ # it in Object so it is universally available.
4
+ module AsyncMethods
5
+
6
+ module InstanceMethods
7
+ def self.included (base)
8
+ base.send :alias_method, :method_missing_without_async, :method_missing
9
+ base.send :alias_method, :method_missing, :method_missing_with_async
10
+ end
11
+
12
+ # Override missing method to add the async method handling
13
+ def method_missing_with_async (method, *args, &block)
14
+ if method.to_s[0, 6] == 'async_'
15
+ method = method.to_s
16
+ return Proxy.new(self, method[6 , 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[:async_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_async(method, *args, &block)
25
+ ensure
26
+ stack.pop
27
+ end
28
+ end
29
+ end
30
+
31
+ # Call a block asynchronously.
32
+ def asynchronous_block (&block)
33
+ Proxy.new(nil, nil, nil, &block)
34
+ end
35
+ end
36
+
37
+ # This class is used to keep track of methods being called.
38
+ class MethodSignature
39
+
40
+ attr_reader :object, :method
41
+
42
+ def initialize (obj, method)
43
+ @object = obj
44
+ @method = method.to_sym
45
+ end
46
+
47
+ def eql? (sig)
48
+ sig.kind_of(MethodSignature) and sig.object == @object and sig.method == @method
49
+ end
50
+
51
+ end
52
+
53
+ # The proxy object does all the heavy lifting.
54
+ class Proxy
55
+ # These methods we don't want to override. All other existing methods will be redefined.
56
+ PROTECTED_METHODS = %w(initialize __proxy_result__ __proxy_loaded__ method_missing)
57
+
58
+ def initialize (obj, method, args = [], &block)
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
+
64
+ @thread = Thread.new do
65
+ begin
66
+ if obj and method
67
+ @proxy_result = obj.send(method, *args, &block)
68
+ else
69
+ @proxy_result = block.call
70
+ end
71
+ rescue Object => e
72
+ @proxy_exception = e
73
+ end
74
+ end
75
+ end
76
+
77
+ # Get the result of the original method call. The original method will only be called once.
78
+ def __proxy_result__
79
+ @thread.join if @thread && @thread.alive?
80
+ @thread = nil
81
+ raise @proxy_exception if @proxy_exception
82
+ return @proxy_result
83
+ end
84
+
85
+ def __proxy_loaded__
86
+ !(@thread && @thread.alive?)
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
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../async_methods/async_methods', __FILE__)
2
+ Object.send(:include, AsyncMethods::InstanceMethods) unless Object.include?(AsyncMethods::InstanceMethods)
@@ -0,0 +1,102 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'async_methods', 'async_methods'))
2
+ Object.send(:include, AsyncMethods::InstanceMethods) unless Object.include?(AsyncMethods::InstanceMethods)
3
+ require File.expand_path(File.dirname(__FILE__) + '/method_tester')
4
+
5
+ context "AsyncMethods InstanceMethods" do
6
+
7
+ let(:object) { AsyncMethods::Tester.new }
8
+
9
+ specify "should inject async method handling" do
10
+ proxy = object.async_test("arg")
11
+ proxy.to_s.should == "ARG"
12
+ proxy.__proxy_loaded__.should == true
13
+ end
14
+
15
+ specify "should return a proxy object that has not been invoked yet" do
16
+ proxy = object.async_test("arg", 1)
17
+ proxy.__proxy_loaded__.should == false
18
+ end
19
+
20
+ specify "should be able to run a block asynchronously" do
21
+ proxy = asynchronous_block{object.test("arg", 0.1)}
22
+ proxy.__proxy_loaded__.should == false
23
+ proxy.to_s.should == "ARG"
24
+ proxy.__proxy_loaded__.should == true
25
+ end
26
+
27
+ end
28
+
29
+ context "AsyncMethods Proxy" do
30
+
31
+ let(:object) { AsyncMethods::Tester.new }
32
+
33
+ specify "should be able to wrap a method without waiting for it to finish" do
34
+ proxy = object.async_test("arg", 1)
35
+ object.test_called.should == 0
36
+ end
37
+
38
+ specify "should execute the wrapped method when it needs to" do
39
+ proxy = object.async_test("arg")
40
+ proxy.to_s
41
+ object.test_called.should == 1
42
+ end
43
+
44
+ specify "should only execute the wrapped method once" do
45
+ proxy = object.async_test("arg")
46
+ proxy.to_s
47
+ proxy.to_s
48
+ object.test_called.should == 1
49
+ end
50
+
51
+ specify "should allow nil as a valid proxied value" do
52
+ proxy = object.async_test(nil)
53
+ proxy.should_not
54
+ object.test_called.should == 1
55
+ end
56
+
57
+ specify "should allow blocks in the async method" do
58
+ n = 1
59
+ proxy = object.async_test("arg", 0.1) do
60
+ n = 2
61
+ end
62
+ n.should == 1
63
+ proxy.to_s
64
+ n.should == 2
65
+ end
66
+
67
+ specify "should be indistinguishable from the real object" do
68
+ proxy = object.async_test("arg")
69
+ proxy.class.should == String
70
+ proxy.kind_of?(String).should == true
71
+ end
72
+
73
+ specify "should proxy core methods on Object" do
74
+ proxy = "xxx".async_to_s
75
+ proxy.should == "xxx"
76
+ end
77
+
78
+ specify "should proxy missing methods" do
79
+ proxy = object.async_find_test
80
+ proxy.to_s.should == "FINDER"
81
+ end
82
+
83
+ specify "should allow blocks in the async missing methods" do
84
+ n = 1
85
+ proxy = object.async_find_test do
86
+ n = 2
87
+ end
88
+ n.should == 2
89
+ end
90
+
91
+ specify "should not interfere with the proxied object's method_missing" do
92
+ real = object.find_test
93
+ real.to_s.should == "FINDER"
94
+ end
95
+
96
+ specify "should not interfere with real methods that begin with async_" do
97
+ object.async_real_method_called.should == false
98
+ object.async_real_method
99
+ object.async_real_method_called.should == true
100
+ end
101
+
102
+ end
@@ -0,0 +1,32 @@
1
+ # This class is used for testing the async_methods plugin functionality.
2
+
3
+ class AsyncMethods::Tester
4
+
5
+ attr_reader :test_called, :async_real_method_called
6
+
7
+ def initialize
8
+ @test_called = 0
9
+ @async_real_method_called = false
10
+ end
11
+
12
+ def test (arg, delay = 0)
13
+ sleep(delay) if delay > 0
14
+ @test_called += 1
15
+ yield if block_given?
16
+ arg.upcase if arg
17
+ end
18
+
19
+ def async_real_method
20
+ @async_real_method_called = true
21
+ end
22
+
23
+ def method_missing (method, *args, &block)
24
+ if method.to_s[0, 5] == 'find_'
25
+ yield if block_given?
26
+ "FINDER"
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ end
@@ -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 ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async_methods
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Brian Durand
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-22 00:00:00 -05:00
19
+ default_executable:
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 asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes. This is intended to improve throughput on I/O bound processes like making several HTTP calls in row.
52
+ email: brian@embellishedvisions.com
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files:
58
+ - README.rdoc
59
+ files:
60
+ - .gitignore
61
+ - MIT-LICENSE
62
+ - README.rdoc
63
+ - Rakefile
64
+ - VERSION
65
+ - async_methods.gemspec
66
+ - lib/async_methods.rb
67
+ - lib/async_methods/async_methods.rb
68
+ - spec/async_method_spec.rb
69
+ - spec/method_tester.rb
70
+ - spec/spec_helper.rb
71
+ has_rdoc: true
72
+ homepage: http://github.com/bdurand/acts_as_revisionable
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --charset=UTF-8
78
+ - --main
79
+ - README.rdoc
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project:
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Gem that adds asynchronous method calls for all methods on every object to aid in throughput on I/O bound processes.
107
+ test_files:
108
+ - spec/async_method_spec.rb
109
+ - spec/method_tester.rb
110
+ - spec/spec_helper.rb