rrx 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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