gbs-signal-slot 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,215 @@
1
+ = GBS::SignalSlot v. 1.0.0
2
+ Greenblack Software signal/slot pattern implementation for Ruby language
3
+
4
+ == GBS::SignalSlot User's Guide
5
+
6
+ 1. Introduction
7
+ 2. Connecting slots
8
+ 3. Disconnecting slots
9
+ 4. Class signals
10
+ 5. MIT License
11
+
12
+ === Introduction
13
+
14
+ GBS::SignalSlot is a signal/slot mechanism implementation done in Ruby.
15
+ The signal/slot pattern is a way of implementing the Observer design pattern.
16
+ Such implementation was introduced by Trolltech in their famous QT library.
17
+ Indeed the goal was achieved and resulted in code size reduction,
18
+ especially so called boilerplate code.
19
+
20
+ With the Ruby facilities of meta-programming the whole implementation can be
21
+ done within one single file with just a couple of lines. Such implementation was
22
+ proposed by Niklas Frykholm and improved by Nobuyoshi Nakada on <b>comp.lang.ruby</b>
23
+ usenet group (01-02-2002). Their work was the entry point to the Greenblack Software
24
+ implementation. In fact, the idea and main tricks are the same. Our improvements
25
+ were concerning on class methods support, better error checking, some additional
26
+ features and clean output.
27
+
28
+ === Connecting slots
29
+
30
+ The best way to see what GBS::SignalSlot is and to get into it a bit more, is to look
31
+ thoroughly at the following examples.
32
+
33
+ First, let we create a class, a container for a value. Of course with signals
34
+ our container will emit.
35
+
36
+ require "rubygems"
37
+ require "gbs_signal_slot"
38
+
39
+ class ValueContainer
40
+ include GBS::SignalSlot
41
+ attr_reader :value
42
+ signal :new_value # signals are defined just like readers or writers
43
+
44
+ def initialize(v)
45
+ @value = v
46
+ end
47
+
48
+ def value=(v)
49
+ @value = v
50
+ new_value(v) # signal activation
51
+ end
52
+ end
53
+
54
+ As you can guess, the signals are defined just like obvious attributes and
55
+ emitted by calling a method named after the symbol indicating a signal.
56
+ Their parameters are matter of our arbitrary choice, however we should
57
+ stick to our once defined convention. Here, we are passing the new
58
+ value that our container gets.
59
+
60
+ Particularly, the _new_value(v)_ method does not exist in the code.
61
+ It is added by the module behind the scene, as a private method.
62
+
63
+ Next, we can use some mixed-in methods that will allow us to connect and
64
+ disconnect signals and slots. The simplest way is to connect to anonymous closures:
65
+
66
+ vc = ValueContainer.new(0)
67
+ vc.connect(:new_value) { |v| puts "New value is #{v}" }
68
+
69
+ Now, each use of the value writer will result in sending relevant string
70
+ to the output:
71
+
72
+ vc.value = 1
73
+ # >> New value is 1
74
+
75
+ But to get more fun let's make a _Tracker_ object with both class
76
+ and instance methods which will act as slots for our _new_value_ signal.
77
+
78
+ class Tracker
79
+ def track_value(v)
80
+ puts "value changed to #{v}!"
81
+ end
82
+ def self.track_value(v)
83
+ puts "class slot method executed"
84
+ end
85
+ end
86
+
87
+ Now we may connect our signal to newly created tracker's slots.
88
+
89
+ t = Tracker.new
90
+ vc.connect(:new_value, :track_value, t) # track_value instance method
91
+ vc.connect(:new_value, :track_value, Tracker) # track_value class method
92
+ vc.value = 2
93
+
94
+ # >> New value is 2
95
+ # >> value changed to 2
96
+ # >> class slot method excecuted
97
+
98
+ The last possibility is to connect signal to a global method. Like below:
99
+
100
+ def track_fun(v)
101
+ puts "Global function"
102
+ end
103
+
104
+ vc.connect(:new_value, :track_fun) # that's all
105
+ vc.value = 3
106
+
107
+ # >> New value is 3
108
+ # >> value changed to 3
109
+ # >> class slot method excecuted
110
+ # >> Global function
111
+
112
+ It is so easy because the _obj_ parameter is set to _Object_ by default. Every global
113
+ method we create belongs to the _Object_ class, so we do not have to specify
114
+ anything else at all.
115
+
116
+ === Disconnecting slots
117
+
118
+ Ok, now it is time to clean everything up. The connected slots can be disconnected
119
+ using a somewhat contrary method: _disconnect_. Let's get the rid of the global function:
120
+
121
+ vc.disconnect(:new_value, :track_fun)
122
+ vc.value = 4
123
+
124
+ # >> New value is 4
125
+ # >> value changed to 4
126
+ # >> class slot method excecuted
127
+
128
+ As we see the global function call is removed. Similarly we may cut off other slot methods:
129
+
130
+ vc.disconnect(:new_value, :track_value, t)
131
+ vc.disconnect(:new_value, :track_value, Tracker)
132
+ vc.value = 5
133
+
134
+ # >> New value is 5
135
+
136
+ Ok. But how can we deal with the anonymous closure? There are two ways. The first one
137
+ is to get the name, a reference saying precisely, to the proc object. The second way
138
+ is to cut off all slots at once. Here is the action:
139
+
140
+ blah_proc = vc.connect(:new_value) { |v| puts "blah" } # returns the proc object
141
+ vc.value = 6
142
+
143
+ # >> New value is 6
144
+ # >> blah
145
+
146
+ vc.disconnect(:new_value, &blah_proc) # disconnecting the proc object
147
+ vc.value = 7
148
+
149
+ # >> New value is 7
150
+
151
+ If we do not have the proc object, the only way is to disconnect all slots.
152
+
153
+ vc.disconnect(:new_value) # disconnects all slots connected to the :new_value
154
+ vc.value = 8
155
+
156
+ # Nothing to print.
157
+
158
+ === Class signals
159
+
160
+ GBS::SignalSlot module can be used with class methods as well
161
+ (within classes or modules). The only difference is the use of
162
+ _class_signal_ method instead of _signal_.
163
+
164
+ class SomeClass
165
+ include GBS::SignalSlot
166
+
167
+ signal :signal1
168
+ class_signal :signal2
169
+
170
+ def some_method
171
+ signal1 # activation
172
+ end
173
+
174
+ def self.some_other_method
175
+ signal2 # class signal activation
176
+ end
177
+ end
178
+
179
+ some_instance.connect(:signal1) { puts "instance signal" }
180
+ SomeClass.connect(:signal2) { puts "class signal" }
181
+ # etc.
182
+
183
+ The way we activate signals is still identical. It is even possible
184
+ to use the same signal symbol for both contexts (class and instance)
185
+ at the same time. The contexts will not be messed up and the right
186
+ signals will activate right slots as we had defined before.
187
+
188
+ Such distinction (between _signal_ and _class_signal_) is due to readiness
189
+ of class/module definitions. From the technical point of view it is easy to
190
+ embed class and instance level behaviors into one method. The other reason is
191
+ the fewer amount of code generated and inserted into classes.
192
+
193
+ === MIT License
194
+
195
+ The MIT License
196
+
197
+ Copyright (c) 2008 Greenblack Software
198
+
199
+ Permission is hereby granted, free of charge, to any person obtaining a copy
200
+ of this software and associated documentation files (the "Software"), to deal
201
+ in the Software without restriction, including without limitation the rights
202
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
203
+ copies of the Software, and to permit persons to whom the Software is
204
+ furnished to do so, subject to the following conditions:
205
+
206
+ The above copyright notice and this permission notice shall be included in
207
+ all copies or substantial portions of the Software.
208
+
209
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
210
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
211
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
212
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
213
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
214
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
215
+ THE SOFTWARE.
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2008 Greenblack Software. Licensed under the MIT License.
2
+ # For more info please read the README or visit www.greenblacksoftware.com
3
+ # ----
4
+ # Based on Niklas Frykholm and Nobuyoshi Nakada signal/slot pattern implementations
5
+ # posted to comp.lang.ruby usenet group on 01-02-2002
6
+
7
+ # A default namespace used by Greenblack Software projects.
8
+ module GBS
9
+
10
+ # A module providing signal/slot mechanism.
11
+ # ----
12
+ # Copyright (c) 2008 Greenblack Software. Licensed under the MIT License.
13
+ # For more info please read the README or visit www.greenblacksoftware.com
14
+ # ----
15
+ # Based on Niklas Frykholm and Nobuyoshi Nakada signal/slot pattern implementations
16
+ # posted to comp.lang.ruby usenet group on 01-02-2002
17
+ module SignalSlot
18
+
19
+ # Connects a signal to a slot. The slot might be either a method referenced as
20
+ # a symbol (often together with a class or an instance) or just an anonymous closure
21
+ # passed as a block.
22
+ #
23
+ # :call-seq:
24
+ # sth.connect(signal) { ... } -> proc
25
+ # sth.connect(signal, slot) -> global slot method
26
+ # sth.connect(signal, slot, obj) -> obj.slot method
27
+ #
28
+ # === Parameters
29
+ # [+signal+]
30
+ # symbol defined in a class with the +signal+ or +class_signal+ method
31
+ # [+slot+]
32
+ # a method name referenced as a symbol object. If ommited the +connect+
33
+ # will try to bind a block
34
+ # [+obj+]
35
+ # an instance (for instance method), a class (for class methods)
36
+ # or nothing for global methods
37
+ #
38
+ # === Returns
39
+ # * determined procedure (method) object or +nil+ if only the +signal+ parameter
40
+ # was passed (without a block or a slot method name). It is important for
41
+ # disconnecting anonymous proc objects because this is the only way to obtain
42
+ # the reference. See also +GBS::SignalSlot#disconnect+
43
+ # ----
44
+ # === Examples
45
+ # [+sth.connect(:value_changed, :update_control, self)+]
46
+ # if the signal +:value_changed+ is emitted, the +self#update_control+
47
+ # will be executed
48
+ # [+sth.connect(:value_changed, update_status, MyClass)+]
49
+ # similar example but with +MyClass.update_status+ class method instead
50
+ # [+sth.connect(:value_changed, :global_update)+]
51
+ # now we are reffering to a global function (to something that
52
+ # belongs to the +Object+ class in fact as the default value of +obj+
53
+ # is set to +Object+)
54
+ # [+sth.connect(:value_changed) { |value| puts value }+]
55
+ # passing anonymous closure
56
+ def connect(signal, slot = nil, obj = Object, &pr)
57
+ s = slot ? obj.method(slot) : pr
58
+ raise ArgumentError, "No slot provided" unless s
59
+ ((@_gbs_signals ||= {})[signal] ||= []) << s
60
+ s
61
+ end
62
+
63
+ # Disconnects a signal from a slot. The slot might be either a method referenced as
64
+ # a symbol together with a class or an instance or just an closure passed as a parameter
65
+ # (with & prefix)
66
+ #
67
+ # :call-seq:
68
+ # sth.disconnect(signal)
69
+ # sth.disconnect(signal, &proc)
70
+ # sth.disconnect(signal, slot)
71
+ # sth.disconnect(signal, slot, obj)
72
+ #
73
+ # === Parameters
74
+ # [+signal+]
75
+ # symbol defined in a class with the +signal+ or +class_signal+ method
76
+ # [+slot+]
77
+ # a method name referenced as a symbol object. If ommited the +connect+
78
+ # will try to bind the +&proc+. If the +&proc+ parameter
79
+ # is also omitted, all connections to the signal will be erased.
80
+ # It disconnects all slots.
81
+ # [+obj+]
82
+ # an instance (for instance method), a class (for class methods)
83
+ # or nothing for global methods
84
+ # [+&proc+]
85
+ # a proc object appended with the & prefix. This object can be
86
+ # obtained by the +GBS::SignalSlot#connect+ method with anonymous closure
87
+ # passed.
88
+ # ----
89
+ # === Examples
90
+ # [+sth.disconnect(:value_changed, :update_control, self)+]
91
+ # if the signal +:value_changed+ is emitted,
92
+ # the +self#update_control+ will not be executed anymore
93
+ # [+sth.disconnect(:value_changed, update_status, MyClass)+]
94
+ # similar example but with +MyClass.update_status+ class method instead
95
+ # [+sth.disconnect(:value_changed, :global_update)+]
96
+ # now we are reffering to a global function (to something that
97
+ # belongs to the +Object+ class in fact as the default value of +obj+
98
+ # is set to +Object+)
99
+ # [+sth.disconnect(:value_changed, &proc)+]
100
+ # the +proc+ object will not be called anymore on
101
+ # +:value_changed+ signal activation.
102
+ # [+sth.disconnect(:value_changed)+]
103
+ # +:value_changed+ totally disconnected from all slots
104
+ def disconnect(signal, slot = nil, obj = Object, &pr)
105
+ s = slot ? obj.method(slot) : pr
106
+ return unless @_gbs_signals and @_gbs_signals[signal]
107
+ if s
108
+ @_gbs_signals[signal].delete(s)
109
+ else
110
+ @_gbs_signals[signal] = []
111
+ end
112
+ end
113
+
114
+ def _gbs_activate_signal(signal, *args, &pr)
115
+ return unless @_gbs_signals and @_gbs_signals[signal]
116
+ # little support for asynchronous connection
117
+ # adding/removing during execution
118
+ slots = @_gbs_signals[signal].clone
119
+ slots.each { |slot| slot.call(*args, &pr) }
120
+ end
121
+
122
+ module Sender
123
+
124
+ # Defines signals allowed to emit by an instance methods.
125
+ # Signals should be provided as symbols and activated by calling instance methods
126
+ # named after corresponding symbols. Those methods are appended dynamically.
127
+ def signal(*signals) # :doc:
128
+ signals.each { |s| module_eval method_str(s, false) }
129
+ end
130
+
131
+ # Defines signals allowed to emit by class methods.
132
+ # Signals should be provided as symbols and activated by calling class methods
133
+ # named after corresponding symbols. Those methods are appended dynamically.
134
+ def class_signal(*signals) # :doc:
135
+ signals.each { |s| module_eval method_str(s, true) }
136
+ end
137
+
138
+ def method_str(signal, klass)
139
+ %Q{
140
+ def #{klass ? "self." : ""}#{signal}(*args, &pr)
141
+ _gbs_activate_signal(:#{signal}, *args, &pr)
142
+ end
143
+ private#{klass ? "_class_method" : ""} :#{signal}
144
+ }
145
+ end
146
+
147
+ private :signal, :class_signal, :method_str
148
+ end
149
+
150
+ def self.append_features(klass)
151
+ super
152
+ klass.extend(Sender)
153
+ klass.extend(SignalSlot)
154
+ end
155
+
156
+ private :_gbs_activate_signal
157
+ private_class_method :append_features
158
+ end
159
+ end
@@ -0,0 +1,175 @@
1
+ # Copyright (c) 2008 Greenblack Software. Licensed under the MIT License.
2
+ # For more info please read the README or visit www.greenblacksoftware.com
3
+
4
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
5
+
6
+ require "test/unit"
7
+ require "gbs_signal_slot"
8
+
9
+ $global_mock_value = nil
10
+
11
+ def global_mock_method(text)
12
+ $global_mock_value = text
13
+ end
14
+
15
+ class TestSignalSlot < Test::Unit::TestCase
16
+ @@class_mock_value = nil
17
+
18
+ def setup
19
+ @val1 = nil
20
+ @val2 = nil
21
+ @@class_mock_value = nil
22
+ end
23
+
24
+ class InstanceTest
25
+ include GBS::SignalSlot
26
+
27
+ signal :test_signal
28
+
29
+ def fire_test_sig(t)
30
+ test_signal(t)
31
+ end
32
+ end
33
+
34
+ module ModuleTest
35
+ include GBS::SignalSlot
36
+
37
+ class_signal :class_test_signal
38
+
39
+ def self.fire_class_test_sig(t)
40
+ class_test_signal(t)
41
+ end
42
+ end
43
+
44
+ class InstanceClassTest
45
+ include GBS::SignalSlot
46
+
47
+ signal :test_signal
48
+ class_signal :class_test_signal
49
+
50
+ def fire_test_sig(t)
51
+ test_signal(t)
52
+ end
53
+
54
+ def self.fire_class_test_sig(t)
55
+ class_test_signal(t)
56
+ end
57
+ end
58
+
59
+ def mock_slot_method(text)
60
+ @val1 = text
61
+ end
62
+
63
+ def self.class_mock_method(text)
64
+ @@class_mock_value = text
65
+ end
66
+
67
+ def test_connect_instance
68
+ it = InstanceTest.new
69
+ it.connect(:test_signal) { |text| @val2 = text }
70
+ it.connect(:test_signal, :mock_slot_method, self)
71
+ it.connect(:test_signal, :class_mock_method, TestSignalSlot)
72
+ it.connect(:test_signal, :global_mock_method)
73
+ it.fire_test_sig("a")
74
+ assert_equal("a", @val2)
75
+ assert_equal("a", @val1)
76
+ assert_equal("a", @@class_mock_value)
77
+ assert_equal("a", $global_mock_value)
78
+ end
79
+
80
+ def test_connect_module
81
+ ModuleTest.connect(:class_test_signal) { |text| @val2 = text }
82
+ ModuleTest.connect(:class_test_signal, :mock_slot_method, self)
83
+ ModuleTest.fire_class_test_sig("b")
84
+ assert_equal("b", @val2)
85
+ assert_equal("b", @val1)
86
+ end
87
+
88
+ def test_connect_mixed
89
+ obj = InstanceClassTest.new
90
+ obj.connect(:test_signal) { |text| @val2 = text }
91
+ obj.connect(:test_signal, :mock_slot_method, self)
92
+ obj.fire_test_sig("a")
93
+ assert_equal("a", @val2)
94
+ assert_equal("a", @val1)
95
+ InstanceClassTest.connect(:class_test_signal) { |text| @val2 = text }
96
+ InstanceClassTest.connect(:class_test_signal, :mock_slot_method, self)
97
+ InstanceClassTest.fire_class_test_sig("b")
98
+ assert_equal("b", @val2)
99
+ assert_equal("b", @val1)
100
+ end
101
+
102
+ def test_disconnect
103
+ it = InstanceTest.new
104
+ proc = it.connect(:test_signal) { |text| @val2 = text }
105
+ it.connect(:test_signal, :mock_slot_method, self)
106
+ it.fire_test_sig(1)
107
+ assert_equal(1, @val2)
108
+ assert_equal(1, @val1)
109
+ @val2 = @val1 = nil
110
+ it.disconnect(:test_signal, &proc)
111
+ it.fire_test_sig(1)
112
+ assert_nil(@val2)
113
+ assert_not_nil(@val1)
114
+ @val2 = @val1 = nil
115
+ it.disconnect(:test_signal, :mock_slot_method, self)
116
+ it.fire_test_sig(1)
117
+ assert_nil(@val2)
118
+ assert_nil(@val1)
119
+ end
120
+
121
+ def test_disconnect_all
122
+ it = InstanceTest.new
123
+ it.connect(:test_signal) { |t| @val2 = t }
124
+ it.connect(:test_signal) { |t| @val1 = t }
125
+ it.fire_test_sig(1)
126
+ assert_equal(1, @val2)
127
+ assert_equal(1, @val1)
128
+ it.disconnect(:test_signal)
129
+ it.fire_test_sig(2)
130
+ assert_not_equal(2, @val2)
131
+ assert_not_equal(2, @val1)
132
+ end
133
+
134
+ def test_signal_class_instance
135
+ ic = InstanceClassTest.new
136
+ ic.connect(:test_signal) { |t| @val1 = t }
137
+ InstanceClassTest.connect(:test_signal) { |t| @val2 = t}
138
+ ic.fire_test_sig(1)
139
+ assert_equal(1, @val1)
140
+ assert_nil(@val2)
141
+ InstanceClassTest.fire_class_test_sig(2)
142
+ assert_equal(1, @val1)
143
+ assert_nil(@val2)
144
+ end
145
+
146
+ class SpecialInstanceClassTest
147
+ include GBS::SignalSlot
148
+ signal :test_signal
149
+ class_signal :test_signal
150
+
151
+ def self.fire_test_sig(n)
152
+ test_signal(n)
153
+ end
154
+ def fire_test_sig(n)
155
+ test_signal(n)
156
+ end
157
+ end
158
+
159
+ def test_special_case
160
+ SpecialInstanceClassTest.connect(:test_signal) { |v| @val1 = v }
161
+ s = SpecialInstanceClassTest.new
162
+ s.connect(:test_signal) { |v| @val2 = v }
163
+ SpecialInstanceClassTest.fire_test_sig(10)
164
+ assert_equal(10, @val1)
165
+ assert_nil(@val2)
166
+ s.fire_test_sig(3)
167
+ assert_equal(10, @val1)
168
+ assert_equal(3, @val2)
169
+ end
170
+
171
+ def test_bad_use
172
+ it = InstanceTest.new
173
+ assert_raise(ArgumentError) { it.connect(:test_signal) }
174
+ end
175
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gbs-signal-slot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Greenblack Software
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-06 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: info@greenblacksoftware.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - test/tc_gbs_signal_slot.rb
26
+ - lib/gbs_signal_slot.rb
27
+ - README
28
+ has_rdoc: true
29
+ homepage: www.greenblacksoftware.com
30
+ post_install_message:
31
+ rdoc_options: []
32
+
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project: gbs-signal-slot
50
+ rubygems_version: 1.1.0
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: GBS::SignalSlot - a signal/slot mechanism in Ruby
54
+ test_files:
55
+ - test/tc_gbs_signal_slot.rb