weak_observable 1.0.0

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ -fp
3
+ -Ilib
4
+ -Ispec
5
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ # [v1.0.0][]
2
+
3
+ Initial release. WeakObservable and Hub!
4
+
5
+ [v1.0.0]: https://github.com/Burgestrand/weak_observable/tree/v1.0.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gemspec
3
+ gem 'yard'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Kim Burgestrand
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ # WeakObservable
2
+
3
+ WeakObservable is very similar to Observable from ruby’ standard library, but
4
+ with the very important difference in that it allows it’s subscribers to be
5
+ garbage collected.
6
+
7
+ ## Usage
8
+
9
+ First, install the gems with rubygems.
10
+
11
+ ```shell
12
+ gem install weak_observable
13
+ ```
14
+
15
+ Now include it onto any object, set up your subscribers and play away!
16
+
17
+ ```ruby
18
+ require 'weak_observable'
19
+
20
+ class Observer
21
+ def update(event, *args, &block)
22
+ puts "[#{event}]: #{args.inspect}"
23
+ end
24
+ end
25
+
26
+ class Playlist
27
+ include WeakObservable::Mixin
28
+ end
29
+
30
+ # Create an observer to wait for notifications.
31
+ observer = Observer.new
32
+
33
+ playlist = Playlist.new
34
+ playlist.observers << observer
35
+ playlist.observers.count # => 1
36
+
37
+ # Notify all observers. Any arguments and given block goes out to them all.
38
+ playlist.observers.notify(:ping)
39
+ # ^ the above ends up calling observer.update(:ping)
40
+
41
+ # Unassign the variable, allowing the observer to be garbage collected.
42
+ observer = nil
43
+
44
+ # Some time passes, ruby garbage collection eventually kicks in, and now…
45
+ playlist.observers.count # => 0
46
+ playlist.observers.notify(:ping) # nothing happens, we have no observers.
47
+ ```
48
+
49
+ ## Contributing
50
+
51
+ Please fork the repository and clone it. To get started with development you’ll
52
+ need to install the development dependencies and make sure you can run the
53
+ tests. You can easily install all development dependencies using bundler.
54
+
55
+ ```shell
56
+ $> gem install bundler
57
+ $> bundle install # => installs development dependencies
58
+ ```
59
+
60
+ Once you have the dependencies installed you should be able to run the tests.
61
+
62
+ ```shell
63
+ $> rake
64
+ ```
65
+
66
+ They should be green. Now go ahead and add your tests, add your changes, and push
67
+ it back up to GitHub. Once you feel you’re done create a pull request. Thank you!
@@ -0,0 +1,15 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ rescue LoadError
4
+ end
5
+
6
+ begin
7
+ require 'yard'
8
+ require 'yard/rake/yardoc_task'
9
+ YARD::Rake::YardocTask.new
10
+ end
11
+
12
+ require 'rspec/core/rake_task'
13
+ RSpec::Core::RakeTask.new
14
+
15
+ task :default => :spec
@@ -0,0 +1,89 @@
1
+ require "ref"
2
+ require "weak_observable/version"
3
+ require "weak_observable/mixin"
4
+ require "weak_observable/hub"
5
+
6
+ # WeakObservable is like the Observable from standard library,
7
+ # with a slightly different API and one important difference:
8
+ # it does not keep any strong references to it’s observers.
9
+ #
10
+ # This is useful in case you want observers, but don’t really
11
+ # care if they are garbage collected or not. Mostly useful for
12
+ # asynchronous callbacks in C extensions or FFI bindings.
13
+ #
14
+ # @example
15
+ # observable = WeakObservable.new
16
+ # observer = Object.new
17
+ #
18
+ # observable.add(observer)
19
+ # observable.notify # notifies observable
20
+ #
21
+ # observer = nil
22
+ # GC.start # this might garbage collect observer
23
+ # observable.notify # if observer is garbage collected, this does nothing now
24
+ class WeakObservable
25
+ def initialize
26
+ @monitor = Ref::SafeMonitor.new
27
+ @observers = Ref::WeakValueMap.new
28
+ @observers.extend(Enumerable)
29
+ @callbacks = Ref::WeakKeyMap.new
30
+ end
31
+
32
+ # Add an observer.
33
+ #
34
+ # @note You cannot add the same observer multiple times. The
35
+ # most recent call overrides any earlier calls; because
36
+ # of this you can change the method for an observer by
37
+ # calling add with a new method.
38
+ #
39
+ # @param [#hash] observer
40
+ # @param [String, Symbol] method
41
+ # @return observer, as passed in.
42
+ # @raise ArgumentError if observer does not respodn to method.
43
+ def add(observer, method = :update)
44
+ unless observer.respond_to?(method)
45
+ raise ArgumentError, "#{observer} does not respond to #{method}"
46
+ end
47
+
48
+ synchronize do
49
+ @callbacks[observer] = method
50
+ @observers[key(observer)] = observer
51
+ end
52
+ end
53
+
54
+ # Remove an observer.
55
+ #
56
+ # @param [#hash] observer
57
+ # @return [Object, nil] the observer, or nil if it was not an observer.
58
+ def delete(observer)
59
+ synchronize do
60
+ @callbacks.delete(observer)
61
+ @observers.delete(key(observer))
62
+ end
63
+ end
64
+
65
+ # Notify all observers, passing along all arguments and given block.
66
+ #
67
+ # @return [Array] all return values.
68
+ def notify(*args, &block)
69
+ synchronize do
70
+ @observers.map do |_, object|
71
+ callback = @callbacks[object]
72
+ # rare, but could happen if @callbacks is touched by finalizer
73
+ # before the @observers
74
+ next if callback.nil?
75
+ object.send(callback, *args, &block)
76
+ end
77
+ end
78
+ end
79
+
80
+ protected
81
+
82
+ def key(observer)
83
+ observer.hash
84
+ end
85
+
86
+ def synchronize
87
+ @monitor.synchronize { return yield }
88
+ end
89
+ end
@@ -0,0 +1,98 @@
1
+ require 'set'
2
+
3
+ class WeakObservable
4
+ # The Hub is like a hash, mapping a key to an observable.
5
+ #
6
+ # Hub holds no strong references. Objects added as observers
7
+ # for a given key can be garbage collected. When all observers
8
+ # for a given key has been garbage collected, the observable
9
+ # for that key is also (eventually) garbage collected.
10
+ #
11
+ # This class is useful for having observers for a given key
12
+ # without necessarily protecting them from being collected by
13
+ # the garbage collector. Say, if you were mapping a pointer
14
+ # address to a bunch of ruby objects for FFI callbacks.
15
+ #
16
+ # @example
17
+ # $hub = WeakObservable::Hub.new
18
+ #
19
+ # def some_callback(address, some_arg)
20
+ # $hub[address].notify(some_arg)
21
+ # end
22
+ #
23
+ # object = Object.new
24
+ # $hub[pointer.address] = object
25
+ # SomeFFIBinding.register_callback(object.pointer, :some_callback)
26
+ class Hub
27
+ def initialize
28
+ @mapping = Ref::WeakValueMap.new
29
+ @monitor = Ref::SafeMonitor.new
30
+ end
31
+
32
+ # Add an object as an observer for updates related to the given key.
33
+ #
34
+ # @param [#hash, #eql?] key
35
+ # @param object
36
+ # @param [Symbol, String] method
37
+ # @return object
38
+ def add(key, object, method = :update)
39
+ synchronize do
40
+ observable = (@mapping[key] ||= WeakObservable.new)
41
+ backrefs_of(object) << observable
42
+ observable.add(object, method)
43
+ end
44
+ end
45
+
46
+ # Remove an object as an observer from updates related to the given key.
47
+ #
48
+ # @param [#hash, #eql?] key
49
+ # @param object
50
+ # @return [Object, nil] object, or nil if it was not an observer for that key
51
+ def delete(key, object)
52
+ synchronize do
53
+ if observable = @mapping[key]
54
+ backrefs_of(object).delete(observable)
55
+ observable.delete(object)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Notify all observers listening to events on the given key.
61
+ # All arguments are passed, as well as the given block.
62
+ #
63
+ # @param [#hash, #eql?] key
64
+ # @param args
65
+ # @return [Array] all return values
66
+ def notify(key, *args, &block)
67
+ synchronize do
68
+ if observable = @mapping[key]
69
+ observable.notify(*args, &block)
70
+ end
71
+ end
72
+ end
73
+
74
+ protected
75
+
76
+ def synchronize
77
+ @monitor.synchronize { return yield }
78
+ end
79
+
80
+ # The observers retain their observable, but the hub does not.
81
+ # This way we don’t need to garbage collect our mapping, since
82
+ # when all observers are gone, the observables for the address
83
+ # they are registered to is also eligible for garbage collection.
84
+ def backrefs_of(object)
85
+ # The object may be attached to several hubs, so make sure
86
+ # we don’t fuck up by mapping our own object_id first.
87
+ ivar = '@__observable__hubs__'
88
+ back_hash = object.instance_variable_get(ivar)
89
+ back_hash ||= Hash.new
90
+ object.instance_variable_set(ivar, back_hash)
91
+
92
+ # An object may also be attached to several addresses within
93
+ # this hub. We want to allow this, but we don’t care about
94
+ # the addresses so we just keep a list of observables.
95
+ back_hash[object_id] ||= Set.new
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,7 @@
1
+ class WeakObservable
2
+ module Mixin
3
+ def observers
4
+ @__weak_observable_observers__ ||= WeakObservable.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class WeakObservable
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1 @@
1
+ require 'weak_observable'
@@ -0,0 +1,177 @@
1
+ describe WeakObservable::Hub do
2
+ let(:observable) { WeakObservable::Hub.new }
3
+ let(:observer) { stub(:update => 1) }
4
+ let(:key) { 1337 }
5
+ let(:other_key) { 0xDEADBEEF }
6
+
7
+ describe "#add and #notify" do
8
+ it "adds an observer for given key" do
9
+ observer.should_receive(:update)
10
+
11
+ observable.add(key, observer)
12
+ observable.notify(key)
13
+ end
14
+
15
+ it "allows us to specify callback method" do
16
+ observer.should_receive(:trigger)
17
+
18
+ observable.add(key, observer, :trigger)
19
+ observable.notify(key)
20
+ end
21
+
22
+ it "allows the same observer for multiple keys" do
23
+ observer.should_receive(:update).twice
24
+
25
+ observable.add(key, observer)
26
+ observable.add(other_key, observer)
27
+
28
+ observable.notify(key)
29
+ observable.notify(other_key)
30
+ end
31
+
32
+ it "allows different methods for different keys" do
33
+ observer.should_receive(:update).once
34
+ observer.should_receive(:trigger).once
35
+
36
+ observable.add(key, observer)
37
+ observable.add(other_key, observer, :trigger)
38
+
39
+ observable.notify(key)
40
+ observable.notify(other_key)
41
+ end
42
+
43
+ it "does not allow duplicates for the same key" do
44
+ observer.should_receive(:update).once
45
+
46
+ observable.add(key, observer)
47
+ observable.notify(key)
48
+ end
49
+
50
+ it "raises an error if observer does not respond to method" do
51
+ expect { observable.add(key, stub) }.to raise_error(ArgumentError)
52
+ end
53
+ end
54
+
55
+ describe "#notify" do
56
+ it "notifies observers only with the same key" do
57
+ observer.should_not_receive(:update)
58
+
59
+ observable.add(key, observer)
60
+ observable.notify(other_key)
61
+ end
62
+
63
+ it "passes along any args and given block" do
64
+ observer.should_receive(:update).with(1, 2).and_return do |x, y, &block|
65
+ block.call(x, y)
66
+ end
67
+
68
+ observable.add(key, observer)
69
+ observable.notify(key, 1, 2) { |x, y| x + y }.should eq [3]
70
+ end
71
+
72
+ it "returns all return values" do
73
+ observer1 = stub(:update => 1)
74
+ observer2 = stub(:update => 2)
75
+
76
+ observable.add(key, observer1)
77
+ observable.add(key, observer2)
78
+
79
+ observable.notify(key).should eq [1, 2]
80
+ end
81
+ end
82
+
83
+ describe "#delete" do
84
+ it "deletes the observer with the given key" do
85
+ observer.should_not_receive(:update)
86
+
87
+ observable.add(key, observer)
88
+ observable.delete(key, observer)
89
+ observable.notify(key).should eq []
90
+ end
91
+
92
+ it "does not crash trying to remove a non-existing observer" do
93
+ observable.add(key, observer)
94
+ expect { observable.delete(key, stub) }.to_not raise_error
95
+ end
96
+
97
+ it "does not crash trying to remove an observer with a non-existing key" do
98
+ expect { observable.delete(key, observer) }.to_not raise_error
99
+ end
100
+
101
+ it "keeps the observer for other keys" do
102
+ observer1 = stub(:update => 1)
103
+ observer2 = stub(:update => 2)
104
+
105
+ observer2.should_not_receive(:update)
106
+
107
+ observable.add(key, observer1)
108
+ observable.add(other_key, observer2)
109
+ observable.delete(other_key, observer2)
110
+
111
+ observable.notify(key).should eq [1]
112
+ observable.notify(other_key).should eq []
113
+ end
114
+
115
+ it "does not remove any other observer for a given key" do
116
+ observer1 = stub(:update => 1)
117
+ observer2 = stub(:update => 2)
118
+
119
+ observer2.should_not_receive(:update)
120
+
121
+ observable.add(key, observer1)
122
+ observable.add(key, observer2)
123
+ observable.delete(key, observer2)
124
+
125
+ observable.notify(key).should eq [1]
126
+ end
127
+ end
128
+
129
+ specify "observers can be garbage collected" do
130
+ counter = 0
131
+ finalizer = lambda { |_| counter += 1 }
132
+
133
+ threshold = 5
134
+ stretches = 5
135
+
136
+ threshold.times do
137
+ # stub with return value for method cannot be garbage collected.
138
+ observer = OpenStruct.new(:update => nil)
139
+ observable.add(key, observer)
140
+ ObjectSpace.define_finalizer(observer, finalizer)
141
+ end
142
+
143
+ (threshold * stretches).times do
144
+ GC.start
145
+ sleep 0.001
146
+ end
147
+
148
+ # if one is garbage collected, we can expect all to eventually be
149
+ # as well
150
+ counter.should be > 1
151
+ end
152
+
153
+ specify "entire keys can be garbage collected" do
154
+ counter = 0
155
+ finalizer = lambda { |_| counter += 1 }
156
+
157
+ threshold = 5
158
+ stretches = 5
159
+
160
+ threshold.times do |i|
161
+ # stub with return value for method cannot be garbage collected.
162
+ observer = OpenStruct.new(:update => nil)
163
+ observable.add(key + i, observer)
164
+ ObjectSpace.define_finalizer(observer, finalizer)
165
+ end
166
+
167
+ (threshold * stretches).times do
168
+ GC.start
169
+ sleep 0.001
170
+ end
171
+
172
+ # some very intimate introspection here
173
+ observable.instance_eval do
174
+ @mapping.to_a.length.should < threshold
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,14 @@
1
+ describe WeakObservable::Mixin do
2
+ let(:klass) { Class.new { include WeakObservable::Mixin } }
3
+ let(:observable) { klass.new }
4
+
5
+ describe "#observers" do
6
+ it "is a weak observable" do
7
+ observable.observers.should be_a WeakObservable
8
+ end
9
+
10
+ it "is memoized" do
11
+ observable.observers.should eql observable.observers
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,110 @@
1
+ require 'ostruct'
2
+
3
+ describe WeakObservable do
4
+ let(:observable) { WeakObservable.new }
5
+ let(:observer) { stub(:update => nil) }
6
+
7
+ it "has a version" do
8
+ WeakObservable::VERSION.should be_a String
9
+ end
10
+
11
+ describe "#add and #notify" do
12
+ it "adds an observer" do
13
+ observer.should_receive(:update)
14
+
15
+ observable.add(observer)
16
+ observable.notify
17
+ end
18
+
19
+ it "allows us to specify callback method" do
20
+ observer.should_receive(:trigger)
21
+
22
+ observable.add(observer, :trigger)
23
+ observable.notify
24
+ end
25
+
26
+ it "does not allow duplicates" do
27
+ observer.should_receive(:update).once
28
+
29
+ observable.add(observer)
30
+ observable.add(observer)
31
+ observable.notify
32
+ end
33
+
34
+ it "raises an error if observer does not respond to method" do
35
+ expect { observable.add(stub) }.to raise_error(ArgumentError)
36
+ end
37
+ end
38
+
39
+ describe "#notify" do
40
+ it "passes along any args given block" do
41
+ observer.should_receive(:update).with(1, 2).and_return do |&block|
42
+ block.call(2)
43
+ end
44
+
45
+ observable.add(observer)
46
+ observable.notify(1, 2) { |x| x * 2 }.should eq [4]
47
+ end
48
+
49
+ it "returns all return values" do
50
+ observer1 = stub(:update => 1)
51
+ observer2 = stub(:update => 2)
52
+
53
+ observable.add(observer1)
54
+ observable.add(observer2)
55
+
56
+ observable.notify.should eq [1, 2]
57
+ end
58
+ end
59
+
60
+ describe "#delete" do
61
+ it "removes the observer" do
62
+ observer.should_not_receive(:update)
63
+
64
+ observable.add(observer)
65
+ observable.delete(observer)
66
+ observable.notify.should eq []
67
+ end
68
+
69
+ it "does not remove any other observer" do
70
+ observer1 = stub(:update => 1)
71
+ observer2 = stub(:update => 2)
72
+
73
+ observer2.should_not_receive(:update)
74
+
75
+ observable.add(observer1)
76
+ observable.add(observer2)
77
+ observable.delete(observer2)
78
+
79
+ observable.notify.should eq [1]
80
+ end
81
+
82
+ it "does not crash removing a non-existing observer" do
83
+ expect { observable.delete(observer) }.to_not raise_error
84
+ end
85
+ end
86
+
87
+ specify "observers can be garbage collected" do
88
+ counter = 0
89
+ finalizer = lambda { |_| counter += 1 }
90
+
91
+ threshold = 5
92
+ stretches = 5
93
+
94
+ threshold.times do
95
+ # stub with return value for method cannot be garbage collected.
96
+ observer = OpenStruct.new(:update => nil)
97
+ observable.add(observer)
98
+ ObjectSpace.define_finalizer(observer, finalizer)
99
+ end
100
+
101
+ (threshold * stretches).times do
102
+ GC.start
103
+ sleep 0.001
104
+ end
105
+
106
+ # if one is garbage collected, we can expect all to eventually be
107
+ # as well
108
+ counter.should be > 1
109
+ end
110
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'weak_observable/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "weak_observable"
8
+ gem.version = WeakObservable::VERSION
9
+ gem.authors = ["Kim Burgestrand"]
10
+ gem.email = ["kim@burgestrand.se"]
11
+ gem.summary = "Like Observer from the standard library, but allows the subscribers to be garbage collected."
12
+ gem.description = <<-DESC.gsub(/ {2,}/, '')
13
+ Observable::Weak is very similar to Observable from ruby’ standard library, but
14
+ with the very important difference in that it allows it’s subscribers to be
15
+ garbage collected.
16
+ DESC
17
+ gem.homepage = "http://github.com/Burgestrand/weak_observable"
18
+
19
+ gem.files = `git ls-files`.split($/)
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ["lib"]
22
+
23
+ gem.add_dependency 'ref', '~> 1.0.0'
24
+ gem.add_development_dependency 'rspec', '~> 2.11.0'
25
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weak_observable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kim Burgestrand
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ref
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.11.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.11.0
46
+ description: ! 'Observable::Weak is very similar to Observable from ruby’ standard
47
+ library, but
48
+
49
+ with the very important difference in that it allows it’s subscribers to be
50
+
51
+ garbage collected.
52
+
53
+ '
54
+ email:
55
+ - kim@burgestrand.se
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - .gitignore
61
+ - .rspec
62
+ - CHANGELOG
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/weak_observable.rb
68
+ - lib/weak_observable/hub.rb
69
+ - lib/weak_observable/mixin.rb
70
+ - lib/weak_observable/version.rb
71
+ - spec/spec_helper.rb
72
+ - spec/weak_observable/hub_spec.rb
73
+ - spec/weak_observable/mixin_spec.rb
74
+ - spec/weak_observable/weak_observable_spec.rb
75
+ - weak_observable.gemspec
76
+ homepage: http://github.com/Burgestrand/weak_observable
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.24
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Like Observer from the standard library, but allows the subscribers to be
100
+ garbage collected.
101
+ test_files:
102
+ - spec/spec_helper.rb
103
+ - spec/weak_observable/hub_spec.rb
104
+ - spec/weak_observable/mixin_spec.rb
105
+ - spec/weak_observable/weak_observable_spec.rb
106
+ has_rdoc: