zactor 0.0.4 → 0.0.5
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/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
|
-

|
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
|
-

|
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.
|