reacto 0.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +32 -0
  5. data/README.md +1 -0
  6. data/lib/reacto/behaviours.rb +50 -0
  7. data/lib/reacto/constants.rb +7 -0
  8. data/lib/reacto/executors.rb +31 -0
  9. data/lib/reacto/operations/buffer.rb +80 -0
  10. data/lib/reacto/operations/concat.rb +23 -0
  11. data/lib/reacto/operations/diff.rb +36 -0
  12. data/lib/reacto/operations/drop.rb +41 -0
  13. data/lib/reacto/operations/drop_errors.rb +15 -0
  14. data/lib/reacto/operations/flat_map.rb +26 -0
  15. data/lib/reacto/operations/flatten.rb +26 -0
  16. data/lib/reacto/operations/inject.rb +43 -0
  17. data/lib/reacto/operations/last.rb +31 -0
  18. data/lib/reacto/operations/map.rb +38 -0
  19. data/lib/reacto/operations/merge.rb +47 -0
  20. data/lib/reacto/operations/prepend.rb +19 -0
  21. data/lib/reacto/operations/select.rb +25 -0
  22. data/lib/reacto/operations/take.rb +38 -0
  23. data/lib/reacto/operations/throttle.rb +60 -0
  24. data/lib/reacto/operations/track_on.rb +18 -0
  25. data/lib/reacto/operations/uniq.rb +22 -0
  26. data/lib/reacto/operations.rb +23 -0
  27. data/lib/reacto/resources/executor_resource.rb +19 -0
  28. data/lib/reacto/resources.rb +1 -0
  29. data/lib/reacto/subscriptions/buffered_subscription.rb +52 -0
  30. data/lib/reacto/subscriptions/combining_last_subscription.rb +22 -0
  31. data/lib/reacto/subscriptions/combining_subscription.rb +8 -0
  32. data/lib/reacto/subscriptions/composite_subscription.rb +82 -0
  33. data/lib/reacto/subscriptions/executor_subscription.rb +63 -0
  34. data/lib/reacto/subscriptions/flat_map_subscription.rb +20 -0
  35. data/lib/reacto/subscriptions/inner_subscription.rb +47 -0
  36. data/lib/reacto/subscriptions/operation_subscription.rb +25 -0
  37. data/lib/reacto/subscriptions/simple_subscription.rb +80 -0
  38. data/lib/reacto/subscriptions/subscription.rb +21 -0
  39. data/lib/reacto/subscriptions/subscription_wrapper.rb +16 -0
  40. data/lib/reacto/subscriptions/tracker_subscription.rb +76 -0
  41. data/lib/reacto/subscriptions/zipping_subscription.rb +43 -0
  42. data/lib/reacto/subscriptions.rb +27 -0
  43. data/lib/reacto/trackable.rb +317 -0
  44. data/lib/reacto/tracker.rb +33 -0
  45. data/lib/reacto/version.rb +4 -0
  46. data/lib/reacto.rb +10 -0
  47. data/reacto.gemspec +27 -0
  48. data/spec/reacto/create_trackable_spec.rb +245 -0
  49. data/spec/reacto/executors_and_trackable_spec.rb +32 -0
  50. data/spec/reacto/operations/track_on_spec.rb +20 -0
  51. data/spec/reacto/trackable/buffer_spec.rb +58 -0
  52. data/spec/reacto/trackable/throttle_spec.rb +15 -0
  53. data/spec/reacto/trackable/zip_spec.rb +25 -0
  54. data/spec/reacto/trackable_spec.rb +417 -0
  55. data/spec/spec_helper.rb +15 -0
  56. data/spec/support/helpers.rb +16 -0
  57. metadata +150 -0
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
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
+ subject do
10
+ described_class.new do |tracker_subscription|
11
+ tracker_subscription.on_value(16)
12
+ sleep 1
13
+
14
+ tracker_subscription.on_value(7)
15
+ sleep 2
16
+
17
+ tracker_subscription.on_value(2014)
18
+ sleep 3
19
+
20
+ tracker_subscription.on_close
21
+ end
22
+ end
23
+
24
+ context '#track_on' do
25
+ it 'executes the trackable login on the passed executor' do
26
+ subject
27
+ .execute_on(Reacto::Executors.io)
28
+ .map(-> (v) { v * 2 })
29
+ .on(value: test_on_value, close: test_on_close)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reacto::Operations::TrackOn do
4
+
5
+ subject do
6
+ described_class.new(Reacto::Executors.immediate)
7
+ end
8
+
9
+ context '#call' do
10
+ it 'returns a special ExecutorSubscription' do
11
+ subscription = subject.call(Reacto::Tracker.new)
12
+
13
+ expect(subscription).to(
14
+ be_instance_of(Reacto::Subscriptions::ExecutorSubscription)
15
+ )
16
+ end
17
+ end
18
+
19
+ end
20
+
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ context Reacto::Trackable do
4
+ context '#buffer' do
5
+ context 'count' do
6
+ it 'sends values on batches with the size of the passed count' do
7
+ trackable = described_class.enumerable((1..20)).buffer(count: 5)
8
+ trackable.on(
9
+ value: test_on_value, close: test_on_close, error: test_on_error
10
+ )
11
+
12
+ expect(test_data).to be ==
13
+ [(1..5).to_a, (6..10).to_a, (11..15).to_a, (16..20).to_a, '|']
14
+ end
15
+ end
16
+
17
+ context 'delay' do
18
+ it 'sends values on batches on intervals - the passed delay' do
19
+ trackable = described_class.interval(0.1).take(20).buffer(delay: 0.5)
20
+ subscription = trackable.on(
21
+ value: test_on_value, close: test_on_close, error: test_on_error
22
+ )
23
+ trackable.await(subscription)
24
+ expect(test_data).to be ==
25
+ [
26
+ [0, 1, 2, 3],
27
+ [4, 5, 6, 7, 8],
28
+ [9, 10, 11, 12, 13],
29
+ [14, 15, 16, 17, 18],
30
+ [19],
31
+ "|"
32
+ ]
33
+ end
34
+ end
35
+
36
+ context 'count & delay' do
37
+ it 'uses either the count or the delay to buffer and send' do
38
+ trackable = described_class.make do |subscriber|
39
+ subscriber.on_value(1)
40
+ subscriber.on_value(2)
41
+ subscriber.on_value(3)
42
+ subscriber.on_value(4)
43
+ subscriber.on_value(5)
44
+ sleep 1
45
+ subscriber.on_value(6)
46
+ subscriber.on_close
47
+ end.buffer(delay: 0.5, count: 3)
48
+
49
+ trackable.on(
50
+ value: test_on_value, close: test_on_close, error: test_on_error
51
+ )
52
+ expect(test_data).to be ==
53
+ [[1, 2, 3], [4, 5], [6], '|']
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ context Reacto::Trackable do
4
+ context '#throttle' do
5
+ it 'emits only the last values received after a given timeout' do
6
+ trackable = described_class.interval(0.1).take(30).throttle(0.5)
7
+ subscription = trackable.on(
8
+ value: test_on_value, close: test_on_close, error: test_on_error
9
+ )
10
+ trackable.await(subscription)
11
+ expect(test_data).to be == [4, 9, 14, 19, 24, 29, "|"]
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ context Reacto::Trackable do
4
+ context '.zip' do
5
+ it 'combines the first notifications of the source Trackable instances, ' \
6
+ 'then the second ones, then the third ones and etc. until some of ' \
7
+ 'the sources closes' do
8
+
9
+ trackable1 = described_class.interval(0.3).drop(1).take(4)
10
+ trackable2 = described_class.interval(0.7, ('a'..'b').each)
11
+ trackable3 = described_class.interval(0.5, ('A'..'C').each)
12
+
13
+ trackable = described_class.zip(
14
+ trackable1, trackable2, trackable3
15
+ ) do |v1, v2, v3|
16
+ "#{v1} : #{v2} : #{v3}"
17
+ end
18
+
19
+ subscription = attach_test_trackers(trackable)
20
+ trackable.await(subscription)
21
+
22
+ expect(test_data).to be == ['1 : a : A', '2 : b : B', '|']
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,417 @@
1
+ require 'spec_helper'
2
+
3
+ context Reacto::Trackable do
4
+ let(:test_data) { [] }
5
+ let(:test_on_value) { -> (v) { test_data << v }}
6
+ let(:test_on_error) { -> (e) { test_data << e }}
7
+ let(:test_on_close) { -> () { test_data << '|' }}
8
+
9
+ let(:test_behaviour) do
10
+ lambda do |tracker_subscription|
11
+ tracker_subscription.on_value(5)
12
+ tracker_subscription.on_close
13
+ end
14
+ end
15
+
16
+ let(:source) { described_class.new(test_behaviour) }
17
+
18
+ context '.new' do
19
+ it 'supports behavior invoked on tracking, passed as block' do
20
+ trackable = described_class.new do |tracker_subscription|
21
+ tracker_subscription.on_value(4)
22
+ tracker_subscription.on_close
23
+ end
24
+
25
+ trackable.on(value: test_on_value)
26
+ expect(test_data.size).to be(1)
27
+ expect(test_data[0]).to be(4)
28
+ end
29
+ end
30
+
31
+ context '#on' do
32
+ it 'returns a Reacto::Subscription' do
33
+ actual = described_class.new(Reacto::NO_ACTION).on
34
+
35
+ expect(actual).to_not be(nil)
36
+ expect(actual).to be_kind_of(Reacto::Subscriptions::Subscription)
37
+ end
38
+
39
+ context('value') do
40
+ it 'the trackable behavior uses a subscription which `on_value` ' \
41
+ 'is the passed value action' do
42
+ described_class.new(test_behaviour).on(value: test_on_value)
43
+
44
+ expect(test_data.size).to be(1)
45
+ expect(test_data[0]).to be(5)
46
+ end
47
+ end
48
+ end
49
+
50
+ context '#lift' do
51
+ it 'applies a transformation to the trackable behaviour' do
52
+ lifted_trackable = source.lift do |tracker_subscription|
53
+ Reacto::Subscriptions::OperationSubscription.new(
54
+ tracker_subscription,
55
+ value: -> (v) { tracker_subscription.on_value(v * v) }
56
+ )
57
+ end
58
+
59
+ lifted_trackable.on(value: test_on_value)
60
+
61
+ expect(test_data.size).to be(1)
62
+ expect(test_data[0]).to be(25)
63
+ end
64
+ end
65
+
66
+ context '#map' do
67
+ it 'transforms the value of the source Trackable using the passed ' \
68
+ 'transformation' do
69
+ trackable = source.map do |v|
70
+ v * v * v
71
+ end
72
+
73
+ trackable.on(value: test_on_value)
74
+
75
+ expect(test_data.size).to be(1)
76
+ expect(test_data[0]).to be(125)
77
+ end
78
+
79
+ it 'transforms errors, if error transformation is passed' do
80
+ source = described_class.make do |subscriber|
81
+ subscriber.on_value(4)
82
+ subscriber.on_error(StandardError.new('error'))
83
+ end
84
+ trackable = source.map(-> (v) { v }, error: -> (e) { 5 })
85
+
86
+ trackable.on(value: test_on_value, error: test_on_error)
87
+
88
+ expect(test_data.size).to be(2)
89
+ expect(test_data).to be == [4, 5]
90
+ end
91
+
92
+ it 'emits what is produced by the passed `close` function before close' do
93
+ trackable =
94
+ described_class.enumerable((1..5)).map(close: ->() { 10 }) do |v|
95
+ v
96
+ end
97
+
98
+ trackable.on(value: test_on_value, close: test_on_close)
99
+ expect(test_data).to be == (1..5).to_a + [10, '|']
100
+ end
101
+ end
102
+
103
+ context '#select' do
104
+ it 'doesn\'t notify with values not passing the filter block' do
105
+ trackable = source.select do |v|
106
+ v % 2 == 0
107
+ end
108
+
109
+ trackable.on(value: test_on_value)
110
+
111
+ expect(test_data.size).to be(0)
112
+ end
113
+
114
+ it 'notifies with values passing the filter block' do
115
+ trackable = source.select do |v|
116
+ v % 2 == 1
117
+ end
118
+
119
+ trackable.on(value: test_on_value)
120
+
121
+ expect(test_data.size).to be(1)
122
+ expect(test_data[0]).to be(5)
123
+ end
124
+ end
125
+
126
+ context '#inject' do
127
+ let(:test_behaviour) do
128
+ lambda do |tracker_subscription|
129
+ [16, 7, 2014].each do |value|
130
+ tracker_subscription.on_value(value)
131
+ end
132
+
133
+ tracker_subscription.on_close
134
+ end
135
+ end
136
+
137
+ it 'sends the values created by applying the `inject` operation on the ' \
138
+ 'last value and current value, using for first value the initial one' do
139
+ trackable = source.inject(0) do |prev, v|
140
+ prev + v
141
+ end
142
+ trackable.on(value: test_on_value)
143
+
144
+ expect(test_data.size).to be(3)
145
+ expect(test_data).to be == [16, 23, 2037]
146
+ end
147
+
148
+ it 'sends the values created by applying the `inject` operation on the ' \
149
+ 'last value and current value, using for first value ' \
150
+ 'the first emitted by the source if no initial value provided' do
151
+ trackable = source.inject do |prev, v|
152
+ prev + v
153
+ end
154
+ trackable.on(value: test_on_value)
155
+
156
+ expect(test_data.size).to be(3)
157
+ expect(test_data).to be == [16, 23, 2037]
158
+ end
159
+
160
+ it 'sends the initial value if no value is emitted' do
161
+ source = described_class.new(-> (t) { t.on_close })
162
+ trackable = source.inject(0) do |prev, v|
163
+ prev + v
164
+ end
165
+ trackable.on(value: test_on_value)
166
+
167
+ expect(test_data.size).to be(1)
168
+ expect(test_data).to be == [0]
169
+ end
170
+
171
+ it 'sends nothing if no initial value and no value emitted' do
172
+ source = described_class.new(-> (t) { t.on_close })
173
+ trackable = source.inject do |prev, v|
174
+ prev + v
175
+ end
176
+ trackable.on(value: test_on_value)
177
+
178
+ expect(test_data.size).to be(0)
179
+ end
180
+ end
181
+
182
+ context '#diff' do
183
+ it 'by default emits arrays with two values the - previous and current ' \
184
+ 'element' do
185
+ source = described_class.enumerable((1..10))
186
+ trackable = source.diff
187
+ trackable.on(value: test_on_value, error: test_on_error)
188
+
189
+ expect(test_data).to be == [
190
+ [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10
191
+ ]]
192
+ end
193
+
194
+ it 'can be passed a diff function to calculate the difference between ' \
195
+ 'the previously emitted value and the current and to emit it' do
196
+ source = described_class.enumerable((1..10))
197
+ trackable = source.diff(Reacto::NO_VALUE, -> (p, c) { c - p })
198
+ trackable.on(value: test_on_value, error: test_on_error)
199
+
200
+ expect(test_data).to be == [1] * 9
201
+ end
202
+
203
+ it 'can be passed a diff block to calculate the difference between ' \
204
+ 'the previously emitted value and the current and to emit it' do
205
+ source = described_class.enumerable((1..10))
206
+ trackable = source.diff { |p, c| c - p }
207
+ trackable.on(value: test_on_value, error: test_on_error)
208
+
209
+ expect(test_data).to be == [1] * 9
210
+ end
211
+
212
+ it 'can receive initial value to be used as seed - the first value' do
213
+ source = described_class.enumerable((1..10))
214
+ trackable = source.diff(-5) { |p, c| c - p }
215
+ trackable.on(value: test_on_value, error: test_on_error)
216
+
217
+ expect(test_data).to be == [6] + ([1] * 9)
218
+ end
219
+ end
220
+
221
+ context '#prepend' do
222
+ it 'emits the passed enumerable before the values, emited by the caller' do
223
+ source = described_class.enumerable((1..5))
224
+ trackable = source.prepend((-5..0))
225
+
226
+ trackable.on(value: test_on_value)
227
+
228
+ expect(test_data.size).to be(11)
229
+ expect(test_data).to be == (-5..5).to_a
230
+ end
231
+ end
232
+
233
+ context '#drop_errors' do
234
+ it 'drops all the errors from the source and continues' do
235
+ described_class.enumerable((1..5)).concat(
236
+ described_class.error(StandardError.new)
237
+ ).concat(described_class.enumerable(6..10)).drop_errors.on(
238
+ value: test_on_value, error: test_on_error, close: test_on_close
239
+ )
240
+
241
+ expect(test_data).to be == (1..10).to_a + ['|']
242
+ end
243
+ end
244
+
245
+ context '#drop, #take, #last, #first, #[]' do
246
+ let(:test_behaviour) do
247
+ lambda do |tracker_subscription|
248
+ (1..15).each do |value|
249
+ tracker_subscription.on_value(value)
250
+ end
251
+
252
+ tracker_subscription.on_close
253
+ end
254
+ end
255
+
256
+ context('#drop') do
257
+ it 'drops the first `n` values sent by the source' do
258
+ source.drop(6).on(value: test_on_value)
259
+
260
+ expect(test_data.size).to be(9)
261
+ expect(test_data).to be == (7..15).to_a
262
+ end
263
+ end
264
+
265
+ context('#take') do
266
+ it 'sents only the first `n` values sent by the source' do
267
+ source.take(6).on(value: test_on_value)
268
+
269
+ expect(test_data.size).to be(6)
270
+ expect(test_data).to be == (1..6).to_a
271
+ end
272
+ end
273
+
274
+ context('#last') do
275
+ it 'emits only the last value of the source and the closing ' \
276
+ 'notification' do
277
+ source.last.on(value: test_on_value, close: test_on_close)
278
+
279
+ expect(test_data).to be == [15, '|']
280
+ end
281
+
282
+ it 'only closes if no value was emitted by the source' do
283
+ described_class.close.last.on(
284
+ value: test_on_value, close: test_on_close
285
+ )
286
+
287
+ expect(test_data).to be == ['|']
288
+ end
289
+
290
+ it 'emits the last value before the error and the error when error ' \
291
+ 'notification is received from the source' do
292
+ err = StandardError.new('Hey!')
293
+ source.concat(described_class.error(err)).last.on(
294
+ value: test_on_value, close: test_on_close, error: test_on_error
295
+ )
296
+
297
+ expect(test_data).to be == [15, err]
298
+ end
299
+ end
300
+
301
+ context('#first') do
302
+ it 'emits only the first value of the source and closes' do
303
+ source.first.on(
304
+ value: test_on_value, close: test_on_close
305
+ )
306
+
307
+ expect(test_data).to be == [1, '|']
308
+ end
309
+
310
+ it 'only closes if no value was emitted by the source' do
311
+ described_class.close.first.on(
312
+ value: test_on_value, close: test_on_close
313
+ )
314
+
315
+ expect(test_data).to be == ['|']
316
+ end
317
+ end
318
+
319
+ context('#[]') do
320
+ it 'emits only the n-th value of the source and closes' do
321
+ source[4].on(
322
+ value: test_on_value, close: test_on_close
323
+ )
324
+
325
+ expect(test_data).to be == [5, '|']
326
+ end
327
+
328
+ it 'just closes if no value was emitted by the source' do
329
+ described_class.close[3].on(
330
+ value: test_on_value, close: test_on_close
331
+ )
332
+
333
+ expect(test_data).to be == ['|']
334
+ end
335
+ end
336
+ end
337
+
338
+ context '#concat' do
339
+ it 'starts emitting the values from the concatenated after emitting the ' \
340
+ 'values from the source, then emits a `close` notification' do
341
+ trackable = source.concat(described_class.enumerable((6..10)))
342
+ trackable.on(value: test_on_value, close: test_on_close)
343
+
344
+ expect(test_data).to be == (5..10).to_a + ['|']
345
+ end
346
+
347
+ it 'can be chained' do
348
+ trackable = source
349
+ .concat(described_class.enumerable((6..8)))
350
+ .concat(described_class.enumerable((9..10)))
351
+ trackable.on(value: test_on_value, close: test_on_close)
352
+
353
+ expect(test_data).to be == (5..10).to_a + ['|']
354
+ end
355
+
356
+ it 'closes on error' do
357
+ err = StandardError.new('Hey')
358
+ trackable = source
359
+ .concat(described_class.error(err))
360
+ .concat(described_class.enumerable((9..10)))
361
+ trackable.on(
362
+ value: test_on_value, close: test_on_close, error: test_on_error
363
+ )
364
+
365
+ expect(test_data).to be == [5, err]
366
+ end
367
+
368
+ context '#merge' do
369
+ it 'merges the passed trackable\'s emitions with the source ones' do
370
+ trackable =
371
+ described_class.interval(0.2).map { |v| v.to_s + 'a'}.take(5)
372
+ to_be_merged =
373
+ described_class.interval(0.35).map { |v| v.to_s + 'b'}.take(4)
374
+ subscription = trackable.merge(to_be_merged).on(
375
+ value: test_on_value, close: test_on_close, error: test_on_error
376
+ )
377
+ trackable.await(subscription)
378
+
379
+ expect(test_data).to be ==
380
+ ["0a", "0b", "1a", "2a", "1b", "3a", "4a", "2b", "3b", "|"]
381
+ end
382
+
383
+ it 'finishes with the error if `delay_error` is true' do
384
+ err = StandardError.new('Hey')
385
+ trackable = described_class.interval(0.2).map do |v|
386
+ raise err if v == 3
387
+ v.to_s + 'a'
388
+ end.take(5)
389
+
390
+ to_be_merged =
391
+ described_class.interval(0.35).map { |v| v.to_s + 'b'}.take(4)
392
+
393
+ trackable = trackable.merge(to_be_merged, delay_error: true)
394
+ subscription = trackable.on(
395
+ value: test_on_value, close: test_on_close, error: test_on_error
396
+ )
397
+ trackable.await(subscription, 2)
398
+
399
+ expect(test_data).to be ==
400
+ ["0a", "0b", "1a", "2a", "1b", "2b", "3b", err]
401
+
402
+ end
403
+ end
404
+
405
+ context 'uniq' do
406
+ it 'sends only uniq values, dropping the repeating ones' do
407
+ trackable =
408
+ described_class.enumerable([1, 2, 3, 2, 4, 3, 2, 1, 5]).uniq
409
+
410
+ trackable.on(
411
+ value: test_on_value, close: test_on_close, error: test_on_error
412
+ )
413
+ expect(test_data).to be == [1, 2, 3, 4, 5, '|']
414
+ end
415
+ end
416
+ end
417
+ end
@@ -0,0 +1,15 @@
1
+ require 'reacto'
2
+
3
+ require_relative './support/helpers'
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+
10
+ config.mock_with :rspec do |mocks|
11
+ mocks.verify_partial_doubles = true
12
+ end
13
+ config.include Helpers
14
+ end
15
+
@@ -0,0 +1,16 @@
1
+ module Helpers
2
+ extend RSpec::SharedContext
3
+
4
+ let(:test_data) { [] }
5
+ let(:test_on_value) { -> (v) { test_data << v }}
6
+ let(:test_on_close) { -> () { test_data << '|' }}
7
+ let(:test_on_error) { -> (e) { test_data << e }}
8
+
9
+ def attach_test_trackers(trackable)
10
+ trackable.on(
11
+ value: test_on_value,
12
+ error: test_on_error,
13
+ close: test_on_close
14
+ )
15
+ end
16
+ end