group_delegator 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +120 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/examples/compare_to_map.rb +50 -0
- data/examples/diff_w_map.rb +21 -0
- data/examples/find_troublemakers.rb +12 -0
- data/examples/remote_component_update_sim.rb +47 -0
- data/examples/search_examples_with_benchmarks.rb +40 -0
- data/lib/group_delegator.rb +23 -0
- data/lib/group_delegator/group_delegator_instances.rb +32 -0
- data/lib/group_delegator/group_delegator_klasses.rb +62 -0
- data/lib/group_delegator/source_group.rb +161 -0
- data/lib/group_delegator/source_helper.rb +17 -0
- data/spec/group_delegator_instances_spec.rb +150 -0
- data/spec/group_delegator_klasses_spec.rb +270 -0
- data/spec/group_delegator_spec.rb +12 -0
- data/spec/source_group_spec.rb +259 -0
- data/spec/spec_helper.rb +12 -0
- metadata +152 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.3.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 David Martin
|
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,120 @@
|
|
1
|
+
= group_delegator
|
2
|
+
|
3
|
+
GroupDelegator provides a way to wrap a collection of objects and send method calls to the entire collection.
|
4
|
+
* Simple case: Bind a collection of existing objects to a common wrapper that will proxy the method calls to each object
|
5
|
+
Example:
|
6
|
+
require 'group_delegator'
|
7
|
+
|
8
|
+
proxy_numbers = ["1", "2", "3"]
|
9
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
10
|
+
proxy_data = proxy_all.to_i
|
11
|
+
#=> {"1"=>1, "2"=>2, "3"=>3}
|
12
|
+
|
13
|
+
#or if you just wanted the integers
|
14
|
+
proxy_integers = proxy_all.to_i
|
15
|
+
#=> [1, 2, 3]
|
16
|
+
puts "proxied to_i: #{proxy_integers.values.inspect}"
|
17
|
+
|
18
|
+
* Why not just use Array#map to do the same transformation? i.e.:
|
19
|
+
map_numbers =["1", "2", "3"]
|
20
|
+
mapped_integers = map_numbers.map{|t| t.to_i}
|
21
|
+
puts "mapped to_i: #{mapped_integers.inspect}"
|
22
|
+
|
23
|
+
* Let's compare the two approaches in a bit of detail
|
24
|
+
#proxy by group_delegator
|
25
|
+
proxy_numbers = ["1", "2", "3"]
|
26
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
27
|
+
proxy_integers = proxy_all.to_i
|
28
|
+
#lets add the string "times" to each number
|
29
|
+
proxy_string = proxy_all<< " times"
|
30
|
+
#then make it uppercase
|
31
|
+
proxy_upcase = proxy_all.upcase
|
32
|
+
|
33
|
+
#tranform collection by Array#map
|
34
|
+
map_numbers =["1", "2", "3"]
|
35
|
+
map_integers = map_numbers.map{|t| t.to_i}
|
36
|
+
#lets add the string "times" to each number
|
37
|
+
map_string = map_numbers.map{|t| t << " times"}
|
38
|
+
#then make it uppercase
|
39
|
+
map_upcase = map_string.map{|t| t.upcase}
|
40
|
+
|
41
|
+
#proxy output
|
42
|
+
puts "Proxy: #{proxy_integers.values.inspect}"
|
43
|
+
puts "Proxy: #{proxy_string.values.inspect}"
|
44
|
+
puts "Proxy: #{proxy_upcase.values.inspect}"
|
45
|
+
#=> Proxy: [1, 2, 3]
|
46
|
+
#=> Proxy: ["1 times", "2 times", "3 times"]
|
47
|
+
#=> Proxy: ["1 TIMES", "2 TIMES", "3 TIMES"]
|
48
|
+
|
49
|
+
#map output
|
50
|
+
puts "Map: #{map_integers.inspect}"
|
51
|
+
puts "Map: #{map_string.inspect}"
|
52
|
+
puts "Map: #{map_upcase.inspect}"
|
53
|
+
#=> Map: [1, 2, 3]
|
54
|
+
#=> Map: ["1 times", "2 times", "3 times"]
|
55
|
+
#=> Map: ["1 TIMES", "2 TIMES", "3 TIMES"]
|
56
|
+
|
57
|
+
* Although we get to the same point at the end, the route getting there is quite a bit different between the two approaches. Also, I use this example, not because it illustrates how GroupDelegator is useful, but to relate its behavior to something relatively common.
|
58
|
+
|
59
|
+
Things to notice:
|
60
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
61
|
+
This will have all methods called on proxy_all, to be forward to each object in the proxy_numbers array. Any method on <i>proxy_all</i> is applied to each object in <i>proxy_numbers</i>, however, each object does not have to respond to the method (as long as at least one does).
|
62
|
+
|
63
|
+
proxy_all.to_i
|
64
|
+
Apply the method #to_i to all members of proxy_numbers. Which brings us to a feature mentioned above. Say our original array was:
|
65
|
+
["1", "2", "3", :a, "b", "cat"]
|
66
|
+
With SimpleDelegator, calling #to_i results in a hash, and the values of that hash would be:
|
67
|
+
[1, 2, 3, 0, 0]
|
68
|
+
On the other hand, with Array#map, the result is a NoMethodError.
|
69
|
+
|
70
|
+
What is the structure of the GroupDelegator hash then? It's the source object as the key, with the method result as the value, so #to_i on a SimpleDelegator object returns
|
71
|
+
{"1" => 1, "2" => 2, "3" => 3, "b" => 0, "cat" => 0}
|
72
|
+
Notice :a is not in the hash. That's because it errored out. Errors are ignored by GroupDelegator (SimpleDelegator is a type of GroupDelegator).
|
73
|
+
|
74
|
+
|
75
|
+
* If we are able to work around troublesome objects, it might be useful to identify them
|
76
|
+
|
77
|
+
#Group Delegator proxying
|
78
|
+
proxy_numbers = ["1", "2", "3", :a, "b", "cat"]
|
79
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
80
|
+
proxy_integers = proxy_all.to_i
|
81
|
+
|
82
|
+
#find trouble makers
|
83
|
+
trouble_makers = proxy_numbers - proxy_integers.keys
|
84
|
+
#=> [:a]
|
85
|
+
|
86
|
+
== Beyond the basics
|
87
|
+
So up to now I've made it look like GroupDelegator is just a slightly better alternative to Array#map. But there's some more advanced things that GroupDelegator can do. For example
|
88
|
+
* Multiple Concurrenty models. The default model is to iterate over object in the collection that's being proxied, but also supported is to spawn a thread for each object, resulting in each method being called to each object concurrently. Especially useful for method calls that might take some time (like http requests). Additionally, it may be useful in certain cases that all is needed is a single response from any object. That too is supported. Finally, it's possible to pass your own block into a GroupDelegator if some other concurrency model is required.
|
89
|
+
* Class level delegation. The SimpleGroupDelegator class works on objects, but if you need to proxy an entire class, you can use the GroupDelegator class. Calling #new on the proxy class instantiates an object from each of the proxied classes, and returns a proxy_object. That proxy object then works similarly to a SimpleGroupDelegator object.
|
90
|
+
|
91
|
+
== Future Plans
|
92
|
+
* add new Delegator -> GroupMetaDelegator that passes *all* methods through to sources (including methods from Object and Module). This requires remapping existing default methods to an alias (probably # __gd__method_name)
|
93
|
+
* add a method that returns the underlying objects.
|
94
|
+
proxy_all.tap {|s| s}
|
95
|
+
#=> {obj1 => obj1, obj2 => obj2, etc}
|
96
|
+
As shown above Object#tap can do this, but perhaps a better solution would be:
|
97
|
+
proxy_all.self
|
98
|
+
#=> [obj1, obj2, etc]
|
99
|
+
* Consider adding a helper method for finding objects that didn't respond to a method. I'm not quite sure how to do this though (or even if its possible). Note, I don't want a #respond_to? clone, and re-doing the method isn't a good solution since the ojbect state may have changed (affecting its ability to respond to methods)
|
100
|
+
|
101
|
+
== Examples on Use (until I can get a full fledged tutorial written up)
|
102
|
+
Several examples (included those referenced here) are in the examples directory.
|
103
|
+
There's also the specs. Some of the examples and specs have benchmarks showing the differences between the various concurrency models.
|
104
|
+
|
105
|
+
|
106
|
+
== Contributing to group_delegator
|
107
|
+
|
108
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
109
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
110
|
+
* Fork the project
|
111
|
+
* Start a feature/bugfix branch
|
112
|
+
* Commit and push until you are happy with your contribution
|
113
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
114
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
115
|
+
|
116
|
+
== Copyright
|
117
|
+
|
118
|
+
Copyright (c) 2011 David Martin See LICENSE.txt for
|
119
|
+
further details.
|
120
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "group_delegator"
|
16
|
+
gem.homepage = "http://github.com/forforf/group_delegator"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Delegate to multiple objects concurrently}
|
19
|
+
gem.description = %Q{A wrapper that allows method calls to multiple objects with various concurrency models}
|
20
|
+
gem.email = "dmarti21@gmail.com"
|
21
|
+
gem.authors = ["Dave M"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "group_delegator #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'group_delegator'
|
2
|
+
proxy_numbers = ["1", "2", "3"]
|
3
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
4
|
+
proxy_data = proxy_all.to_i
|
5
|
+
#=> {"1"=>1, "2"=>2, "3"=>3}
|
6
|
+
#or if you just wanted the integers
|
7
|
+
proxy_integers = proxy_all.to_i
|
8
|
+
#=> [1, 2, 3]
|
9
|
+
puts "proxied to_i: #{proxy_integers.values.inspect}"
|
10
|
+
|
11
|
+
#Why not just use map? i.e.:
|
12
|
+
map_numbers =["1", "2", "3"]
|
13
|
+
mapped_integers = map_numbers.map{|t| t.to_i}
|
14
|
+
puts "mapped to_i: #{mapped_integers.inspect}"
|
15
|
+
|
16
|
+
#Let's compare
|
17
|
+
#Group Delegator proxying
|
18
|
+
proxy_numbers = ["1", "2", "3"]
|
19
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
20
|
+
proxy_integers = proxy_all.to_i
|
21
|
+
#lets add the string "times" to each number
|
22
|
+
proxy_string = proxy_all<< " times"
|
23
|
+
#then make it uppercase
|
24
|
+
proxy_upcase = proxy_all.upcase
|
25
|
+
|
26
|
+
#map
|
27
|
+
map_numbers =["1", "2", "3"]
|
28
|
+
map_integers = map_numbers.map{|t| t.to_i}
|
29
|
+
#lets add the string "times" to each number
|
30
|
+
map_string = map_numbers.map{|t| t << " times"}
|
31
|
+
#then make it uppercase
|
32
|
+
map_upcase = map_string.map{|t| t.upcase}
|
33
|
+
|
34
|
+
|
35
|
+
#proxy output
|
36
|
+
puts "Proxy: #{proxy_integers.values.inspect}"
|
37
|
+
puts "Proxy: #{proxy_string.values.inspect}"
|
38
|
+
puts "Proxy: #{proxy_upcase.values.inspect}"
|
39
|
+
#=> Proxy: [1, 2, 3]
|
40
|
+
#=> Proxy: ["1 times", "2 times", "3 times"]
|
41
|
+
#=> Proxy: ["1 TIMES", "2 TIMES", "3 TIMES"]
|
42
|
+
|
43
|
+
#map output
|
44
|
+
puts "Map: #{map_integers.inspect}"
|
45
|
+
puts "Map: #{map_string.inspect}"
|
46
|
+
puts "Map: #{map_upcase.inspect}"
|
47
|
+
#=> Map: [1, 2, 3]
|
48
|
+
#=> Map: ["1 times", "2 times", "3 times"]
|
49
|
+
#=> Map: ["1 TIMES", "2 TIMES", "3 TIMES"]
|
50
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'group_delegator'
|
2
|
+
#Note the last elements of the array
|
3
|
+
proxy_numbers = ["1", "2", "3", :a, "b", "cat"]
|
4
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
5
|
+
proxy_data = proxy_all.to_i
|
6
|
+
#=> {"1"=>1, "2"=>2, "3"=>3}
|
7
|
+
#or if you just wanted the integers
|
8
|
+
proxy_integers = proxy_all.to_i
|
9
|
+
#=> [1, 2, 3]
|
10
|
+
puts "proxied to_i: #{proxy_integers.values.inspect}"
|
11
|
+
|
12
|
+
#Why not just use map? i.e.:
|
13
|
+
map_numbers =["1", "2", "3", :a, "b", "cat"]
|
14
|
+
begin
|
15
|
+
mapped_integers = map_numbers.map{|t| t.to_i}
|
16
|
+
rescue NoMethodError
|
17
|
+
puts "We just rescued a NoMehodError in a #map method call"
|
18
|
+
#=> NoMethod Error
|
19
|
+
end
|
20
|
+
puts "mapped to_i: #{mapped_integers.inspect}"
|
21
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'group_delegator'
|
2
|
+
|
3
|
+
#Group Delegator proxying
|
4
|
+
proxy_numbers = ["1", "2", "3", :a, "b", "cat"]
|
5
|
+
proxy_all = SimpleGroupDelegator.new(proxy_numbers)
|
6
|
+
proxy_integers = proxy_all.to_i
|
7
|
+
|
8
|
+
#find trouble makers
|
9
|
+
trouble_makers = proxy_numbers - proxy_integers.keys
|
10
|
+
#=> [:a]
|
11
|
+
|
12
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'group_delegator'
|
2
|
+
|
3
|
+
|
4
|
+
#Imagine we have many objects
|
5
|
+
class MyRemoteComponent
|
6
|
+
attr_accessor :status, :version
|
7
|
+
|
8
|
+
def remote_update(version)
|
9
|
+
sleep 0.1 #they're slow to update
|
10
|
+
@version = version
|
11
|
+
end
|
12
|
+
|
13
|
+
def
|
14
|
+
end
|
15
|
+
|
16
|
+
components = []
|
17
|
+
100.times do
|
18
|
+
stat = [:ok, :minor, :major, :fail][rand(4)]
|
19
|
+
mycomp = MyRemoteComponent.new
|
20
|
+
mycomp.status = [:ok, :minor, :major, :fail][rand(4)]
|
21
|
+
components << mycomp
|
22
|
+
end
|
23
|
+
|
24
|
+
#let's get the status from each component
|
25
|
+
|
26
|
+
#this proxyies all components' methods into a single wrapper
|
27
|
+
all_as_one = SimpleGroupDelegator.new(components)
|
28
|
+
|
29
|
+
#so we can check the status of all of them in a single line
|
30
|
+
all_status = all_as_one.status
|
31
|
+
|
32
|
+
#the return result is a hash of the {obj1 => result1, obj2 => result2, etc}
|
33
|
+
#if we don't care about the obj we can just grab the values
|
34
|
+
p all_status.values
|
35
|
+
|
36
|
+
all_as_one.remote_update("v1.0.0")
|
37
|
+
|
38
|
+
p all_as_one.version.values.uniq
|
39
|
+
#=> ["v1.0.0"] but that was kinda slow
|
40
|
+
|
41
|
+
#lets do them all in parallel then
|
42
|
+
all_as_one_v2 = SimpleGroupDelegator.new(components, :threaded)
|
43
|
+
|
44
|
+
all_as_one_v2.remote_update("v2.0.0")
|
45
|
+
|
46
|
+
p all_as_one_v2.version.values.uniq
|
47
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'group_delegator'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'benchmark'
|
4
|
+
class Bing
|
5
|
+
QueryString = "http://www.bing.com/search?q="
|
6
|
+
def search(search_string)
|
7
|
+
url = QueryString + URI.escape(search_string)
|
8
|
+
open(url){|f| f.meta}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Google
|
13
|
+
QueryString = "http://www.google.com/search?q="
|
14
|
+
def search(search_string)
|
15
|
+
url = QueryString + URI.escape(search_string)
|
16
|
+
open(url){|f| f.meta}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Yahoo
|
21
|
+
QueryString = "http://search.yahoo.com/search?p="
|
22
|
+
def search(search_string)
|
23
|
+
url = QueryString + URI.escape(search_string)
|
24
|
+
open(url){|f| f.meta}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#default is iterative searchclass IterativeSearch < SimpleDel
|
29
|
+
searchers = [Bing.new, Google.new, Yahoo.new]
|
30
|
+
search_iteratively = SimpleGroupDelegator.new(searchers)
|
31
|
+
search_threaded = SimpleGroupDelegator.new(searchers, :threaded)
|
32
|
+
search_first_response = SimpleGroupDelegator.new(searchers, :first_response)
|
33
|
+
|
34
|
+
iterative = Benchmark.realtime { search_iteratively.search('stuff') }
|
35
|
+
threaded = Benchmark.realtime { search_threaded.search('stuff') }
|
36
|
+
first_resp = Benchmark.realtime { search_first_response.search('stuff') }
|
37
|
+
|
38
|
+
puts "Iterative Search Time: #{iterative}"
|
39
|
+
puts "Threaded Search Time : #{threaded}"
|
40
|
+
puts "First Response Time : #{first_resp}"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#container for the proxied objects
|
2
|
+
require 'group_delegator/source_group'
|
3
|
+
|
4
|
+
#retrieves the method data from the source group
|
5
|
+
require 'group_delegator/source_helper'
|
6
|
+
|
7
|
+
#class to use if one is only interested in instance methods
|
8
|
+
require 'group_delegator/group_delegator_instances'
|
9
|
+
|
10
|
+
#class to use if one is interested in proxy a complete class
|
11
|
+
#- including class methods and instantiation
|
12
|
+
require 'group_delegator/group_delegator_klasses'
|
13
|
+
|
14
|
+
#for the lazy (like me) set a group delegator class to a default
|
15
|
+
#so people don't have to read docs
|
16
|
+
class GroupDelegator < GroupDelegatorKlasses
|
17
|
+
end
|
18
|
+
|
19
|
+
#Also, here's the basic version with a simple name
|
20
|
+
#This one only deals with instantiated objects (so
|
21
|
+
#no class variables)
|
22
|
+
class SimpleGroupDelegator < GroupDelegatorInstances
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#Takes a set of instantiated objects as arguments and will concurently delegate
|
2
|
+
#method calls to each instance in the set. Built in concurrency models include:
|
3
|
+
# - iterative (iterates the method calls on each object in the set)
|
4
|
+
# - threaded (all method calls are done in parallel until all conclude
|
5
|
+
# - first response (continues once any in the set complete the method)
|
6
|
+
class GroupDelegatorInstances
|
7
|
+
include SourceHelper
|
8
|
+
#unload these methods so the proxy object will handle them
|
9
|
+
[:to_s,:inspect,:=~,:!~,:===].each do |m|
|
10
|
+
undef_method m
|
11
|
+
end
|
12
|
+
|
13
|
+
#object methods, only
|
14
|
+
def initialize(proxied_objs, concurrency_model = :iterative)
|
15
|
+
@source_objects = [] #contains the delegated objects
|
16
|
+
@source_obj_methods = {} #map of all methods to the objects that use them
|
17
|
+
raise "No source instances set" unless proxied_objs.size > 0
|
18
|
+
sources_data = __set_sources_data(proxied_objs)
|
19
|
+
@source_obj_methods = sources_data[:source_methods]
|
20
|
+
@source_objects = sources_data[:source_objs]
|
21
|
+
@instance_source_group = SourceGroup.new(@source_objects, concurrency_model)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(m, *args, &block)
|
26
|
+
if @source_obj_methods.include? m
|
27
|
+
resp = @instance_source_group.forward(m, *args, &block)
|
28
|
+
else
|
29
|
+
raise NoMethodError, "GroupDelegatorKlasses object can't find the method #{m} in any of its sources"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|