reacto 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Gemfile.lock +2 -2
- data/README.md +37 -0
- data/lib/reacto/cache/file.rb +103 -0
- data/lib/reacto/cache/memory.rb +44 -0
- data/lib/reacto/operations/cache.rb +53 -0
- data/lib/reacto/operations.rb +1 -6
- data/lib/reacto/resources/executor_resource.rb +1 -1
- data/lib/reacto/subscriptions/composite_subscription.rb +2 -1
- data/lib/reacto/subscriptions/flat_map_subscription.rb +8 -0
- data/lib/reacto/subscriptions/simple_subscription.rb +1 -1
- data/lib/reacto/trackable.rb +10 -1
- data/lib/reacto/version.rb +1 -1
- data/spec/reacto/create_trackable_spec.rb +0 -210
- data/spec/reacto/executors_and_trackable_spec.rb +0 -5
- data/spec/reacto/trackable/cache_spec.rb +64 -0
- data/spec/reacto/trackable/class_level/enumerable_spec.rb +39 -0
- data/spec/reacto/trackable/class_level/error_spec.rb +12 -0
- data/spec/reacto/trackable/class_level/interval_spec.rb +33 -0
- data/spec/reacto/trackable/class_level/later_spec.rb +23 -0
- data/spec/reacto/trackable/class_level/make_spec.rb +85 -0
- data/spec/reacto/trackable/class_level/value_spec.rb +12 -0
- data/spec/reacto/trackable_spec.rb +0 -35
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ce8f7dd2f7b2c680f62228680fe2dc6fcfbe5b5
|
4
|
+
data.tar.gz: 5518ae7b29f652786d20016dc3018b3d44975d8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16ea2d612d86393abfdc70c72f78369beb436a9ed14791a907e2a5771cc593bb941779bcef49b2906ad523b65e884d32fc7df6971a4ee9e7bee9da3c2c4f32f7
|
7
|
+
data.tar.gz: 747d7487a69e971d63fd100db3def9e147e0596b2d337714c3673d849705710e498bacb97ec24064ac5eb68d3cb5d2b60172f0202000e6aee207e7a7444926a7
|
data/.rspec
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
reacto (0.0.
|
4
|
+
reacto (0.0.1)
|
5
5
|
concurrent-ruby (~> 1.0.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
concurrent-ruby (1.0.
|
10
|
+
concurrent-ruby (1.0.1)
|
11
11
|
diff-lcs (1.2.5)
|
12
12
|
rspec (3.3.0)
|
13
13
|
rspec-core (~> 3.3.0)
|
data/README.md
CHANGED
@@ -1 +1,38 @@
|
|
1
1
|
# reacto
|
2
|
+
|
3
|
+
Reactive Programming for Ruby with some concurrency thrown into the mix!
|
4
|
+
|
5
|
+
## How to install?
|
6
|
+
|
7
|
+
If you use `bundler` just add this line to your `Gemfile`:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'reacto'
|
11
|
+
```
|
12
|
+
|
13
|
+
Alternatively you can instal the gem with `gem install reacto` or clone this
|
14
|
+
repository and play with it!
|
15
|
+
|
16
|
+
## Why?
|
17
|
+
|
18
|
+
Because it is very cool to be reactive these days!
|
19
|
+
Seriously - to be reactive means that your code is able to react on changes
|
20
|
+
happening in various places right away. For example third party data sources or
|
21
|
+
other parts of the code. It helps writing multy-component apps which could
|
22
|
+
handle failures and still be availale.
|
23
|
+
|
24
|
+
Of course there are other implemenations of reactive programming for ruby:
|
25
|
+
|
26
|
+
* [RxRuby](https://github.com/ReactiveX/RxRuby) : Very powerful implemenation
|
27
|
+
of RX. Handles concurrency and as a whole has more features than `Reacto`.
|
28
|
+
So why `Reacto`? `Reacto` has simpler interface it is native Ruby lib and
|
29
|
+
is easier to use it. The goal of `Reacto` is to be competitor to `RxRuby`
|
30
|
+
in the ruby world.
|
31
|
+
Still the author of reacto is big fan of `RX` especially `RxJava`. He even has
|
32
|
+
a book on the topic using `RxJava` :
|
33
|
+
[Learning Reactive Programming with Java 8](https://www.packtpub.com/application-development/learning-reactive-programming-java-8)
|
34
|
+
* [Frappuccino](https://github.com/steveklabnik/frappuccino) : Very cool lib,
|
35
|
+
easy to use and simply beautiful. The only drawback - it is a bit limitted :
|
36
|
+
no concurrency and small set of operators.
|
37
|
+
But if you don't need more complicated operators it is the best.
|
38
|
+
The author of `Reacto` highy recommends it.
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'reacto/cache/memory'
|
3
|
+
|
4
|
+
module Reacto
|
5
|
+
module Cache
|
6
|
+
class File
|
7
|
+
attr_reader :location, :ttl
|
8
|
+
attr_reader :data
|
9
|
+
|
10
|
+
def initialize(location: nil, ttl: 60)
|
11
|
+
@location = location
|
12
|
+
@ttl = ttl
|
13
|
+
|
14
|
+
if @location.nil?
|
15
|
+
fail(
|
16
|
+
ArgumentError, 'File location is mandatory while using file cache!'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ready?
|
22
|
+
return data.ready? unless data.nil?
|
23
|
+
|
24
|
+
fresh?
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
return unless ready?
|
29
|
+
deserialize
|
30
|
+
|
31
|
+
data.each do |value|
|
32
|
+
yield value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def error?
|
37
|
+
return false unless ready?
|
38
|
+
deserialize
|
39
|
+
|
40
|
+
data.error?
|
41
|
+
end
|
42
|
+
|
43
|
+
def closed?
|
44
|
+
return false unless ready?
|
45
|
+
deserialize
|
46
|
+
|
47
|
+
data.closed?
|
48
|
+
end
|
49
|
+
|
50
|
+
def error
|
51
|
+
return false unless ready?
|
52
|
+
deserialize
|
53
|
+
|
54
|
+
data.error
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_value(value)
|
58
|
+
init_data
|
59
|
+
|
60
|
+
data.on_value(value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_error(error)
|
64
|
+
init_data
|
65
|
+
|
66
|
+
data.on_error(error)
|
67
|
+
serialize
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_close
|
71
|
+
init_data
|
72
|
+
|
73
|
+
data.on_close
|
74
|
+
serialize
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def fresh?
|
80
|
+
::File.file?(location) && (Time.now - ::File.mtime(location)) <= ttl
|
81
|
+
end
|
82
|
+
|
83
|
+
def init_data
|
84
|
+
@data ||= Memory.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def deserialize
|
88
|
+
return unless fresh?
|
89
|
+
|
90
|
+
@data ||= YAML::load(::File.read(location))
|
91
|
+
end
|
92
|
+
|
93
|
+
def serialize
|
94
|
+
return if data.nil?
|
95
|
+
|
96
|
+
::File.open(location, 'w') do |f|
|
97
|
+
f.write(YAML::dump(data))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Reacto
|
2
|
+
module Cache
|
3
|
+
class Memory
|
4
|
+
def initialize(_ = nil)
|
5
|
+
@values = []
|
6
|
+
@closed = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def each
|
10
|
+
@values.each do |value|
|
11
|
+
yield value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def ready?
|
16
|
+
error? || closed?
|
17
|
+
end
|
18
|
+
|
19
|
+
def error?
|
20
|
+
@error != nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def closed?
|
24
|
+
@closed
|
25
|
+
end
|
26
|
+
|
27
|
+
def error
|
28
|
+
@error
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_value(value)
|
32
|
+
@values << value
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_error(error)
|
36
|
+
@error = error
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_close
|
40
|
+
@closed = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'reacto/constants'
|
2
|
+
require 'reacto/subscriptions/operation_subscription'
|
3
|
+
require 'reacto/cache/memory'
|
4
|
+
require 'reacto/cache/file'
|
5
|
+
|
6
|
+
module Reacto
|
7
|
+
module Operations
|
8
|
+
class Cache
|
9
|
+
TYPES = {
|
10
|
+
memory: Reacto::Cache::Memory,
|
11
|
+
file: Reacto::Cache::File
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(type: :memory, **settings)
|
15
|
+
type = TYPES[type] if TYPES.key?(type)
|
16
|
+
|
17
|
+
@cache = type.new(**settings)
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(tracker)
|
21
|
+
if @cache.ready?
|
22
|
+
@cache.each do |value|
|
23
|
+
tracker.on_value(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
if @cache.error?
|
27
|
+
tracker.on_error(@cache.error)
|
28
|
+
else
|
29
|
+
tracker.on_close
|
30
|
+
end
|
31
|
+
|
32
|
+
NOTHING
|
33
|
+
else
|
34
|
+
Subscriptions::OperationSubscription.new(
|
35
|
+
tracker,
|
36
|
+
value: -> (v) do
|
37
|
+
@cache.on_value(v)
|
38
|
+
tracker.on_value(v)
|
39
|
+
end,
|
40
|
+
error: -> (e) do
|
41
|
+
@cache.on_error(e)
|
42
|
+
tracker.on_error(e)
|
43
|
+
end,
|
44
|
+
close: -> () do
|
45
|
+
@cache.on_close
|
46
|
+
tracker.on_close
|
47
|
+
end
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/reacto/operations.rb
CHANGED
@@ -42,6 +42,7 @@ module Reacto
|
|
42
42
|
return unless subscribed?
|
43
43
|
return if waiting?
|
44
44
|
|
45
|
+
|
45
46
|
on_value_subscriptions(val)
|
46
47
|
after_on_value(val)
|
47
48
|
end
|
@@ -69,7 +70,7 @@ module Reacto
|
|
69
70
|
|
70
71
|
def on_close
|
71
72
|
return unless subscribed?
|
72
|
-
return
|
73
|
+
return if @subscriptions.any? { |s| !s.closed? }
|
73
74
|
|
74
75
|
@subscriber.on_close
|
75
76
|
unsubscribe
|
@@ -15,6 +15,14 @@ module Reacto
|
|
15
15
|
def on_value_subscriptions(v)
|
16
16
|
@subscriber.on_value(v)
|
17
17
|
end
|
18
|
+
|
19
|
+
def on_close
|
20
|
+
return unless subscribed?
|
21
|
+
return unless @subscriptions.any? { |s| !s.closed? }
|
22
|
+
|
23
|
+
@subscriber.on_close
|
24
|
+
unsubscribe
|
25
|
+
end
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
data/lib/reacto/trackable.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'concurrent'
|
2
2
|
|
3
|
+
require 'reacto/constants'
|
3
4
|
require 'reacto/behaviours'
|
4
5
|
require 'reacto/subscriptions'
|
5
6
|
require 'reacto/tracker'
|
@@ -92,6 +93,8 @@ module Reacto
|
|
92
93
|
task = Concurrent::TimerTask.new(execution_interval: interval) do
|
93
94
|
queue.push('ready')
|
94
95
|
end
|
96
|
+
|
97
|
+
Thread::abort_on_exception = true
|
95
98
|
thread = Thread.new do
|
96
99
|
begin
|
97
100
|
loop do
|
@@ -193,7 +196,8 @@ module Reacto
|
|
193
196
|
operation = block_given? ? block : operation
|
194
197
|
Trackable.new(nil, @executor) do |tracker_subscription|
|
195
198
|
begin
|
196
|
-
|
199
|
+
modified = operation.call(tracker_subscription)
|
200
|
+
lift_behaviour(modified) unless modified == NOTHING
|
197
201
|
rescue Exception => e
|
198
202
|
tracker_subscription.on_error(e)
|
199
203
|
end
|
@@ -282,6 +286,11 @@ module Reacto
|
|
282
286
|
lift(Operations::Throttle.new(delay))
|
283
287
|
end
|
284
288
|
|
289
|
+
def cache(type: :memory, **settings)
|
290
|
+
settings ||= {}
|
291
|
+
lift(Operations::Cache.new(type: type, **settings))
|
292
|
+
end
|
293
|
+
|
285
294
|
def track_on(executor)
|
286
295
|
lift(Operations::TrackOn.new(executor))
|
287
296
|
end
|
data/lib/reacto/version.rb
CHANGED
@@ -1,216 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
context Reacto::Trackable do
|
4
|
-
context '.error' do
|
5
|
-
it 'creates a new trackable emitting only the error passed' do
|
6
|
-
err = StandardError.new('Errrr')
|
7
|
-
attach_test_trackers described_class.error(err)
|
8
|
-
|
9
|
-
expect(test_data).to be == [err]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
context '.make' do
|
14
|
-
it 'creates a new trackable with custom behaviour passed as lambda' do
|
15
|
-
behaviour = lambda do |tracker|
|
16
|
-
(1..10).each do |v|
|
17
|
-
tracker.on_value(v)
|
18
|
-
end
|
19
|
-
|
20
|
-
tracker.on_close
|
21
|
-
end
|
22
|
-
|
23
|
-
attach_test_trackers described_class.make(behaviour)
|
24
|
-
|
25
|
-
expect(test_data.size).to be(11)
|
26
|
-
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'creates a new trackable with custom behaviour passed as block' do
|
30
|
-
trackable = described_class.make do |tracker|
|
31
|
-
(1..10).each do |v|
|
32
|
-
tracker.on_value(v)
|
33
|
-
end
|
34
|
-
|
35
|
-
tracker.on_close
|
36
|
-
end
|
37
|
-
attach_test_trackers trackable
|
38
|
-
|
39
|
-
expect(test_data.size).to be(11)
|
40
|
-
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'does not emit anything once it is closed' do
|
44
|
-
trackable = described_class.make do |tracker|
|
45
|
-
(1..5).each do |v|
|
46
|
-
tracker.on_value(v)
|
47
|
-
end
|
48
|
-
|
49
|
-
tracker.on_close
|
50
|
-
|
51
|
-
(1..5).each do |v|
|
52
|
-
tracker.on_value(v)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
attach_test_trackers trackable
|
56
|
-
|
57
|
-
expect(test_data.size).to be(6)
|
58
|
-
expect(test_data).to be == [1, 2, 3, 4, 5, '|']
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'does not emit anything once it has an error' do
|
62
|
-
trackable = described_class.make do |tracker|
|
63
|
-
(1..5).each do |v|
|
64
|
-
tracker.on_value(v)
|
65
|
-
end
|
66
|
-
|
67
|
-
tracker.on_error('error')
|
68
|
-
|
69
|
-
(1..5).each do |v|
|
70
|
-
tracker.on_value(v)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
attach_test_trackers trackable
|
74
|
-
|
75
|
-
expect(test_data.size).to be(6)
|
76
|
-
expect(test_data).to be == [1, 2, 3, 4, 5, 'error']
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'emits the same behaviour for every subscribe' do
|
80
|
-
trackable = described_class.make do |tracker|
|
81
|
-
(1..5).each do |v|
|
82
|
-
tracker.on_value(v)
|
83
|
-
end
|
84
|
-
|
85
|
-
tracker.on_close
|
86
|
-
end
|
87
|
-
attach_test_trackers trackable
|
88
|
-
attach_test_trackers trackable
|
89
|
-
|
90
|
-
expect(test_data.size).to be(12)
|
91
|
-
expect(test_data).to be == [1, 2, 3, 4, 5, '|', 1, 2, 3, 4, 5, '|']
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context '.later' do
|
96
|
-
it 'emits the passed value after the passed time runs out and then emits ' \
|
97
|
-
'a close notification' do
|
98
|
-
trackable = described_class.later(0.2, 5)
|
99
|
-
subscription = attach_test_trackers(trackable)
|
100
|
-
|
101
|
-
expect(test_data).to be_empty
|
102
|
-
trackable.await(subscription)
|
103
|
-
expect(test_data).to be == [5, '|']
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'it can use a specific executor' do
|
107
|
-
trackable = described_class.later(
|
108
|
-
0.2, 5, executor: Reacto::Executors.immediate
|
109
|
-
)
|
110
|
-
subscription = attach_test_trackers(trackable)
|
111
|
-
expect(test_data).to be == [5, '|']
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context '.interval' do
|
116
|
-
it 'emits an infinite sequence of number on every n seconds by default' do
|
117
|
-
trackable = described_class.interval(0.1)
|
118
|
-
subscription = attach_test_trackers(trackable)
|
119
|
-
sleep 1
|
120
|
-
subscription.unsubscribe
|
121
|
-
|
122
|
-
expect(test_data).to be == (0..8).to_a
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'can use any enumerator to produce the sequence to emit' do
|
126
|
-
trackable = described_class.interval(0.1, ('a'..'z').each)
|
127
|
-
subscription = attach_test_trackers(trackable)
|
128
|
-
sleep 1
|
129
|
-
subscription.unsubscribe
|
130
|
-
|
131
|
-
expect(test_data).to be == ('a'..'i').to_a
|
132
|
-
end
|
133
|
-
|
134
|
-
it 'can use the immediate executor to block the current thread while ' \
|
135
|
-
'emitting' do
|
136
|
-
trackable = described_class.interval(
|
137
|
-
0.1, (1..5).each, executor: Reacto::Executors.immediate
|
138
|
-
)
|
139
|
-
subscription = attach_test_trackers(trackable)
|
140
|
-
|
141
|
-
expect(test_data).to be == (1..5).to_a + ['|']
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
context '.value' do
|
146
|
-
it 'emits only the passed value and then closes' do
|
147
|
-
trackable = described_class.value(5)
|
148
|
-
subscription = attach_test_trackers(trackable)
|
149
|
-
|
150
|
-
expect(test_data).to be == [5, '|']
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
context '.enumerable' do
|
155
|
-
it 'emits the whole enumerable one-by-one and then closes' do
|
156
|
-
trackable = described_class.enumerable([1, 3, 5, 6, 7, 8])
|
157
|
-
subscription = attach_test_trackers(trackable)
|
158
|
-
|
159
|
-
expect(test_data).to be == [1, 3, 5, 6, 7, 8, '|']
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'on error it emits all til the error and the error' do
|
163
|
-
class PositiveArray
|
164
|
-
include Enumerable
|
165
|
-
|
166
|
-
def error
|
167
|
-
@error ||= StandardError.new('Bad.')
|
168
|
-
end
|
169
|
-
|
170
|
-
def initialize(*members)
|
171
|
-
@members = members
|
172
|
-
end
|
173
|
-
|
174
|
-
def each(&block)
|
175
|
-
@members.each do |member|
|
176
|
-
raise error if member < 0
|
177
|
-
block.call(member)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
enumerable = PositiveArray.new(2, 3, -4, 3, 6)
|
183
|
-
trackable = described_class.enumerable(enumerable)
|
184
|
-
subscription = attach_test_trackers(trackable)
|
185
|
-
|
186
|
-
expect(test_data).to be == [2, 3, enumerable.error]
|
187
|
-
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
context '.combine' do
|
192
|
-
it 'combines the notifications of Trackables with different number of ' \
|
193
|
-
'notifications using the passed combinator' do
|
194
|
-
trackable1 = described_class.interval(0.3).take(4)
|
195
|
-
trackable2 = described_class.interval(0.7, ('a'..'b').each)
|
196
|
-
trackable3 = described_class.interval(0.5, ('A'..'C').each)
|
197
|
-
|
198
|
-
trackable = described_class.combine(
|
199
|
-
trackable1, trackable2, trackable3
|
200
|
-
) do |v1, v2, v3|
|
201
|
-
"#{v1} : #{v2} : #{v3}"
|
202
|
-
end
|
203
|
-
|
204
|
-
subscription = attach_test_trackers(trackable)
|
205
|
-
trackable.await(subscription)
|
206
|
-
|
207
|
-
expect(test_data).to be == [
|
208
|
-
'1 : a : A', '2 : a : A', '2 : a : B', '3 : a : B',
|
209
|
-
'3 : b : B', '3 : b : C', '|'
|
210
|
-
]
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
4
|
context '.combine_last' do
|
215
5
|
it 'combines the notifications of Trackables based on their ' \
|
216
6
|
'sequence number - the first notification of the sources, then ' \
|
@@ -1,11 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
context Reacto::Trackable do
|
4
|
-
|
5
|
-
let(:test_data) { [] }
|
6
|
-
let(:test_on_value) { -> (v) { test_data << v }}
|
7
|
-
let(:test_on_close) { -> () { test_data << '|' }}
|
8
|
-
|
9
4
|
subject do
|
10
5
|
described_class.new do |tracker_subscription|
|
11
6
|
tracker_subscription.on_value(16)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
let(:source_updates) { [] }
|
5
|
+
let(:source) do
|
6
|
+
Reacto::Trackable.make do |tracker|
|
7
|
+
source_updates << true
|
8
|
+
|
9
|
+
tracker.on_value(3)
|
10
|
+
tracker.on_value(4)
|
11
|
+
tracker.on_value(5)
|
12
|
+
tracker.on_close
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_cache(trackable)
|
17
|
+
trackable.on(value: test_on_value, close: test_on_close)
|
18
|
+
expect(test_data).to be == [3, 4, 5, '|']
|
19
|
+
expect(source_updates).to be == [true]
|
20
|
+
|
21
|
+
trackable.on(value: test_on_value, close: test_on_close)
|
22
|
+
expect(test_data).to be == [3, 4, 5, '|', 3, 4, 5, '|']
|
23
|
+
expect(source_updates).to be == [true]
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#cache' do
|
27
|
+
context 'memory' do
|
28
|
+
it 'caches all the values received before the closing notifincation and ' \
|
29
|
+
'replayes them on subsequent subscriptions' do
|
30
|
+
check_cache(source.cache)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'file' do
|
35
|
+
after(:each) do
|
36
|
+
File.delete('tmp/test.cache')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'caches all the values received before the closing notifincation and ' \
|
40
|
+
'replayes them on subsequent subscriptions' do
|
41
|
+
check_cache(source.cache(type: :file, location: 'tmp/test.cache'))
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'uses the cache only if it is fresh' do
|
45
|
+
trackable1 =
|
46
|
+
source.cache(type: :file, location: 'tmp/test.cache', ttl: 0.5)
|
47
|
+
|
48
|
+
check_cache(trackable1)
|
49
|
+
|
50
|
+
trackable2 =
|
51
|
+
source.cache(type: :file, location: 'tmp/test.cache', ttl: 0.5)
|
52
|
+
trackable2.on
|
53
|
+
expect(source_updates).to be == [true]
|
54
|
+
|
55
|
+
sleep(1)
|
56
|
+
|
57
|
+
trackable3 =
|
58
|
+
source.cache(type: :file, location: 'tmp/test.cache', ttl: 0.5)
|
59
|
+
trackable3.on
|
60
|
+
expect(source_updates).to be == [true] * 2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.enumerable' do
|
5
|
+
it 'emits the whole enumerable one-by-one and then closes' do
|
6
|
+
trackable = described_class.enumerable([1, 3, 5, 6, 7, 8])
|
7
|
+
subscription = attach_test_trackers(trackable)
|
8
|
+
|
9
|
+
expect(test_data).to be == [1, 3, 5, 6, 7, 8, '|']
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'on error it emits all til the error and the error' do
|
13
|
+
class PositiveArray
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
def error
|
17
|
+
@error ||= StandardError.new('Bad.')
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(*members)
|
21
|
+
@members = members
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(&block)
|
25
|
+
@members.each do |member|
|
26
|
+
raise error if member < 0
|
27
|
+
block.call(member)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
enumerable = PositiveArray.new(2, 3, -4, 3, 6)
|
33
|
+
trackable = described_class.enumerable(enumerable)
|
34
|
+
subscription = attach_test_trackers(trackable)
|
35
|
+
|
36
|
+
expect(test_data).to be == [2, 3, enumerable.error]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.error' do
|
5
|
+
it 'creates a new trackable emitting only the error passed' do
|
6
|
+
err = StandardError.new('Errrr')
|
7
|
+
attach_test_trackers described_class.error(err)
|
8
|
+
|
9
|
+
expect(test_data).to be == [err]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.interval' do
|
5
|
+
it 'emits an infinite sequence of number on every n seconds by default' do
|
6
|
+
trackable = described_class.interval(0.1)
|
7
|
+
subscription = attach_test_trackers(trackable)
|
8
|
+
sleep 1
|
9
|
+
subscription.unsubscribe
|
10
|
+
|
11
|
+
expect(test_data).to be == (0..8).to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can use any enumerator to produce the sequence to emit' do
|
15
|
+
trackable = described_class.interval(0.1, ('a'..'z').each)
|
16
|
+
subscription = attach_test_trackers(trackable)
|
17
|
+
sleep 1
|
18
|
+
subscription.unsubscribe
|
19
|
+
|
20
|
+
expect(test_data).to be == ('a'..'i').to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can use the immediate executor to block the current thread while ' \
|
24
|
+
'emitting' do
|
25
|
+
trackable = described_class.interval(
|
26
|
+
0.1, (1..5).each, executor: Reacto::Executors.immediate
|
27
|
+
)
|
28
|
+
subscription = attach_test_trackers(trackable)
|
29
|
+
|
30
|
+
expect(test_data).to be == (1..5).to_a + ['|']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.later' do
|
5
|
+
it 'emits the passed value after the passed time runs out and then emits ' \
|
6
|
+
'a close notification' do
|
7
|
+
trackable = described_class.later(0.2, 5)
|
8
|
+
subscription = attach_test_trackers(trackable)
|
9
|
+
|
10
|
+
expect(test_data).to be_empty
|
11
|
+
trackable.await(subscription)
|
12
|
+
expect(test_data).to be == [5, '|']
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'it can use a specific executor' do
|
16
|
+
trackable = described_class.later(
|
17
|
+
0.2, 5, executor: Reacto::Executors.immediate
|
18
|
+
)
|
19
|
+
subscription = attach_test_trackers(trackable)
|
20
|
+
expect(test_data).to be == [5, '|']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.make' do
|
5
|
+
it 'creates a new trackable with custom behaviour passed as lambda' do
|
6
|
+
behaviour = lambda do |tracker|
|
7
|
+
(1..10).each do |v|
|
8
|
+
tracker.on_value(v)
|
9
|
+
end
|
10
|
+
|
11
|
+
tracker.on_close
|
12
|
+
end
|
13
|
+
|
14
|
+
attach_test_trackers described_class.make(behaviour)
|
15
|
+
|
16
|
+
expect(test_data.size).to be(11)
|
17
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates a new trackable with custom behaviour passed as block' do
|
21
|
+
trackable = described_class.make do |tracker|
|
22
|
+
(1..10).each do |v|
|
23
|
+
tracker.on_value(v)
|
24
|
+
end
|
25
|
+
|
26
|
+
tracker.on_close
|
27
|
+
end
|
28
|
+
attach_test_trackers trackable
|
29
|
+
|
30
|
+
expect(test_data.size).to be(11)
|
31
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not emit anything once it is closed' do
|
35
|
+
trackable = described_class.make do |tracker|
|
36
|
+
(1..5).each do |v|
|
37
|
+
tracker.on_value(v)
|
38
|
+
end
|
39
|
+
|
40
|
+
tracker.on_close
|
41
|
+
|
42
|
+
(1..5).each do |v|
|
43
|
+
tracker.on_value(v)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
attach_test_trackers trackable
|
47
|
+
|
48
|
+
expect(test_data.size).to be(6)
|
49
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, '|']
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not emit anything once it has an error' do
|
53
|
+
trackable = described_class.make do |tracker|
|
54
|
+
(1..5).each do |v|
|
55
|
+
tracker.on_value(v)
|
56
|
+
end
|
57
|
+
|
58
|
+
tracker.on_error('error')
|
59
|
+
|
60
|
+
(1..5).each do |v|
|
61
|
+
tracker.on_value(v)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
attach_test_trackers trackable
|
65
|
+
|
66
|
+
expect(test_data.size).to be(6)
|
67
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 'error']
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'emits the same behaviour for every subscribe' do
|
71
|
+
trackable = described_class.make do |tracker|
|
72
|
+
(1..5).each do |v|
|
73
|
+
tracker.on_value(v)
|
74
|
+
end
|
75
|
+
|
76
|
+
tracker.on_close
|
77
|
+
end
|
78
|
+
attach_test_trackers trackable
|
79
|
+
attach_test_trackers trackable
|
80
|
+
|
81
|
+
expect(test_data.size).to be(12)
|
82
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, '|', 1, 2, 3, 4, 5, '|']
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
context '.value' do
|
5
|
+
it 'emits only the passed value and then closes' do
|
6
|
+
trackable = described_class.value(5)
|
7
|
+
subscription = attach_test_trackers(trackable)
|
8
|
+
|
9
|
+
expect(test_data).to be == [5, '|']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -13,39 +13,4 @@ context Reacto::Trackable do
|
|
13
13
|
expect(test_data[0]).to be(4)
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
context '#on' do
|
18
|
-
it 'returns a Reacto::Subscription' do
|
19
|
-
actual = described_class.new(Reacto::NO_ACTION).on
|
20
|
-
|
21
|
-
expect(actual).to_not be(nil)
|
22
|
-
expect(actual).to be_kind_of(Reacto::Subscriptions::Subscription)
|
23
|
-
end
|
24
|
-
|
25
|
-
context('value') do
|
26
|
-
it 'the trackable behavior uses a subscription which `on_value` ' \
|
27
|
-
'is the passed value action' do
|
28
|
-
described_class.new(test_behaviour).on(value: test_on_value)
|
29
|
-
|
30
|
-
expect(test_data.size).to be(1)
|
31
|
-
expect(test_data[0]).to be(5)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context '#lift' do
|
37
|
-
it 'applies a transformation to the trackable behaviour' do
|
38
|
-
lifted_trackable = source.lift do |tracker_subscription|
|
39
|
-
Reacto::Subscriptions::OperationSubscription.new(
|
40
|
-
tracker_subscription,
|
41
|
-
value: -> (v) { tracker_subscription.on_value(v * v) }
|
42
|
-
)
|
43
|
-
end
|
44
|
-
|
45
|
-
lifted_trackable.on(value: test_on_value)
|
46
|
-
|
47
|
-
expect(test_data.size).to be(1)
|
48
|
-
expect(test_data[0]).to be(25)
|
49
|
-
end
|
50
|
-
end
|
51
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reacto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nickolay Tzvetinov - Meddle
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,10 +66,13 @@ files:
|
|
66
66
|
- README.md
|
67
67
|
- lib/reacto.rb
|
68
68
|
- lib/reacto/behaviours.rb
|
69
|
+
- lib/reacto/cache/file.rb
|
70
|
+
- lib/reacto/cache/memory.rb
|
69
71
|
- lib/reacto/constants.rb
|
70
72
|
- lib/reacto/executors.rb
|
71
73
|
- lib/reacto/operations.rb
|
72
74
|
- lib/reacto/operations/buffer.rb
|
75
|
+
- lib/reacto/operations/cache.rb
|
73
76
|
- lib/reacto/operations/concat.rb
|
74
77
|
- lib/reacto/operations/diff.rb
|
75
78
|
- lib/reacto/operations/drop.rb
|
@@ -111,7 +114,14 @@ files:
|
|
111
114
|
- spec/reacto/executors_and_trackable_spec.rb
|
112
115
|
- spec/reacto/operations/track_on_spec.rb
|
113
116
|
- spec/reacto/trackable/buffer_spec.rb
|
117
|
+
- spec/reacto/trackable/cache_spec.rb
|
118
|
+
- spec/reacto/trackable/class_level/enumerable_spec.rb
|
119
|
+
- spec/reacto/trackable/class_level/error_spec.rb
|
120
|
+
- spec/reacto/trackable/class_level/interval_spec.rb
|
121
|
+
- spec/reacto/trackable/class_level/later_spec.rb
|
122
|
+
- spec/reacto/trackable/class_level/make_spec.rb
|
114
123
|
- spec/reacto/trackable/class_level/never_spec.rb
|
124
|
+
- spec/reacto/trackable/class_level/value_spec.rb
|
115
125
|
- spec/reacto/trackable/concat_spec.rb
|
116
126
|
- spec/reacto/trackable/diff_spec.rb
|
117
127
|
- spec/reacto/trackable/drop_errors_spec.rb
|
@@ -156,7 +166,14 @@ test_files:
|
|
156
166
|
- spec/reacto/executors_and_trackable_spec.rb
|
157
167
|
- spec/reacto/operations/track_on_spec.rb
|
158
168
|
- spec/reacto/trackable/buffer_spec.rb
|
169
|
+
- spec/reacto/trackable/cache_spec.rb
|
170
|
+
- spec/reacto/trackable/class_level/enumerable_spec.rb
|
171
|
+
- spec/reacto/trackable/class_level/error_spec.rb
|
172
|
+
- spec/reacto/trackable/class_level/interval_spec.rb
|
173
|
+
- spec/reacto/trackable/class_level/later_spec.rb
|
174
|
+
- spec/reacto/trackable/class_level/make_spec.rb
|
159
175
|
- spec/reacto/trackable/class_level/never_spec.rb
|
176
|
+
- spec/reacto/trackable/class_level/value_spec.rb
|
160
177
|
- spec/reacto/trackable/concat_spec.rb
|
161
178
|
- spec/reacto/trackable/diff_spec.rb
|
162
179
|
- spec/reacto/trackable/drop_errors_spec.rb
|