rrx 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.
@@ -0,0 +1,20 @@
1
+ module Reactive::Observable
2
+ class Map < Wrapper
3
+
4
+ add_attributes :mapper
5
+
6
+ #TODO need to handle array returned from @map.call ?
7
+ observer_on_next do |value|
8
+ begin
9
+ new_value = @mapper.call(value)
10
+ rescue Exception => e
11
+ on_error(e)
12
+ else
13
+ #new_values.each {|v| @target.on_next(v) }
14
+ @target.on_next(new_value)
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,26 @@
1
+ module Reactive
2
+ module Observable
3
+ class Merge < DoubleWrapper
4
+
5
+ class Observer < ObserverWrapper
6
+ attr_accessor :num_completed
7
+
8
+ def initialize(*args)
9
+ @num_completed = 0
10
+ super
11
+ end
12
+
13
+ def on_complete
14
+ if num_completed == 0
15
+ self.num_completed = 1
16
+ else
17
+ self.target.on_complete
18
+ unwrap
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ module Reactive::Observable
2
+
3
+ class Push < Composite
4
+ add_attributes :o1, :o2
5
+
6
+ #def initialize(o1, o2)
7
+ # @o1, @o2 = o1, o2
8
+ #end
9
+
10
+ def initial_subscriptions
11
+ [@o1]
12
+ end
13
+
14
+ def observer_args(observer, parent)
15
+ [observer, parent, @o2]
16
+ end
17
+
18
+ class Observer < Reactive::ObserverWrapper
19
+ attr_reader :next_observable
20
+
21
+ def initialize(observer, parent, ob)
22
+ @next_observable = ob
23
+ super(observer, parent)
24
+ end
25
+
26
+ def on_complete
27
+ if @next_observable
28
+ next_observable = @next_observable
29
+ @next_observable = nil
30
+ disposable = next_observable.subscribe_observer(self)
31
+ wrap_with_parent(disposable) if @target
32
+ else
33
+ @target.on_complete
34
+ unwrap
35
+ end
36
+ end
37
+
38
+ def unwrap
39
+ @next_observable = nil
40
+ super
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,25 @@
1
+ module Reactive::Observable
2
+
3
+ class Skip < Wrapper
4
+ add_attributes :count
5
+
6
+ class Observer < Reactive::ObserverWrapper
7
+
8
+ def initialize(*args)
9
+ @skipped = 0
10
+ super
11
+ end
12
+
13
+ def on_next(value)
14
+ if @skipped == @count
15
+ @target.on_next(value)
16
+ else
17
+ @skipped += 1
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,10 @@
1
+ module Reactive::Observable
2
+
3
+ class Wrapper < Composite
4
+ add_attributes :target
5
+
6
+ def initial_subscriptions
7
+ [@target]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ module Reactive
2
+ class Observer
3
+
4
+ def initialize(handlers)
5
+ @handlers = handlers
6
+ end
7
+
8
+ def on_next(value)
9
+ @handlers[:on_next].call(value)
10
+ end
11
+
12
+ def on_complete
13
+ @handlers[:on_complete].call()
14
+ end
15
+
16
+ def on_error(error)
17
+ @handlers[:on_error].call(error)
18
+ unwrap
19
+ end
20
+
21
+ def unwrap
22
+ @handlers = nil
23
+ end
24
+
25
+
26
+ end
27
+
28
+ class EmptyObserver
29
+
30
+ def on_next(value); end
31
+ def on_complete; end
32
+ def on_error(error); end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,23 @@
1
+ module Reactive
2
+ class Scheduler
3
+
4
+ def schedule_periodic(at, action, wrapper = Disposable::Wrapper.new)
5
+ return unless wrapper
6
+ callback = lambda do
7
+ new_at = action.()
8
+ if new_at #defined ~
9
+ schedule_periodic(new_at, action, wrapper)
10
+ else
11
+ wrapper.unwrap
12
+ end
13
+ end
14
+ wrapper.target = schedule_once(at, callback)
15
+
16
+ end
17
+
18
+ def schedule_once(at, action)
19
+ EventMachine.add_timer(at / 1000.0, action)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Rrx
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,125 @@
1
+ require "reactive/version"
2
+ require 'weakref'
3
+ require 'pp'
4
+ require 'active_support'
5
+ require 'eventmachine'
6
+
7
+ require 'active_support/core_ext/module/delegation'
8
+ require 'active_support/core_ext/module/introspection'
9
+ require 'active_support/core_ext/class/attribute'
10
+
11
+ module Reactive
12
+
13
+ autoload :Observer, 'reactive/observer'
14
+ autoload :Scheduler, 'reactive/scheduler'
15
+ autoload :Observable, 'reactive/observable'
16
+
17
+ class AmbivalentRef < WeakRef
18
+
19
+ def self.create(value)
20
+ case value
21
+ when NilClass, TrueClass, FalseClass, Fixnum, Symbol
22
+ value
23
+ else
24
+ new(value)
25
+ end
26
+ end
27
+
28
+ def __getobj__
29
+ super rescue nil
30
+ end
31
+ end
32
+
33
+ module Disposable
34
+ class Wrapper
35
+ attr_reader :target
36
+
37
+ def initialize(target = nil, parent = nil)
38
+ @parent = AmbivalentRef.create(parent)
39
+ self.target = target
40
+ end
41
+
42
+ def target=(v)
43
+ @target = AmbivalentRef.create(v)
44
+ end
45
+
46
+ def unwrap
47
+ self.target = nil #return previous target?
48
+ end
49
+
50
+ end
51
+
52
+ class Closure
53
+ def initialize(&cleanup)
54
+ ObjectSpace.define_finalizer(self, cleanup)
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ class ObserverWrapper
61
+ attr_reader :target, :parent
62
+
63
+ def initialize(target, parent, opts = {})
64
+ attributes.each do |name, default|
65
+ instance_variable_set(:"@#{name}", opts[name])
66
+ end
67
+ @target = target
68
+ @parent = AmbivalentRef.new(parent)
69
+ end
70
+
71
+ def attributes
72
+ self.class.parent.attributes
73
+ end
74
+
75
+ def on_next(value)
76
+ @target.on_next(value)
77
+ end
78
+
79
+ def on_complete
80
+ @target.on_complete
81
+ unwrap
82
+ end
83
+
84
+ def wrap_with_parent(child)
85
+ @parent.target = child
86
+ end
87
+
88
+ def unwrap
89
+ attributes.each {|name, default| instance_variable_set(:"@#{name}", nil) }
90
+ @target = EmptyObserver.new
91
+ unwrap_parent if active?
92
+ end
93
+
94
+ def active?
95
+ @parent
96
+ end
97
+
98
+ def unwrap_parent(*args)
99
+ @parent.unwrap(*args)
100
+ end
101
+ end
102
+
103
+ #class ReplaySubject
104
+ #
105
+ #
106
+ # def subject
107
+ # @subject ||= Subject.new
108
+ # end
109
+ #
110
+ # def run(observer)
111
+ # wrapper = subject.run(observer)
112
+ # @queue.each {|q| q.accept(observer) }
113
+ # return wrapper
114
+ # end
115
+ #
116
+ # def on_next(value)
117
+ # @queue << value
118
+ # value.accept(subject)
119
+ # end
120
+ #
121
+ #end
122
+
123
+
124
+ end
125
+
@@ -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 'reactive/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rrx"
8
+ gem.version = Rrx::VERSION
9
+ gem.authors = ["Daniel Davis"]
10
+ gem.email = ["shoefish@gmail.com"]
11
+ gem.description = %q{Reactive Extensions provide an enumerable-like interface to process temporal data(events) with the eash usually reserved for structural Enumerables }
12
+ gem.summary = %q{Ruby Reactive Extensions}
13
+ gem.homepage = "https://github.com/emirikol/rrx"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'rspec'
22
+
23
+ gem.add_runtime_dependency 'eventmachine'
24
+ gem.add_runtime_dependency 'active_support'
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable::Count do
4
+
5
+ include Lets
6
+
7
+ context '#count from interval(1000)' do
8
+ before do
9
+ @s = Reactive::Observable.interval(1000).count
10
+ @s.subscribe(observer)
11
+ end
12
+
13
+ it 'fires with 1 after one interval' do
14
+ observer[:on_next].should_receive(:call).with(1)
15
+ advance_by(1001)
16
+ end
17
+
18
+ it 'fires with 2 after two intervals' do
19
+ observer[:on_next].should_receive(:call).with(2)
20
+ advance_by 2001
21
+ end
22
+
23
+ it 'fires with 7 after 7 intervals' do
24
+ observer[:on_next].should_receive(:call).with(7)
25
+ advance_by 7001
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable::First do
4
+ include Lets
5
+
6
+ context '#first(3) from interval' do
7
+ before do
8
+ @s = Reactive::Observable::First.new(target: Reactive::Observable.interval(1000), count: 3)
9
+ @s.subscribe(observer)
10
+ end
11
+
12
+ context 'after one interval' do
13
+ advance_by(1001) {
14
+ should send_call.with(0).to(observer, :on_next)
15
+ }
16
+ end
17
+
18
+ context 'after 3 intervals' do
19
+ advance_by(3001, ignore: :on_complete) {
20
+ should send_call.exactly(3).times.with(kind_of(Fixnum)).to(observer, :on_next)
21
+ }
22
+ advance_by(3001, ignore: :on_next) {
23
+ should send_call.with(no_args).to(observer, :on_complete)
24
+ }
25
+ end
26
+
27
+
28
+ context 'after 4 intervals' do
29
+ advance_by(4001, ignore: :on_complete) {
30
+ should send_call.exactly(3).times.with(kind_of(Fixnum)).to(observer, :on_next)
31
+ }
32
+ end
33
+
34
+ end
35
+
36
+ context do
37
+ it 'take 1 from 1', :focus => true do
38
+ @s = Reactive::Observable::First.new(target: Reactive::Observable.once(1), count: 1 )
39
+ observer[:on_next].should_receive(:call).with(1)
40
+ observer[:on_complete].should_receive(:call).with(no_args())
41
+ @s.subscribe(observer)
42
+ end
43
+
44
+ end
45
+
46
+
47
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable do
4
+ include Lets
5
+
6
+ context '#interval(400)' do
7
+
8
+ before do
9
+ @s = Reactive::Observable.interval(400)
10
+ @s.subscribe(observer)
11
+ end
12
+
13
+ it "does not fire on subscribe" do
14
+ observer[:on_next].should_not_receive(:call)
15
+ end
16
+
17
+ it "does not fire before interval has passed" do
18
+ scheduler.advance_by(200)
19
+ end
20
+
21
+ it "fires once interval was reached" do
22
+ observer[:on_next].should_receive(:call).with(0)
23
+ scheduler.advance_by(400)
24
+ end
25
+
26
+ it "fires for each interval passed" do
27
+ [0,1,2,3].each do |i|
28
+ observer[:on_next].should_receive(:call).times.with(i).ordered
29
+ end
30
+ scheduler.advance_by(1600)
31
+ end
32
+
33
+ context "when it has been destroyed" do
34
+ #didn't manage to garbage collect without using before block.
35
+ before do
36
+ remove_instance_variable(:@s)
37
+ ObjectSpace.garbage_collect
38
+ end
39
+ it 'does not fire' do
40
+ scheduler.advance_by(400)
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+
47
+ end