pg_eventstore 1.4.0 → 1.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/pg_eventstore/extensions/callback_handlers_extension.rb +21 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rb +38 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +45 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +103 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +75 -0
- data/lib/pg_eventstore/subscriptions/commands_handler.rb +13 -29
- data/lib/pg_eventstore/subscriptions/events_processor.rb +17 -41
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +77 -125
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +30 -55
- data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +70 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +8 -2
- data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +39 -0
- data/lib/pg_eventstore/utils.rb +8 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +9 -1
- data/lib/pg_eventstore/web/paginator/events_collection.rb +1 -3
- data/lib/pg_eventstore/web/paginator/helpers.rb +4 -1
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +10 -1
- data/lib/pg_eventstore/web/subscriptions/helpers.rb +3 -4
- data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -0
- data/lib/pg_eventstore/web/views/home/partials/events.erb +6 -1
- data/lib/pg_eventstore/web/views/subscriptions/index.erb +2 -2
- data/lib/pg_eventstore.rb +1 -0
- data/sig/interfaces/_raw_event_handler.rbs +3 -0
- data/sig/pg_eventstore/extensions/callback_handlers_extension.rbs +11 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rbs +10 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +11 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +31 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +19 -0
- data/sig/pg_eventstore/subscriptions/events_processor.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +2 -30
- data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +27 -0
- data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rbs +24 -0
- data/sig/pg_eventstore/utils.rbs +2 -0
- data/sig/pg_eventstore/web/subscriptions/helpers.rbs +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec41b398e1f38d66e73967447ea4f12896ed2cf57eb0dbad8b71acf4257f9cd4
|
4
|
+
data.tar.gz: ce571ef411adeeb18b7bae463cac339a053d96f1b36e3de0b1023b8551b03ac5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9273a4efb9855d219b1a92b5d9081e3d0d62f9de7f21c48e634949d218ed6a8278d81367f794ee8b70efa64541d7a4442ccf09e7b4cc3bcbd39398f5c7980ec2
|
7
|
+
data.tar.gz: ffb85f8c9063aa6e11b8f900e128b4b873e89db117b39e703de6603fc73c014811b63950ff0a0340b0316da11ed2562418f56f1faf42a04491fb0105d913fced
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.5.0]
|
4
|
+
- Add ability to toggle link events in the admin UI
|
5
|
+
- Mark linked events in the admin UI with "link" icon
|
6
|
+
|
3
7
|
## [1.4.0]
|
4
8
|
- Add an ability to configure subscription graceful shutdown timeout globally and per subscription. Default value is 15 seconds. Previously it was hardcoded to 5 seconds. Examples:
|
5
9
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Extensions
|
5
|
+
module CallbackHandlersExtension
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# @param name [Symbol] a name of the handler
|
12
|
+
# @return [Proc]
|
13
|
+
def setup_handler(name, *args)
|
14
|
+
proc do |*rest|
|
15
|
+
public_send(name, *args, *rest)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
class CommandsHandlerHandlers
|
5
|
+
include Extensions::CallbackHandlersExtension
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param config_name [Symbol]
|
9
|
+
# @param subscription_feeder [PgEventstore::SubscriptionFeeder]
|
10
|
+
# @return [void]
|
11
|
+
def process_feeder_commands(config_name, subscription_feeder)
|
12
|
+
CommandHandlers::SubscriptionFeederCommands.new(config_name, subscription_feeder).process
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param config_name [Symbol]
|
16
|
+
# @param runners [Array<PgEventstore::SubscriptionRunner>]
|
17
|
+
# @param subscription_feeder [PgEventstore::SubscriptionFeeder]
|
18
|
+
# @return [void]
|
19
|
+
def process_runners_commands(config_name, runners, subscription_feeder)
|
20
|
+
CommandHandlers::SubscriptionRunnersCommands.new(config_name, runners, subscription_feeder.id).process
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param basic_runner [PgEventstore::BasicRunner]
|
24
|
+
# @param restart_delay [Integer]
|
25
|
+
# @param error [StandardError]
|
26
|
+
# @return [void]
|
27
|
+
def restore_runner(basic_runner, restart_delay, error)
|
28
|
+
PgEventstore.logger&.error "PgEventstore::CommandsHandler: Error occurred: #{error.message}"
|
29
|
+
PgEventstore.logger&.error "PgEventstore::CommandsHandler: Backtrace: #{error.backtrace&.join("\n")}"
|
30
|
+
PgEventstore.logger&.error "PgEventstore::CommandsHandler: Trying to auto-repair in #{restart_delay} seconds..."
|
31
|
+
Thread.new do
|
32
|
+
sleep restart_delay
|
33
|
+
basic_runner.restore
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
class EventsProcessorHandlers
|
5
|
+
include Extensions::CallbackHandlersExtension
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param callbacks [PgEventstore::Callbacks]
|
9
|
+
# @param handler [#call]
|
10
|
+
# @param raw_events [Array<Hash>]
|
11
|
+
# @return [void]
|
12
|
+
def process_event(callbacks, handler, raw_events)
|
13
|
+
raw_event = raw_events.shift
|
14
|
+
return sleep 0.5 if raw_event.nil?
|
15
|
+
|
16
|
+
callbacks.run_callbacks(:process, Utils.original_global_position(raw_event)) do
|
17
|
+
handler.call(raw_event)
|
18
|
+
end
|
19
|
+
rescue
|
20
|
+
raw_events.unshift(raw_event)
|
21
|
+
raise
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param callbacks [PgEventstore::Callbacks]
|
25
|
+
# @param error [StandardError]
|
26
|
+
# @return [void]
|
27
|
+
def after_runner_died(callbacks, error)
|
28
|
+
callbacks.run_callbacks(:error, error)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param callbacks [PgEventstore::Callbacks]
|
32
|
+
# @return [void]
|
33
|
+
def before_runner_restored(callbacks)
|
34
|
+
callbacks.run_callbacks(:restart)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param callbacks [PgEventstore::Callbacks]
|
38
|
+
# @param state [String]
|
39
|
+
# @return [void]
|
40
|
+
def change_state(callbacks, state)
|
41
|
+
callbacks.run_callbacks(:change_state, state)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
class SubscriptionFeederHandlers
|
5
|
+
include Extensions::CallbackHandlersExtension
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
9
|
+
# @param state [String]
|
10
|
+
# @return [void]
|
11
|
+
def update_subscriptions_set_state(subscriptions_set_lifecycle, state)
|
12
|
+
subscriptions_set_lifecycle.persisted_subscriptions_set.update(state: state)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
|
16
|
+
# @return [void]
|
17
|
+
def lock_subscriptions(subscriptions_lifecycle)
|
18
|
+
subscriptions_lifecycle.lock_all
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
|
22
|
+
# @return [void]
|
23
|
+
def start_runners(subscriptions_lifecycle)
|
24
|
+
subscriptions_lifecycle.runners.each(&:start)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param cmds_handler [PgEventstore::CommandsHandler]
|
28
|
+
# @return [void]
|
29
|
+
def start_cmds_handler(cmds_handler)
|
30
|
+
cmds_handler.start
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
34
|
+
# @param error [StandardError]
|
35
|
+
# @return [void]
|
36
|
+
def persist_error_info(subscriptions_set_lifecycle, error)
|
37
|
+
subscriptions_set_lifecycle.persisted_subscriptions_set.update(
|
38
|
+
last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
43
|
+
# @param basic_runner [PgEventstore::BasicRunner]
|
44
|
+
# @param _error [StandardError]
|
45
|
+
# @return [void]
|
46
|
+
def restart_runner(subscriptions_set_lifecycle, basic_runner, _error)
|
47
|
+
subscriptions_set = subscriptions_set_lifecycle.persisted_subscriptions_set
|
48
|
+
return if subscriptions_set.restart_count >= subscriptions_set.max_restarts_number
|
49
|
+
|
50
|
+
Thread.new do
|
51
|
+
sleep subscriptions_set.time_between_restarts
|
52
|
+
basic_runner.restore
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
57
|
+
# @return [void]
|
58
|
+
def ping_subscriptions_set(subscriptions_set_lifecycle)
|
59
|
+
subscriptions_set_lifecycle.ping_subscriptions_set
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
|
63
|
+
# @param config_name [Symbol]
|
64
|
+
# @return [void]
|
65
|
+
def feed_runners(subscriptions_lifecycle, config_name)
|
66
|
+
SubscriptionRunnersFeeder.new(config_name).feed(subscriptions_lifecycle.runners)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
|
70
|
+
# @return [void]
|
71
|
+
def ping_subscriptions(subscriptions_lifecycle)
|
72
|
+
subscriptions_lifecycle.ping_subscriptions
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
|
76
|
+
# @return [void]
|
77
|
+
def stop_runners(subscriptions_lifecycle)
|
78
|
+
subscriptions_lifecycle.runners.each(&:stop_async).each(&:wait_for_finish)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
82
|
+
# @return [void]
|
83
|
+
def reset_subscriptions_set(subscriptions_set_lifecycle)
|
84
|
+
subscriptions_set_lifecycle.reset_subscriptions_set
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param cmds_handler [PgEventstore::CommandsHandler]
|
88
|
+
# @return [void]
|
89
|
+
def stop_commands_handler(cmds_handler)
|
90
|
+
cmds_handler.stop
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
|
94
|
+
# @return [void]
|
95
|
+
def update_subscriptions_set_restarts(subscriptions_set_lifecycle)
|
96
|
+
subscriptions_set = subscriptions_set_lifecycle.persisted_subscriptions_set
|
97
|
+
subscriptions_set.update(
|
98
|
+
last_restarted_at: Time.now.utc, restart_count: subscriptions_set.restart_count + 1
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
class SubscriptionRunnerHandlers
|
5
|
+
include Extensions::CallbackHandlersExtension
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param stats [PgEventstore::SubscriptionHandlerPerformance]
|
9
|
+
# @param action [Proc]
|
10
|
+
# @param _current_position [Integer]
|
11
|
+
# @return [void]
|
12
|
+
def track_exec_time(stats, action, _current_position)
|
13
|
+
stats.track_exec_time { action.call }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param subscription [PgEventstore::Subscription]
|
17
|
+
# @param stats [PgEventstore::SubscriptionHandlerPerformance]
|
18
|
+
# @param current_position [Integer]
|
19
|
+
# @return [void]
|
20
|
+
def update_subscription_stats(subscription, stats, current_position)
|
21
|
+
subscription.update(
|
22
|
+
average_event_processing_time: stats.average_event_processing_time,
|
23
|
+
current_position: current_position,
|
24
|
+
total_processed_events: subscription.total_processed_events + 1
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param subscription [PgEventstore::Subscription]
|
29
|
+
# @param error [StandardError]
|
30
|
+
# @return [void]
|
31
|
+
def update_subscription_error(subscription, error)
|
32
|
+
subscription.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param subscription [PgEventstore::Subscription]
|
36
|
+
# @param restart_terminator [#call, nil]
|
37
|
+
# @param failed_subscription_notifier [#call, nil]
|
38
|
+
# @param events_processor [PgEventstore::EventsProcessor]
|
39
|
+
# @param error [StandardError]
|
40
|
+
# @return [void]
|
41
|
+
def restart_events_processor(subscription, restart_terminator, failed_subscription_notifier, events_processor,
|
42
|
+
error)
|
43
|
+
return if restart_terminator&.call(subscription.dup)
|
44
|
+
if subscription.restart_count >= subscription.max_restarts_number
|
45
|
+
return failed_subscription_notifier&.call(subscription.dup, error)
|
46
|
+
end
|
47
|
+
|
48
|
+
Thread.new do
|
49
|
+
sleep subscription.time_between_restarts
|
50
|
+
events_processor.restore
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param subscription [PgEventstore::Subscription]
|
55
|
+
# @param global_position [Integer]
|
56
|
+
# @return [void]
|
57
|
+
def update_subscription_chunk_stats(subscription, global_position)
|
58
|
+
subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param subscription [PgEventstore::Subscription]
|
62
|
+
# @return [void]
|
63
|
+
def update_subscription_restarts(subscription)
|
64
|
+
subscription.update(last_restarted_at: Time.now.utc, restart_count: subscription.restart_count + 1)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param subscription [PgEventstore::Subscription]
|
68
|
+
# @param state [String]
|
69
|
+
# @return [void]
|
70
|
+
def update_subscription_state(subscription, state)
|
71
|
+
subscription.update(state: state)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -31,35 +31,19 @@ module PgEventstore
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def attach_runner_callbacks
|
34
|
-
@basic_runner.define_callback(
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
PgEventstore.logger&.error "#{self.class.name}: Backtrace: #{error.backtrace&.join("\n")}"
|
48
|
-
PgEventstore.logger&.error "#{self.class.name}: Trying to auto-repair in #{RESTART_DELAY} seconds..."
|
49
|
-
Thread.new do
|
50
|
-
sleep RESTART_DELAY
|
51
|
-
@basic_runner.restore
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# @return [PgEventstore::CommandHandlers::SubscriptionFeederCommands]
|
56
|
-
def subscription_feeder_commands
|
57
|
-
CommandHandlers::SubscriptionFeederCommands.new(@config_name, @subscription_feeder)
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return [PgEventstore::CommandHandlers::SubscriptionRunnersCommands]
|
61
|
-
def subscription_runners_commands
|
62
|
-
CommandHandlers::SubscriptionRunnersCommands.new(@config_name, @runners, @subscription_feeder.id)
|
34
|
+
@basic_runner.define_callback(
|
35
|
+
:process_async, :before,
|
36
|
+
CommandsHandlerHandlers.setup_handler(:process_feeder_commands, @config_name, @subscription_feeder)
|
37
|
+
)
|
38
|
+
@basic_runner.define_callback(
|
39
|
+
:process_async, :before,
|
40
|
+
CommandsHandlerHandlers.setup_handler(:process_runners_commands, @config_name, @runners, @subscription_feeder)
|
41
|
+
)
|
42
|
+
|
43
|
+
@basic_runner.define_callback(
|
44
|
+
:after_runner_died, :before,
|
45
|
+
CommandsHandlerHandlers.setup_handler(:restore_runner, @basic_runner, RESTART_DELAY)
|
46
|
+
)
|
63
47
|
end
|
64
48
|
end
|
65
49
|
end
|
@@ -26,7 +26,7 @@ module PgEventstore
|
|
26
26
|
raise EmptyChunkFedError.new("Empty chunk was fed!") if raw_events.empty?
|
27
27
|
|
28
28
|
within_state(:running) do
|
29
|
-
callbacks.run_callbacks(:feed,
|
29
|
+
callbacks.run_callbacks(:feed, Utils.original_global_position(raw_events.last))
|
30
30
|
@raw_events.push(*raw_events)
|
31
31
|
end
|
32
32
|
end
|
@@ -44,47 +44,23 @@ module PgEventstore
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
# @param raw_event [Hash]
|
48
|
-
# @return [void]
|
49
|
-
def process_event(raw_event)
|
50
|
-
callbacks.run_callbacks(:process, global_position(raw_event)) do
|
51
|
-
@handler.call(raw_event)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
47
|
def attach_runner_callbacks
|
56
|
-
@basic_runner.define_callback(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
def after_runner_died(...)
|
73
|
-
callbacks.run_callbacks(:error, ...)
|
74
|
-
end
|
75
|
-
|
76
|
-
def before_runner_restored
|
77
|
-
callbacks.run_callbacks(:restart)
|
78
|
-
end
|
79
|
-
|
80
|
-
def change_state(...)
|
81
|
-
callbacks.run_callbacks(:change_state, ...)
|
82
|
-
end
|
83
|
-
|
84
|
-
# @param raw_event [Hash]
|
85
|
-
# @return [Integer]
|
86
|
-
def global_position(raw_event)
|
87
|
-
raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
|
48
|
+
@basic_runner.define_callback(
|
49
|
+
:process_async, :before,
|
50
|
+
EventsProcessorHandlers.setup_handler(:process_event, @callbacks, @handler, @raw_events)
|
51
|
+
)
|
52
|
+
|
53
|
+
@basic_runner.define_callback(
|
54
|
+
:after_runner_died, :before, EventsProcessorHandlers.setup_handler(:after_runner_died, callbacks)
|
55
|
+
)
|
56
|
+
|
57
|
+
@basic_runner.define_callback(
|
58
|
+
:before_runner_restored, :before, EventsProcessorHandlers.setup_handler(:before_runner_restored, callbacks)
|
59
|
+
)
|
60
|
+
|
61
|
+
@basic_runner.define_callback(
|
62
|
+
:change_state, :before, EventsProcessorHandlers.setup_handler(:change_state, callbacks)
|
63
|
+
)
|
88
64
|
end
|
89
65
|
end
|
90
66
|
end
|