zactor 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -6
- data/Gemfile.lock +16 -66
- data/README.md +73 -147
- data/Rakefile +2 -0
- data/lib/ruby-interface/version.rb +3 -0
- data/lib/ruby-interface/yard.rb +8 -0
- data/lib/ruby_interface.rb +71 -0
- data/{zactor.gemspec → ruby-interface.gemspec} +7 -13
- data/spec/ruby_interface_spec.rb +127 -0
- data/spec/spec_helper.rb +1 -1
- metadata +15 -100
- data/.rake_tasks~ +0 -19
- data/.yardopts +0 -1
- data/Guardfile +0 -12
- data/VERSION +0 -1
- data/examples/chat/client.rb +0 -88
- data/examples/chat/server.rb +0 -59
- data/examples/ping/inproc.rb +0 -50
- data/lib/zactor/actor_pub.rb +0 -19
- data/lib/zactor/actor_sub.rb +0 -46
- data/lib/zactor/broker.rb +0 -71
- data/lib/zactor/log_subscriber.rb +0 -25
- data/lib/zactor/message.rb +0 -23
- data/lib/zactor/version.rb +0 -3
- data/lib/zactor.rb +0 -263
- data/spec/lib/actor_spec.rb +0 -136
- data/spec/lib/exchange_spec.rb +0 -81
- data/spec/lib/link_spec.rb +0 -67
- data/spec/lib/zactor_spec.rb +0 -40
data/Gemfile
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
3
|
group :development do
|
4
|
-
gem "rspec", "
|
4
|
+
gem "rspec", "~> 2.3.0"
|
5
5
|
gem "yard", "~> 0.6.0"
|
6
6
|
gem "bundler", "~> 1.0.0"
|
7
|
+
gem "jeweler", "~> 1.5.2"
|
7
8
|
gem "rcov", ">= 0"
|
8
9
|
gem "bluecloth"
|
9
10
|
gem "rr"
|
10
|
-
gem "guard-rspec"
|
11
|
-
gem "growl"
|
12
|
-
gem "ruby-debug19"
|
13
|
-
gem "em-spec", :git => "https://github.com/mloughran/em-spec.git", :branch => 'rspec2'
|
14
11
|
end
|
15
12
|
|
16
|
-
|
17
13
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,80 +1,33 @@
|
|
1
|
-
GIT
|
2
|
-
remote: https://github.com/mloughran/em-spec.git
|
3
|
-
revision: fc888b289a424fa93848ffdac3dc4b24fdd34950
|
4
|
-
branch: rspec2
|
5
|
-
specs:
|
6
|
-
em-spec (0.2.2)
|
7
|
-
|
8
1
|
PATH
|
9
2
|
remote: .
|
10
3
|
specs:
|
11
|
-
zactor (0.0.
|
4
|
+
zactor (0.0.5)
|
12
5
|
activesupport (> 0.1)
|
13
|
-
|
14
|
-
bson_ext (> 0.1)
|
15
|
-
em-zeromq (> 0.1)
|
16
|
-
ffi (> 0.1)
|
17
|
-
ffi-rzmq (> 0.1)
|
18
|
-
ruby-interface (> 0)
|
6
|
+
i18n (> 0.1)
|
19
7
|
|
20
8
|
GEM
|
21
9
|
remote: http://rubygems.org/
|
22
10
|
specs:
|
23
11
|
activesupport (3.0.7)
|
24
|
-
archive-tar-minitar (0.5.2)
|
25
12
|
bluecloth (2.1.0)
|
26
|
-
bson (1.2.4)
|
27
|
-
bson_ext (1.2.4)
|
28
|
-
columnize (0.3.2)
|
29
|
-
configuration (1.2.0)
|
30
13
|
diff-lcs (1.1.2)
|
31
|
-
|
32
|
-
eventmachine (>= 1.0.0.beta.3)
|
33
|
-
ffi-rzmq (>= 0.7.2)
|
34
|
-
eventmachine (1.0.0.beta.3)
|
35
|
-
ffi (1.0.7)
|
36
|
-
rake (>= 0.8.7)
|
37
|
-
ffi-rzmq (0.7.2)
|
38
|
-
growl (1.0.3)
|
39
|
-
guard (0.3.0)
|
40
|
-
open_gem (~> 1.4.2)
|
41
|
-
thor (~> 0.14.6)
|
42
|
-
guard-rspec (0.2.0)
|
43
|
-
guard (>= 0.2.2)
|
14
|
+
git (1.2.5)
|
44
15
|
i18n (0.5.0)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
ruby_core_source (>= 0.1.4)
|
50
|
-
open_gem (1.4.2)
|
51
|
-
launchy (~> 0.3.5)
|
16
|
+
jeweler (1.5.2)
|
17
|
+
bundler (~> 1.0.0)
|
18
|
+
git (>= 1.2.5)
|
19
|
+
rake
|
52
20
|
rake (0.8.7)
|
53
21
|
rcov (0.9.9)
|
54
22
|
rr (1.0.2)
|
55
|
-
rspec (2.
|
56
|
-
rspec-core (~> 2.
|
57
|
-
rspec-expectations (~> 2.
|
58
|
-
rspec-mocks (~> 2.
|
59
|
-
rspec-core (2.
|
60
|
-
rspec-expectations (2.
|
23
|
+
rspec (2.3.0)
|
24
|
+
rspec-core (~> 2.3.0)
|
25
|
+
rspec-expectations (~> 2.3.0)
|
26
|
+
rspec-mocks (~> 2.3.0)
|
27
|
+
rspec-core (2.3.1)
|
28
|
+
rspec-expectations (2.3.0)
|
61
29
|
diff-lcs (~> 1.1.2)
|
62
|
-
rspec-mocks (2.
|
63
|
-
ruby-debug-base19 (0.11.24)
|
64
|
-
columnize (>= 0.3.1)
|
65
|
-
linecache19 (>= 0.5.11)
|
66
|
-
ruby_core_source (>= 0.1.4)
|
67
|
-
ruby-debug19 (0.11.6)
|
68
|
-
columnize (>= 0.3.1)
|
69
|
-
linecache19 (>= 0.5.11)
|
70
|
-
ruby-debug-base19 (>= 0.11.19)
|
71
|
-
ruby-interface (0.0.2)
|
72
|
-
activesupport (> 0.1)
|
73
|
-
i18n (> 0.1)
|
74
|
-
ruby-interface
|
75
|
-
ruby_core_source (0.1.4)
|
76
|
-
archive-tar-minitar (>= 0.5.2)
|
77
|
-
thor (0.14.6)
|
30
|
+
rspec-mocks (2.3.0)
|
78
31
|
yard (0.6.5)
|
79
32
|
|
80
33
|
PLATFORMS
|
@@ -83,12 +36,9 @@ PLATFORMS
|
|
83
36
|
DEPENDENCIES
|
84
37
|
bluecloth
|
85
38
|
bundler (~> 1.0.0)
|
86
|
-
|
87
|
-
growl
|
88
|
-
guard-rspec
|
39
|
+
jeweler (~> 1.5.2)
|
89
40
|
rcov
|
90
41
|
rr
|
91
|
-
rspec (
|
92
|
-
ruby-debug19
|
42
|
+
rspec (~> 2.3.0)
|
93
43
|
yard (~> 0.6.0)
|
94
44
|
zactor!
|
data/README.md
CHANGED
@@ -1,167 +1,93 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Zactor позволяет любой руби-объект научить общаться с другими объектами посредством отправки и получения сообщений. При этом неважно где именно расположен объект, в том же процессе, в соседнем, или на другой физической машине.
|
1
|
+
RubyInterface
|
2
|
+
=============
|
4
3
|
|
5
|
-
|
4
|
+
Простенький патерн определения интерфейсов в руби. В противовес стандартным миксинам, для каждого интерфейса создается свой класс и соответсвенно экземпляр класса для каждого объекта с интерфейсом.
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
Для начала нужно стартануть Zactor.
|
16
|
-
|
17
|
-
Zactor.start 8000
|
18
|
-
|
19
|
-
Процесс забиндится на 0.0.0.0:8000, через эту точку будет происходить общение между zactor-процессами. Стартоваться должен в запущенном EM-контексте.
|
20
|
-
|
21
|
-
В каждый zactor-активный класс нужно делать include Zactor, после чего у класса и его экземпляров для доступа к функциями Zactor появится метод zactor. После создания объекта нужно выполнить zactor.init
|
22
|
-
|
23
|
-
class A
|
24
|
-
include Zactor
|
25
|
-
|
26
|
-
def initialize
|
27
|
-
zactor.init
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
Для отправки сообщений другому объекту нам нужно знать его идентификатор. Идентификатор можно получить тремя способами:
|
32
|
-
|
33
|
-
* Непосрдественной передачей. При инициализации или в любом другом месте, это исключительно внутренняя логика приложения. Идентификатор объекта можно получить вызвав zactor.actor
|
34
|
-
* При получении сообщения. В сообщении всегда содержится информация об отправителе
|
35
|
-
* Если объект имеет заранее известный identity, то мы можем получить его полный идентификатор вызвав Zactor.get_actor с identity и хостом, на котором он запущен
|
36
|
-
|
37
|
-
actor = Zactor.get_actor "broker", :host => "0.0.0.0:8001"
|
38
|
-
|
39
|
-
Получив идентификатор можно отправлять ему сообщения
|
40
|
-
|
41
|
-
zactor.send_request actor, :show_me, :boobs
|
42
|
-
|
43
|
-
|
44
|
-
Каждый класс может определять какие именно ивенты он может получать и что с ними делать
|
45
|
-
|
46
|
-
include Zactor
|
47
|
-
|
48
|
-
zactor do
|
49
|
-
event(:show_me) do |o, msg, what|
|
50
|
-
case what
|
51
|
-
when :boobs
|
52
|
-
do_it
|
53
|
-
else
|
54
|
-
do_smth_else
|
6
|
+
module Tree
|
7
|
+
extend RubyInterface
|
8
|
+
interface :tree do
|
9
|
+
include Enumerable
|
10
|
+
attr_accessor :parent
|
11
|
+
|
12
|
+
def childs
|
13
|
+
@childs ||= []
|
55
14
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
class A
|
62
|
-
include Zactor
|
63
|
-
|
64
|
-
def initialize
|
65
|
-
zactor.init
|
66
|
-
ping Zactor.get_actor("b")
|
67
|
-
end
|
68
|
-
|
69
|
-
def ping(actor)
|
70
|
-
puts "Ping!"
|
71
|
-
zactor.send_request actor, :ping do |res|
|
72
|
-
puts res
|
15
|
+
|
16
|
+
def each(&blk)
|
17
|
+
blk.call(owner)
|
18
|
+
childs.each { |v| v.tree.each(&blk) }
|
73
19
|
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
class B
|
79
|
-
include Zactor
|
80
|
-
|
81
|
-
zactor do
|
82
|
-
identity "b"
|
83
20
|
|
84
|
-
|
85
|
-
|
21
|
+
def set_parent(parent)
|
22
|
+
parent.tree.childs << owner
|
23
|
+
@parent = parent
|
86
24
|
end
|
87
25
|
end
|
88
|
-
|
89
|
-
def initialize
|
90
|
-
zactor.init
|
91
|
-
end
|
92
|
-
|
93
26
|
end
|
94
27
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
a = A.new
|
99
|
-
b = B.new
|
28
|
+
class A
|
29
|
+
include Tree
|
100
30
|
end
|
101
|
-
|
102
|
-
A посылает сообщение :ping для B, а B отвечает "Pong!"
|
103
|
-
|
104
|
-
В коллбэк определенный в event передается объект получившый сообщение, объект сообщения ({Zactor::Message}) и далее переданные в запросе аргументы (если они есть). У {Zactor::Message} есть два основных метода: sender, возвращающий идентификатор отправителя и reply, который посылает ответ на запрос.
|
105
|
-
|
106
|
-
Важный момент, identity должно задаваться ДО zactor.init и после этого не может меняться.
|
107
|
-
|
108
|
-
ZMQ
|
109
|
-
===
|
110
|
-
|
111
|
-
При Zactor.start стартует брокер, по одному на каждый процесс, через него проходят все сообщения данного процесса, принимает сообщения через SUB-сокет, отправляет через PUB. SUB подписан на все сообщения. Каждый zactor-объект создает по паре сокетов, PUB подключается к SUB-брокера, а SUB к PUB-брокера. SUB подписывается на сообщения содержащие его identity.
|
112
|
-
|
113
|
-
![ZMQ](images/zmq1.png)
|
114
|
-
|
115
|
-
Рассмотрим жизнь сообщения на примере с ping-ping. В случае с b в том же процессе:
|
116
|
-
|
117
|
-
<div class=wsd wsd_style="default"><pre>
|
118
|
-
A[PUB]->Broker[SUB]: Посылаем запрос :ping
|
119
|
-
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
120
|
-
Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
121
|
-
B[PUB]->Broker[SUB]: Отправляем ответ "Pong!"
|
122
|
-
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
123
|
-
Broker[PUB]->A[SUB]: Отправитель получает ответ
|
124
|
-
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
125
31
|
|
126
|
-
|
32
|
+
При разработке интерфейса не нужно задумываться о конфликтах имен переменных, методов, можно делать все что угодно. Аргументом к методу interface передается название метода, по которому этот интерфейс будет доступен.
|
127
33
|
|
128
|
-
|
129
|
-
|
130
|
-
App2 Broker[SUB]->App2 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
131
|
-
App2 Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
132
|
-
B[PUB for App1]->App1 Broker[SUB]: Отправляем ответ "Pong!"
|
133
|
-
App1 Broker[SUB]->App1 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
134
|
-
App1 Broker[PUB]->A[SUB]: Отправитель получает ответ
|
135
|
-
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
34
|
+
a = A.new
|
35
|
+
b = A.new
|
136
36
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
Так как это ZMQ, мы можем очень просто изменить тип получения сообщения. Например, добавив балансер. Теперь можно запускать процессы со ссылкой на этот балансер.
|
141
|
-
|
142
|
-
Zactor.start :balancer => "0.0.0.0:4000"
|
143
|
-
|
144
|
-
У нас получится примерно следующая схема:
|
145
|
-
|
146
|
-
![ZMQ](images/zmq2.png)
|
147
|
-
|
148
|
-
Теперь наш ping можно отправлять в балансер, а отвечать будет один из подключенных воркеров.
|
149
|
-
|
150
|
-
ping Zactor.get_actor("b", :host => "0.0.0.0:4000")
|
37
|
+
a.tree.set_parent b
|
38
|
+
b.tree.childs # => [a]
|
39
|
+
b.tree.map { |o| o } # => [b, a]
|
151
40
|
|
41
|
+
А при использовании методов относящихся к интерфейсу мы явно видим к какому же интерфейсу он относится. Всем профит!
|
152
42
|
|
153
|
-
|
154
|
-
===============
|
43
|
+
В интерфейсе доступен метод owner, возвращающий родительский объект. У класса интерфейса есть <tt>interface_base</tt>, возвращающий класс, куда интерфейс был заинклужен.
|
155
44
|
|
45
|
+
Помимо инстанс метода, создается так же класс-метод. В него можно передать блок, который выполнится в скоупе класса интерфейса. Сам метод возвращает класс интерфейса.
|
156
46
|
|
157
|
-
|
158
|
-
|
47
|
+
module StateMachine
|
48
|
+
extend RubyInterface
|
49
|
+
interface :state_machine do
|
50
|
+
def self.state(name)
|
51
|
+
puts "New state #{name}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
159
55
|
|
160
|
-
|
56
|
+
class A
|
57
|
+
include StateMachine
|
58
|
+
|
59
|
+
state_machine do
|
60
|
+
state(:parked) # => New state parked
|
61
|
+
state(:idling) # => New state idling
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
При наследовании класса с интерфейсом, создается новый класс интерфейса и наследуется от предыдущего, т.е. повторяет иерархию класса, в который он включен.
|
161
66
|
|
162
|
-
|
163
|
-
|
67
|
+
Если в блоке <tt>interface</tt> вызывается метод <tt>interfaced</tt>, то исполнение блока, передаваемого <tt>interfaced</tt>
|
68
|
+
происходит после добавления интерфейса в класс, в контексте этого класса.
|
164
69
|
|
165
|
-
|
166
|
-
|
167
|
-
|
70
|
+
Пример:
|
71
|
+
|
72
|
+
module A
|
73
|
+
extend RubyInterface
|
74
|
+
interface :int do
|
75
|
+
interfaced do
|
76
|
+
def baz
|
77
|
+
self.class.int_interface.foo
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.foo
|
82
|
+
"bar"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class B
|
88
|
+
include A
|
89
|
+
end
|
90
|
+
|
91
|
+
B.new.baz # => "bar"
|
92
|
+
|
93
|
+
В каждом модуле может быть определено произвольное количество интерфейсов
|
data/Rakefile
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
module RubyInterface
|
6
|
+
def interface(method_name, &interface_body)
|
7
|
+
mod_inst = self.const_set("#{method_name.to_s.camelize}InstanceMethods", Module.new)
|
8
|
+
mod_inst.module_eval <<-EOT, __FILE__, __LINE__ + 1
|
9
|
+
def #{method_name}
|
10
|
+
@#{method_name}_interface ||= self.class.#{method_name}_interface.new(self)
|
11
|
+
end
|
12
|
+
EOT
|
13
|
+
|
14
|
+
|
15
|
+
mod_class = self.const_set("#{method_name.to_s.camelize}ClassMethods", Module.new)
|
16
|
+
mod_class.module_eval <<-EOT, __FILE__, __LINE__ + 1
|
17
|
+
def #{method_name}(&blk)
|
18
|
+
self.#{method_name}_interface.class_eval(&blk) if blk
|
19
|
+
self.#{method_name}_interface
|
20
|
+
end
|
21
|
+
|
22
|
+
def inherited(subclass)
|
23
|
+
new_class = subclass.const_set("#{method_name.to_s.camelize}InterfaceClass", Class.new(self.#{method_name}_interface))
|
24
|
+
new_class.interface_base = subclass
|
25
|
+
subclass.#{method_name}_interface = new_class
|
26
|
+
super
|
27
|
+
end
|
28
|
+
EOT
|
29
|
+
|
30
|
+
interface_module = self
|
31
|
+
|
32
|
+
add_interface do |base|
|
33
|
+
base.send(:class_attribute, "#{method_name}_interface")
|
34
|
+
interface_class = base.const_set("#{method_name.to_s.camelize}InterfaceClass", Class.new(RubyInterface::InterfaceClass))
|
35
|
+
interface_class.interface_base = base
|
36
|
+
interface_class.class_eval(&interface_body) if interface_body
|
37
|
+
base.send("#{method_name}_interface=", interface_class)
|
38
|
+
base.extend mod_class
|
39
|
+
base.send :include, mod_inst
|
40
|
+
base.class_eval(&interface_class.interfaced) if interface_class.interfaced
|
41
|
+
end
|
42
|
+
|
43
|
+
interface_module.define_singleton_method(:included) do |base|
|
44
|
+
@_deps.each {|d| d.call(base)}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def add_interface &block
|
50
|
+
@_deps ||= []
|
51
|
+
@_deps << block
|
52
|
+
end
|
53
|
+
|
54
|
+
class InterfaceClass
|
55
|
+
class_attribute :interface_base
|
56
|
+
attr_accessor :owner
|
57
|
+
def initialize(owner)
|
58
|
+
@owner = owner
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
def interfaced(&block)
|
63
|
+
if block_given?
|
64
|
+
@_interfaced_block = block
|
65
|
+
else
|
66
|
+
@_interfaced_block
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "
|
3
|
+
require "ruby-interface/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{zactor}
|
7
|
-
s.version =
|
8
|
-
s.summary = "
|
7
|
+
s.version = RubyInterface::VERSION
|
8
|
+
s.summary = "Ruby interface"
|
9
9
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
-
s.authors = ["Andrew Rudenko"]
|
10
|
+
s.authors = ["Andrew Rudenko", "Nick Recobra"]
|
11
11
|
s.date = %q{2011-03-24}
|
12
|
-
s.description = %q{
|
12
|
+
s.description = %q{Ruby interface}
|
13
13
|
s.email = %q{ceo@prepor.ru}
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
@@ -17,12 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
-
s.add_dependency('ffi', ["> 0.1"])
|
21
|
-
s.add_dependency('ruby-interface', ["> 0"])
|
22
|
-
s.add_dependency('ffi-rzmq', ["> 0.1"])
|
23
|
-
s.add_dependency('em-zeromq', ["> 0.1"])
|
24
|
-
s.add_dependency('bson', ["> 0.1"])
|
25
|
-
s.add_dependency('bson_ext', ["> 0.1"])
|
26
20
|
s.add_dependency('activesupport', ["> 0.1"])
|
27
|
-
|
28
|
-
|
21
|
+
s.add_dependency(%q<i18n>, ["> 0.1"])
|
22
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RubyInterface do
|
5
|
+
def make_interface
|
6
|
+
mod = Module.new
|
7
|
+
mod.extend(RubyInterface)
|
8
|
+
mod
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Создание интерфейса" do
|
12
|
+
let(:mod) { make_interface.tap { |v| v.interface(:test) } }
|
13
|
+
|
14
|
+
it "должен вызываться interfaced, если определен" do
|
15
|
+
@proc = proc {}
|
16
|
+
klass = Class.new
|
17
|
+
mod.interface :int do
|
18
|
+
interfaced(&@proc)
|
19
|
+
end
|
20
|
+
mock(klass).class_eval(&@proc)
|
21
|
+
mock(klass).class_eval(&@proc)
|
22
|
+
klass.send :include, mod
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Класс с интерфейсом" do
|
26
|
+
subject { klass }
|
27
|
+
let(:klass) { Class.new.tap { |v| v.send :include, mod } }
|
28
|
+
let(:interface_klass) { klass.test_interface }
|
29
|
+
its(:test) { should eq(interface_klass) }
|
30
|
+
|
31
|
+
it "может дополнять класс интерфейса" do
|
32
|
+
interface_klass.respond_to?(:test).should be_false
|
33
|
+
klass.test do
|
34
|
+
mattr_accessor :test
|
35
|
+
end
|
36
|
+
interface_klass.respond_to?(:test).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "должен содержать класс интерфейса" do
|
40
|
+
klass::TestInterfaceClass.ancestors.should include(RubyInterface::InterfaceClass)
|
41
|
+
klass.test_interface.should eq(klass::TestInterfaceClass)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "Класс интерфейса" do
|
45
|
+
subject { interface_klass }
|
46
|
+
its(:interface_base) { should eq(klass) }
|
47
|
+
|
48
|
+
describe "Объект с интерфейсом" do
|
49
|
+
subject { obj }
|
50
|
+
let(:obj) { klass.new }
|
51
|
+
its(:test) { should be_kind_of(interface_klass)}
|
52
|
+
describe "Объект интерфейса" do
|
53
|
+
subject { interface_obj }
|
54
|
+
let(:interface_obj) { obj.test }
|
55
|
+
its(:owner) { should eq(obj) }
|
56
|
+
let(:mod) do
|
57
|
+
make_interface.tap do |v|
|
58
|
+
v.interface(:test) do
|
59
|
+
def foo
|
60
|
+
"bar"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "должен поддерживать методы определенные в интерфейсе" do
|
67
|
+
interface_obj.foo.should eq('bar')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "при наследовании" do
|
73
|
+
let(:klass2) { Class.new(klass) }
|
74
|
+
it "должен создавать новый интерфейс-класс, наследуя старый" do
|
75
|
+
klass2.test.ancestors.should include(interface_klass)
|
76
|
+
end
|
77
|
+
it "новый интерфейс-класс должен ссылаться на новый класс" do
|
78
|
+
klass2.test.interface_base.should eq(klass2)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "Модуль с двумя интерфейсами" do
|
88
|
+
before(:each) do
|
89
|
+
@mod = Module.new
|
90
|
+
@mod.send :extend, RubyInterface
|
91
|
+
@mod.interface :first do
|
92
|
+
interfaced do
|
93
|
+
def baz
|
94
|
+
first.foo
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def foo
|
99
|
+
"bar"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
@mod.interface :second do
|
104
|
+
interfaced do
|
105
|
+
self.second_interface.foo
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.foo
|
109
|
+
"baz"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "не должно происходить ошибок при подключении модуля с двумя интерфейсами" do
|
115
|
+
b = Class.new
|
116
|
+
lambda { b.send :include, @mod }.should_not raise_error
|
117
|
+
end
|
118
|
+
|
119
|
+
it "должны появиться интерфейсы с методами" do
|
120
|
+
b = Class.new
|
121
|
+
b.send :include, @mod
|
122
|
+
bb = b.new
|
123
|
+
bb.baz.should eq("bar")
|
124
|
+
bb.second_interface.foo.should eq("baz")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
require 'rspec'
|
4
|
-
require 'zactor'
|
5
4
|
require 'rr'
|
5
|
+
require 'ruby_interface'
|
6
6
|
|
7
7
|
# Requires supporting files with custom matchers and macros, etc,
|
8
8
|
# in ./support/ and its subdirectories.
|