|
2 | 2 |
|
3 | 3 | module RubyEventStore |
4 | 4 | class Projection |
5 | | - private_class_method :new |
| 5 | + ANONYMOUS_CLASS = "#<Class:".freeze |
6 | 6 |
|
7 | | - def self.from_stream(stream_or_streams) |
8 | | - streams = Array(stream_or_streams) |
9 | | - raise(ArgumentError, "At least one stream must be given") if streams.empty? |
10 | | - new(streams: streams) |
11 | | - end |
12 | | - |
13 | | - def self.from_all_streams |
14 | | - new |
15 | | - end |
16 | | - |
17 | | - def initialize(streams: []) |
18 | | - @streams = streams |
| 7 | + def initialize(initial_state = nil) |
19 | 8 | @handlers = {} |
20 | | - @init = -> { {} } |
| 9 | + @init = -> { initial_state } |
21 | 10 | end |
22 | 11 |
|
23 | | - attr_reader :streams, :handlers |
24 | | - |
25 | | - def init(handler) |
26 | | - @init = handler |
27 | | - self |
28 | | - end |
| 12 | + def on(*event_klasses, &block) |
| 13 | + raise(ArgumentError, 'No handler block given') unless block_given? |
29 | 14 |
|
30 | | - def when(events, handler) |
31 | | - Array(events).each { |event| handlers[event.to_s] = handler } |
| 15 | + event_klasses.each do |event_klass| |
| 16 | + name = event_klass.to_s |
| 17 | + raise(ArgumentError, 'Anonymous class is missing name') if name.start_with? ANONYMOUS_CLASS |
32 | 18 |
|
| 19 | + @handlers[name] = ->(state, event) { block.call(state, event) } |
| 20 | + end |
33 | 21 | self |
34 | 22 | end |
35 | 23 |
|
36 | | - def initial_state |
37 | | - @init.call |
38 | | - end |
39 | | - |
40 | | - def current_state |
41 | | - @current_state ||= initial_state |
42 | | - end |
43 | | - |
44 | | - def call(event) |
45 | | - handlers.fetch(event.event_type).(current_state, event) |
46 | | - end |
47 | | - |
48 | | - def handled_events |
49 | | - handlers.keys |
50 | | - end |
51 | | - |
52 | | - def run(event_store, start: nil, count: PAGE_SIZE) |
| 24 | + def call(*scopes) |
53 | 25 | return initial_state if handled_events.empty? |
54 | | - streams.any? ? reduce_from_streams(event_store, start, count) : reduce_from_all_streams(event_store, start, count) |
55 | | - end |
56 | | - |
57 | | - private |
58 | 26 |
|
59 | | - def valid_starting_point?(start) |
60 | | - return true unless start |
61 | | - streams.any? ? (start.instance_of?(Array) && start.size === streams.size) : start.instance_of?(String) |
| 27 | + scopes.reduce(initial_state) do |state, scope| |
| 28 | + scope.of_types(handled_events).reduce(state, &method(:transition)) |
| 29 | + end |
62 | 30 | end |
63 | 31 |
|
64 | | - def reduce_from_streams(event_store, start, count) |
65 | | - raise ArgumentError.new("Start must be an array with event ids") unless valid_starting_point?(start) |
66 | | - streams |
67 | | - .zip(start_events(start)) |
68 | | - .reduce(initial_state) do |state, (stream_name, start_event_id)| |
69 | | - read_scope(event_store, stream_name, count, start_event_id).reduce(state, &method(:transition)) |
70 | | - end |
71 | | - end |
72 | | - |
73 | | - def reduce_from_all_streams(event_store, start, count) |
74 | | - raise ArgumentError.new("Start must be valid event id") unless valid_starting_point?(start) |
75 | | - read_scope(event_store, nil, count, start).reduce(initial_state, &method(:transition)) |
76 | | - end |
| 32 | + private |
77 | 33 |
|
78 | | - def read_scope(event_store, stream, count, start) |
79 | | - scope = event_store.read.in_batches(count) |
80 | | - scope = scope.of_type(handled_events) |
81 | | - scope = scope.stream(stream) if stream |
82 | | - scope = scope.from(start) if start |
83 | | - scope |
| 34 | + def initial_state |
| 35 | + @init.call |
84 | 36 | end |
85 | 37 |
|
86 | | - def start_events(start) |
87 | | - start ? start : Array.new |
| 38 | + def handled_events |
| 39 | + @handlers.keys |
88 | 40 | end |
89 | 41 |
|
90 | 42 | def transition(state, event) |
91 | | - handlers.fetch(event.event_type).call(state, event) |
92 | | - state |
| 43 | + @handlers.fetch(event.event_type).call(state, event) |
93 | 44 | end |
94 | 45 | end |
95 | 46 | end |
0 commit comments