classroom 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +15 -0
- data/classroom.gemspec +16 -0
- data/examples/demo_client.rb +35 -0
- data/examples/demo_client2.rb +20 -0
- data/examples/demo_client3.rb +32 -0
- data/examples/demo_client4.rb +49 -0
- data/examples/demo_client5.rb +51 -0
- data/examples/server.rb +9 -0
- data/examples/test_modules.rb +48 -0
- data/examples/test_modules2.rb +32 -0
- data/examples/test_modules3.rb +35 -0
- data/lib/classroom.rb +241 -0
- data/test/classroom_test.rb +46 -0
- metadata +60 -0
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => [:test_units]
|
5
|
+
|
6
|
+
desc "Run basic tests"
|
7
|
+
Rake::TestTask.new("test_units") do |t|
|
8
|
+
t.pattern = 'test/*_test.rb'
|
9
|
+
t.verbose = true
|
10
|
+
t.warning = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :run_server do
|
14
|
+
system 'ruby -Ilib examples/server.rb'
|
15
|
+
end
|
data/classroom.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'classroom'
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.summary = "ClassRoom is a 'class server' based on DRb"
|
8
|
+
s.files = Dir.glob("**/**/**").delete_if {|item| item.include?(".svn")}
|
9
|
+
s.test_files = Dir.glob("test/*_test.rb")
|
10
|
+
s.require_path = 'lib'
|
11
|
+
s.autorequire = 'classroom'
|
12
|
+
s.author = "Peter Cooper"
|
13
|
+
s.email = "coops@petercooper.co.uk"
|
14
|
+
s.rubyforge_project = "classroom"
|
15
|
+
s.homepage = "http://classroom.rubyforge.org"
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Add a basic class to the ClassRoom server and use it
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'classroom'
|
5
|
+
|
6
|
+
# A basic 'queue' class with a fixed length queue and
|
7
|
+
# push and pull methods to use the queue.
|
8
|
+
klass = %q{
|
9
|
+
class BasicQueue
|
10
|
+
def initialize(size = 10); @size = size; @data = Array.new; end
|
11
|
+
def push(item); @data.length < @size ? (@data << item) && true : nil; end
|
12
|
+
def pull; @data.shift; end
|
13
|
+
def self.dummy_class_method; (1..10).to_a; end
|
14
|
+
end
|
15
|
+
}
|
16
|
+
|
17
|
+
# Here's the magic..
|
18
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
|
19
|
+
class_server.add_class(klass)
|
20
|
+
class_server.load_class(binding, :all)
|
21
|
+
|
22
|
+
# Create and use objects as you would normally, but remember
|
23
|
+
# 'BasicQueue' actually exists on the ClassRoom server and,
|
24
|
+
# if you wished, could be changed mid-execution by another
|
25
|
+
# client!
|
26
|
+
q = BasicQueue.new(5)
|
27
|
+
10.times { |i| q.push("test #{i}") }
|
28
|
+
10.times { puts q.pull }
|
29
|
+
|
30
|
+
r = BasicQueue.new(10)
|
31
|
+
10.times { |i| r.push("test #{i}") }
|
32
|
+
10.times { puts r.pull }
|
33
|
+
|
34
|
+
# Yes, class methods work out of the box too!
|
35
|
+
puts BasicQueue.dummy_class_method.inspect
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This client will run ONLY IF the BasicQueue class exists on the
|
2
|
+
# ClassRoom server. If you ran demo_client.rb first, this should be
|
3
|
+
# fine.
|
4
|
+
|
5
|
+
# This client should return exactly the same as demo_client.rb,
|
6
|
+
# proving the class is running entirely off of the ClassRoom server.
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'classroom'
|
10
|
+
|
11
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
|
12
|
+
class_server.load_class(binding, :BasicQueue)
|
13
|
+
|
14
|
+
q = BasicQueue.new(5)
|
15
|
+
10.times { |i| q.push("test #{i}") }
|
16
|
+
10.times { puts q.pull }
|
17
|
+
|
18
|
+
r = BasicQueue.new(10)
|
19
|
+
10.times { |i| r.push("test #{i}") }
|
20
|
+
10.times { puts r.pull }
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# A basic demo with a basic class
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'classroom'
|
5
|
+
|
6
|
+
# A basic class whose function you can mostly ignore
|
7
|
+
klass = %q{
|
8
|
+
class BasicDataClass
|
9
|
+
def initialize(params); @data = params; end
|
10
|
+
def method_missing(method, *args); @data[method]; end
|
11
|
+
def size; @data.size; end
|
12
|
+
def self.dummy_class_method; (1..10).to_a; end
|
13
|
+
end
|
14
|
+
}
|
15
|
+
|
16
|
+
# Connect to the ClassRoom server and upload the class
|
17
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
|
18
|
+
class_server.add_class(klass)
|
19
|
+
|
20
|
+
# Load local references to all the classes on the ClassRoom server,
|
21
|
+
# including BasicDataClass
|
22
|
+
class_server.load_class(binding, :all)
|
23
|
+
|
24
|
+
# Create and use objects as you would normally, but remember
|
25
|
+
# 'BasicDataClass' actually exists on the ClassRoom server
|
26
|
+
c = BasicDataClass.new(:chunky => "Bacon", :roger => "Rabbit")
|
27
|
+
puts c.class # => BasicDataClass
|
28
|
+
puts c.chunky # => Bacon
|
29
|
+
puts c.size # => 2
|
30
|
+
|
31
|
+
# Yes, class methods work out of the box too!
|
32
|
+
puts BasicDataClass.dummy_class_method
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# A demonstration of nested classes all working perfectly..
|
2
|
+
# (we hope!)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'classroom'
|
6
|
+
|
7
|
+
# A basic 'queue' class with a fixed length queue and
|
8
|
+
# push and pull methods to use the queue.
|
9
|
+
klass = %q{
|
10
|
+
class BasicParent
|
11
|
+
def self.give_child
|
12
|
+
BasicParent::BasicChild.new
|
13
|
+
end
|
14
|
+
def self.x
|
15
|
+
"PARENT CLASS METHOD X"
|
16
|
+
end
|
17
|
+
def x
|
18
|
+
"PARENT CHILD METHOD X"
|
19
|
+
end
|
20
|
+
attr_accessor :blah
|
21
|
+
class BasicChild
|
22
|
+
def self.x
|
23
|
+
"CHILD CLASS METHOD X"
|
24
|
+
end
|
25
|
+
def x
|
26
|
+
"CHILD OBJECT METHOD X"
|
27
|
+
end
|
28
|
+
class BasicSubChild
|
29
|
+
def self.x
|
30
|
+
"GRANDCHILD CLASS METHOD X"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
# Here's the magic..
|
38
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
|
39
|
+
class_server.add_class(klass)
|
40
|
+
class_server.load_class(binding, :all)
|
41
|
+
#class_server.load_non_present_classes(binding)
|
42
|
+
|
43
|
+
puts BasicParent.give_child.x
|
44
|
+
puts BasicParent.x
|
45
|
+
puts BasicParent::BasicChild::BasicSubChild.x
|
46
|
+
|
47
|
+
x = BasicParent.new
|
48
|
+
x.blah = 50
|
49
|
+
puts x.blah
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Attempt to do nasty / insecure operations!
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'classroom'
|
5
|
+
|
6
|
+
# A basic 'queue' class with a fixed length queue and
|
7
|
+
# push and pull methods to use the queue.
|
8
|
+
klass = %q{
|
9
|
+
class BasicParent
|
10
|
+
def do_nasty
|
11
|
+
`ls -l`
|
12
|
+
end
|
13
|
+
def do_nasty2
|
14
|
+
x = 'ls'
|
15
|
+
y = '-l'
|
16
|
+
z = 'system'
|
17
|
+
eval z + '"' + x + ' ' + y + '"'
|
18
|
+
end
|
19
|
+
def do_nasty3
|
20
|
+
ClassRoom::ClassServerContainer.attr_writer(:xyzzy)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
# Here's the magic..
|
26
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
|
27
|
+
class_server.add_class(klass)
|
28
|
+
class_server.load_class(binding, :all)
|
29
|
+
|
30
|
+
a = BasicParent.new
|
31
|
+
|
32
|
+
puts "Nasty 1"
|
33
|
+
begin
|
34
|
+
a.do_nasty
|
35
|
+
rescue
|
36
|
+
puts "FAILED WITH #{$!}}!"
|
37
|
+
end
|
38
|
+
|
39
|
+
puts "Nasty 2"
|
40
|
+
begin
|
41
|
+
a.do_nasty2
|
42
|
+
rescue
|
43
|
+
puts "FAILED WITH #{$!}}!"
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "Nasty 3"
|
47
|
+
begin
|
48
|
+
a.do_nasty3
|
49
|
+
rescue
|
50
|
+
puts "FAILED WITH #{$!}}!"
|
51
|
+
end
|
data/examples/server.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Tests that modules work in one sense
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'classroom'
|
5
|
+
|
6
|
+
klass = %q{
|
7
|
+
module TestModule
|
8
|
+
class BasicParent
|
9
|
+
def self.give_child
|
10
|
+
BasicParent::BasicChild.new
|
11
|
+
end
|
12
|
+
def self.x
|
13
|
+
"PARENT CLASS METHOD X"
|
14
|
+
end
|
15
|
+
def x
|
16
|
+
"PARENT CHILD METHOD X"
|
17
|
+
end
|
18
|
+
attr_accessor :blah
|
19
|
+
class BasicChild
|
20
|
+
def self.x
|
21
|
+
"CHILD CLASS METHOD X"
|
22
|
+
end
|
23
|
+
def x
|
24
|
+
"CHILD OBJECT METHOD X"
|
25
|
+
end
|
26
|
+
class BasicSubChild
|
27
|
+
def self.x
|
28
|
+
"GRANDCHILD CLASS METHOD X"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
# Here's the magic..
|
37
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
|
38
|
+
class_server.add_class(klass)
|
39
|
+
class_server.load_non_present_classes(binding)
|
40
|
+
|
41
|
+
puts class_server.classes.inspect
|
42
|
+
puts TestModule::BasicParent.give_child.x
|
43
|
+
puts TestModule::BasicParent.x
|
44
|
+
puts TestModule::BasicParent::BasicChild::BasicSubChild.x
|
45
|
+
|
46
|
+
x = TestModule::BasicParent.new
|
47
|
+
x.blah = 50
|
48
|
+
puts x.blah
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Tests that module mixins work entirely on the server end
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'classroom'
|
5
|
+
|
6
|
+
# A basic 'queue' class with a fixed length queue and
|
7
|
+
# push and pull methods to use the queue.
|
8
|
+
klass = %q{
|
9
|
+
module TestModule
|
10
|
+
def blah
|
11
|
+
"blah #{@s}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
}
|
15
|
+
|
16
|
+
klass2 = %q{
|
17
|
+
class Something
|
18
|
+
include TestModule
|
19
|
+
def initialize(s)
|
20
|
+
@s = s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
# Here's the magic..
|
26
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
|
27
|
+
class_server.add_class(klass)
|
28
|
+
class_server.add_class(klass2)
|
29
|
+
class_server.load_class(binding, :all)
|
30
|
+
|
31
|
+
x = Something.new(10)
|
32
|
+
puts x.blah
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# THIS TEST WILL NOT WORK!! UNIMPLEMENTED FEATURE!!
|
2
|
+
|
3
|
+
# Use a remote module from the ClassRoom server as a mixin
|
4
|
+
# on a local class.. (currently not possible)
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'classroom'
|
8
|
+
|
9
|
+
# A basic 'queue' class with a fixed length queue and
|
10
|
+
# push and pull methods to use the queue.
|
11
|
+
klass = %q{
|
12
|
+
module TestModule
|
13
|
+
def blah
|
14
|
+
"blah #{@s}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
# Here's the magic..
|
21
|
+
class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
|
22
|
+
class_server.add_class(klass)
|
23
|
+
class_server.load_class(binding, :all)
|
24
|
+
|
25
|
+
|
26
|
+
class Something
|
27
|
+
include TestModule
|
28
|
+
def initialize(s)
|
29
|
+
@s = s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
x = Something.new(10)
|
35
|
+
puts x.blah
|
data/lib/classroom.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module ClassRoom
|
5
|
+
|
6
|
+
# Create a 'blank slate' class template so we don't have any extraneous
|
7
|
+
# methods lying around to be (potentially) abused.
|
8
|
+
# Technique by http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
9
|
+
class BlankSlate
|
10
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Provide a separate module as the container for our user classes
|
14
|
+
module ClassContainer
|
15
|
+
end
|
16
|
+
|
17
|
+
# The class that proxies between user classes on the ClassRoom server and
|
18
|
+
# the requests made by client apps. It stores references to the user classes,
|
19
|
+
# evals the code, and allows instances to be created of the user classes.
|
20
|
+
class ClassServerContainer < BlankSlate
|
21
|
+
@@classes = []
|
22
|
+
|
23
|
+
# Add class(es) to the ClassRoom server
|
24
|
+
def self.add_class(code)
|
25
|
+
base_class = code.scan(/(class|module)\s+([\w\:]+)/).collect{|c| c[1]}.flatten.first
|
26
|
+
base_class.untaint
|
27
|
+
@@classes << base_class
|
28
|
+
|
29
|
+
# Actually add the class to the ClassRoom server
|
30
|
+
begin
|
31
|
+
# Force the code to run by untainting it
|
32
|
+
code.untaint
|
33
|
+
# Force the code into the ClassContainer module and force a
|
34
|
+
# SAFE mode where all data is considered tainted. This stops
|
35
|
+
# users running system commands, etc.
|
36
|
+
eval("module ClassRoom; module ClassContainer\n$SAFE = 3\n" + code + "\nend; end")
|
37
|
+
rescue
|
38
|
+
# If the user's code was bad, duck out.
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Now the code is loaded.. loop through all of the classes and add their
|
43
|
+
# full (nested) names to the class list so automatic loading
|
44
|
+
# can work for clients
|
45
|
+
classes_to_check = [base_class]
|
46
|
+
classes_to_check.each do |class_name|
|
47
|
+
class_internal = eval("ClassRoom::ClassContainer::#{class_name}")
|
48
|
+
class_internal.constants.each do |subclass_name|
|
49
|
+
if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
|
50
|
+
sub_class = class_name + "::" + subclass_name
|
51
|
+
@@classes << sub_class
|
52
|
+
classes_to_check << sub_class
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Hack to make sure the classes array is clean
|
58
|
+
@@classes.collect! { |c| c.to_sym }
|
59
|
+
@@classes.uniq!
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# THESE ARE NOT WORKING
|
64
|
+
#def self.remove_class(class_name)
|
65
|
+
# class_internal = eval("ClassRoom::ClassContainer")
|
66
|
+
# class_internal.constants.each do |subclass_name|
|
67
|
+
# if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
|
68
|
+
# class_internal.remove_const(subclass_name) if subclass_name.to_s == class_name.to_s
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#end
|
72
|
+
#
|
73
|
+
#def self.remove_all
|
74
|
+
# class_internal = eval("ClassRoom::ClassContainer")
|
75
|
+
# class_internal.constants.each do |subclass_name|
|
76
|
+
# if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
|
77
|
+
# class_internal.remove_const(subclass_name)
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#end
|
81
|
+
|
82
|
+
# Return a list of all classes in the ClassContainer
|
83
|
+
# (must be a nicer way to do this without logging it ourselves..?)
|
84
|
+
def self.classes
|
85
|
+
return @@classes
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return a new instance of a class in the ClassContainer
|
89
|
+
def self.new_instance_of(class_name, *args)
|
90
|
+
begin
|
91
|
+
obj = eval('ClassRoom::ClassContainer::' + class_name.to_s).new(*args)
|
92
|
+
obj.extend(DRbUndumped)
|
93
|
+
obj
|
94
|
+
rescue
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Call a class method on a class in the ClassContainer
|
100
|
+
# (this might need work to check what comes back and whether to
|
101
|
+
# mix in DRbUndumped if it's an object)
|
102
|
+
def self.class_method(class_name, method_name, *args)
|
103
|
+
begin
|
104
|
+
obj = eval('ClassRoom::ClassContainer::' + class_name.to_s).send(method_name.to_s, *args)
|
105
|
+
obj.extend(DRbUndumped) if self.classes.detect { |c| obj.class.to_s.index(c.to_s) }
|
106
|
+
obj
|
107
|
+
rescue
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# Provides methods to get the server up and running easily
|
115
|
+
class ClassServer < BlankSlate
|
116
|
+
# Prepare DRb to act as a server using the correct URL
|
117
|
+
# Do some messy hacks to get our own URL scheme
|
118
|
+
def self.prepare(drb_url = 'classroom://:2001')
|
119
|
+
$SAFE = 1
|
120
|
+
drb_url.gsub!('classroom:', 'druby:')
|
121
|
+
DRb.start_service(drb_url, ClassServerContainer)
|
122
|
+
return DRb.uri.gsub('druby:', 'classroom:')
|
123
|
+
end
|
124
|
+
|
125
|
+
# Do the thread.join with DRb to actually get the server processing requests
|
126
|
+
# TODO: Get this daemonizing!
|
127
|
+
def self.start
|
128
|
+
#fork do
|
129
|
+
# Process.setsid
|
130
|
+
# exit if fork
|
131
|
+
# trap("TERM") {daemon.stop; exit}
|
132
|
+
Kernel.send :remove_method, :system
|
133
|
+
class << Kernel; self; end.send :remove_method, :system
|
134
|
+
$SAFE = 3
|
135
|
+
DRb.thread.join
|
136
|
+
#end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Provides methods for the client side
|
141
|
+
class Client < BlankSlate
|
142
|
+
|
143
|
+
def self.get_drb(drb_url)
|
144
|
+
@@drb[drb_url]
|
145
|
+
end
|
146
|
+
|
147
|
+
# Set up the DRbObject referring to the ClassRoom server
|
148
|
+
def initialize(drb_url)
|
149
|
+
@drb_url = drb_url.gsub('classroom:', 'druby:')
|
150
|
+
(@@drb ||= {})[@drb_url] = DRbObject.new(nil, @drb_url)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Passes through calls for non-local methods to the DRb server
|
154
|
+
def method_missing(method, *args)
|
155
|
+
@@drb[@drb_url].send(method, *args)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a proxy object referring to the remote class required
|
159
|
+
def remote_class(class_name)
|
160
|
+
ClassRoom::Client::ProxyObject.new(@drb, class_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Load a class (or all classes, if :all is specified) from the
|
164
|
+
# ClassRoom server to be used locally
|
165
|
+
def load_class(b, *classes)
|
166
|
+
return nil unless classes && b
|
167
|
+
classes = self.classes if classes.first == :all
|
168
|
+
classes.each do |class_name|
|
169
|
+
eval %Q{
|
170
|
+
class #{class_name} < ClassRoom::Client::ProxyObject
|
171
|
+
end
|
172
|
+
#{class_name}.class_name = :\"#{class_name}\"
|
173
|
+
#{class_name}.drb = ClassRoom::Client::get_drb('#{@drb_url}')
|
174
|
+
}, b
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Load all classes on the ClassRoom server that do not already
|
179
|
+
# exist locally (not fully tested yet!)
|
180
|
+
def load_non_present_classes(b)
|
181
|
+
return nil unless b
|
182
|
+
classes = self.classes
|
183
|
+
classes.each do |class_name|
|
184
|
+
eval %Q{
|
185
|
+
unless defined?(#{class_name})
|
186
|
+
class #{class_name} < ClassRoom::Client::ProxyObject
|
187
|
+
end
|
188
|
+
end
|
189
|
+
}, b
|
190
|
+
eval %Q{
|
191
|
+
begin
|
192
|
+
#{class_name}.drb = @drb
|
193
|
+
rescue
|
194
|
+
end
|
195
|
+
}
|
196
|
+
eval %Q{
|
197
|
+
begin
|
198
|
+
#{class_name}.class_name = :\"#{class_name}\"
|
199
|
+
rescue
|
200
|
+
end
|
201
|
+
}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# ProxyObject acts as a local proxy for ClassRoom clients and
|
206
|
+
# handles passing through requests to run class methods and initializations
|
207
|
+
class ProxyObject
|
208
|
+
|
209
|
+
# Set up a weird /true/ class variable system (that is, class variable ==
|
210
|
+
# class variable, not weird hierarchy variable-foo as is Ruby's wont)
|
211
|
+
def self.drb; @@drb[self.to_s.to_sym]; end
|
212
|
+
def self.drb=(d); (@@drb ||= {})[self.to_s.to_sym] = d; end
|
213
|
+
def self.class_name; @@class_name[self.to_s.to_sym]; end
|
214
|
+
def self.class_name=(d); (@@class_name ||= {})[self.to_s.to_sym] = d; end
|
215
|
+
def drb; @@drb[self.class.to_s.to_sym]; end
|
216
|
+
def class_name; @@class_name[self.class.to_s.to_sym]; end
|
217
|
+
|
218
|
+
# Create an instance of the remote class
|
219
|
+
def initialize(*args)
|
220
|
+
@drbed_object = drb.send(:new_instance_of, self.class.to_s.to_sym, *args)
|
221
|
+
end
|
222
|
+
|
223
|
+
# If we're getting methods called on the proxy, they must be for
|
224
|
+
# the remote class rather than a method, so ask the ClassRoom server
|
225
|
+
# to run the class_method on the supplied object
|
226
|
+
def self.method_missing(method, *args)
|
227
|
+
drb.send(:class_method, class_name, method, *args)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Return the 'real' class name
|
231
|
+
#def class; @class_name; end
|
232
|
+
|
233
|
+
def method_missing(method, *args)
|
234
|
+
@drbed_object.send(method, *args)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'classroom'
|
4
|
+
|
5
|
+
# Get a binding for the top level
|
6
|
+
$top_level_binding = binding
|
7
|
+
|
8
|
+
class ClassRoomTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
$class_server = ClassRoom::Client.new('classroom://:2001')
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_a_classroom_server_responds
|
16
|
+
assert $class_server.classes.class == Array
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_b_can_add_basic_class
|
20
|
+
klass = %q{
|
21
|
+
class SmallClass
|
22
|
+
def self.result; "GOOD CLASS"; end
|
23
|
+
def result; "GOOD INSTANCE"; end
|
24
|
+
end
|
25
|
+
}
|
26
|
+
assert $class_server.add_class(klass)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_c_can_use_class_method_directly
|
30
|
+
assert_equal "GOOD CLASS", $class_server.class_method(:SmallClass, :result)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_d_can_create_instance_directly
|
34
|
+
assert_equal "GOOD INSTANCE", $class_server.new_instance_of(:SmallClass).result
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_e_can_import_classes
|
38
|
+
assert $class_server.load_class($top_level_binding, :all)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_f_can_use_imported_classes
|
42
|
+
assert_equal "GOOD CLASS", SmallClass.result
|
43
|
+
assert_equal "GOOD INSTANCE", SmallClass.new.result
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: classroom
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-07-12 00:00:00 +01:00
|
8
|
+
summary: ClassRoom is a 'class server' based on DRb
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: coops@petercooper.co.uk
|
12
|
+
homepage: http://classroom.rubyforge.org
|
13
|
+
rubyforge_project: classroom
|
14
|
+
description:
|
15
|
+
autorequire: classroom
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Peter Cooper
|
30
|
+
files:
|
31
|
+
- classroom.gemspec
|
32
|
+
- examples
|
33
|
+
- lib
|
34
|
+
- Rakefile
|
35
|
+
- test
|
36
|
+
- examples/demo_client.rb
|
37
|
+
- examples/demo_client2.rb
|
38
|
+
- examples/demo_client3.rb
|
39
|
+
- examples/demo_client4.rb
|
40
|
+
- examples/demo_client5.rb
|
41
|
+
- examples/server.rb
|
42
|
+
- examples/test_modules.rb
|
43
|
+
- examples/test_modules2.rb
|
44
|
+
- examples/test_modules3.rb
|
45
|
+
- lib/classroom.rb
|
46
|
+
- test/classroom_test.rb
|
47
|
+
test_files:
|
48
|
+
- test/classroom_test.rb
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
extra_rdoc_files: []
|
52
|
+
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
dependencies: []
|
60
|
+
|