nxt_state_machine 0.1.8 → 0.1.9
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 +9 -0
- data/Gemfile.lock +20 -20
- data/README.md +18 -5
- data/lib/nxt_state_machine.rb +4 -5
- data/lib/nxt_state_machine/callback_registry.rb +5 -5
- data/lib/nxt_state_machine/defuse_registry.rb +3 -3
- data/lib/nxt_state_machine/error_callback_registry.rb +2 -2
- data/lib/nxt_state_machine/event.rb +3 -2
- data/lib/nxt_state_machine/graph.rb +0 -1
- data/lib/nxt_state_machine/integrations/active_record.rb +14 -3
- data/lib/nxt_state_machine/state_machine.rb +5 -5
- data/lib/nxt_state_machine/transition.rb +2 -2
- data/lib/nxt_state_machine/transition/around_callback_chain.rb +1 -1
- data/lib/nxt_state_machine/transition/factory.rb +3 -3
- data/lib/nxt_state_machine/version.rb +1 -1
- data/nxt_state_machine.gemspec +1 -1
- metadata +4 -5
- data/lib/nxt_state_machine/transition/store.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6128e445380f2ed2b836b4c23ed58556efd2ff0728add08c2cbd45e97812e068
|
4
|
+
data.tar.gz: 05c8861e99afefec3c654366050b225d7a93c10ff984e508cd14d6345b1d0ff3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3038d32da3a7bfa5a49b4537e7c6f163259ddd0f3d2b48a05bfa838021350f648d75bfc493125529c9e1374dc34aa2c9abe94325790fe4ba82a09f01e2afdc8
|
7
|
+
data.tar.gz: 64178102f125d3b180f31b1a0bbebd766226b44351bbef906d907cc6339f2ff213c743c51b52238706827cfd597e410b828eb0824f8a4747f363c9b2b79a0ce9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# v0.1.9 2020-09-23
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- Allow to toggle locking of transitions for active record adapter per event or globally
|
6
|
+
|
7
|
+
[Compare v0.1.8...v0.1.9](https://github.com/nxt-insurance/nxt_state_machine/compare/v0.1.8...v0.1.9)
|
8
|
+
|
9
|
+
|
1
10
|
# v0.1.8 2020-0-05
|
2
11
|
|
3
12
|
### Added
|
data/Gemfile.lock
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nxt_state_machine (0.1.
|
4
|
+
nxt_state_machine (0.1.9)
|
5
5
|
activesupport
|
6
|
-
nxt_registry (~> 0.
|
6
|
+
nxt_registry (~> 0.3.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (6.0.3.
|
12
|
-
activesupport (= 6.0.3.
|
13
|
-
activerecord (6.0.3.
|
14
|
-
activemodel (= 6.0.3.
|
15
|
-
activesupport (= 6.0.3.
|
16
|
-
activesupport (6.0.3.
|
11
|
+
activemodel (6.0.3.3)
|
12
|
+
activesupport (= 6.0.3.3)
|
13
|
+
activerecord (6.0.3.3)
|
14
|
+
activemodel (= 6.0.3.3)
|
15
|
+
activesupport (= 6.0.3.3)
|
16
|
+
activesupport (6.0.3.3)
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
18
|
i18n (>= 0.7, < 2)
|
19
19
|
minitest (~> 5.1)
|
20
20
|
tzinfo (~> 1.1)
|
21
21
|
zeitwerk (~> 2.2, >= 2.2.2)
|
22
|
-
coderay (1.1.
|
23
|
-
concurrent-ruby (1.1.
|
24
|
-
diff-lcs (1.
|
25
|
-
i18n (1.8.
|
22
|
+
coderay (1.1.3)
|
23
|
+
concurrent-ruby (1.1.7)
|
24
|
+
diff-lcs (1.4.4)
|
25
|
+
i18n (1.8.5)
|
26
26
|
concurrent-ruby (~> 1.0)
|
27
27
|
method_source (1.0.0)
|
28
|
-
minitest (5.14.
|
29
|
-
nxt_registry (0.
|
28
|
+
minitest (5.14.2)
|
29
|
+
nxt_registry (0.3.2)
|
30
30
|
activesupport
|
31
31
|
pry (0.13.1)
|
32
32
|
coderay (~> 1.1)
|
@@ -37,15 +37,15 @@ GEM
|
|
37
37
|
rspec-core (~> 3.9.0)
|
38
38
|
rspec-expectations (~> 3.9.0)
|
39
39
|
rspec-mocks (~> 3.9.0)
|
40
|
-
rspec-core (3.9.
|
41
|
-
rspec-support (~> 3.9.
|
42
|
-
rspec-expectations (3.9.
|
40
|
+
rspec-core (3.9.2)
|
41
|
+
rspec-support (~> 3.9.3)
|
42
|
+
rspec-expectations (3.9.2)
|
43
43
|
diff-lcs (>= 1.2.0, < 2.0)
|
44
44
|
rspec-support (~> 3.9.0)
|
45
|
-
rspec-mocks (3.9.
|
45
|
+
rspec-mocks (3.9.1)
|
46
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
47
47
|
rspec-support (~> 3.9.0)
|
48
|
-
rspec-support (3.9.
|
48
|
+
rspec-support (3.9.3)
|
49
49
|
rspec_junit_formatter (0.4.1)
|
50
50
|
rspec-core (>= 2, < 4, != 2.12.0)
|
51
51
|
ruby-graphviz (1.2.5)
|
@@ -54,7 +54,7 @@ GEM
|
|
54
54
|
thread_safe (0.3.6)
|
55
55
|
tzinfo (1.2.7)
|
56
56
|
thread_safe (~> 0.1)
|
57
|
-
zeitwerk (2.
|
57
|
+
zeitwerk (2.4.0)
|
58
58
|
|
59
59
|
PLATFORMS
|
60
60
|
ruby
|
data/README.md
CHANGED
@@ -241,12 +241,14 @@ article.approve(approved_at: Time.current)
|
|
241
241
|
article.approve!(approved_at: Time.current)
|
242
242
|
```
|
243
243
|
|
244
|
-
**NOTE:** Transitions run in transactions that acquire a lock to prevent concurrency issues
|
245
|
-
rolled back in case of an exception or if your target cannot be saved due to validation errors.
|
246
|
-
The state is set back to the state before the transition! If you try to
|
244
|
+
**NOTE:** Transitions run in transactions that acquire a lock to prevent concurrency issues per default.
|
245
|
+
Transactions will be rolled back in case of an exception or if your target cannot be saved due to validation errors.
|
246
|
+
The state is set back to the state before the transition! If you try to transition on records with unpersisted changes
|
247
247
|
you will get a `RuntimeError: Locking a record with unpersisted changes is not supported.` error saying something
|
248
248
|
like `Use :save to persist the changes, or :reload to discard them explicitly.` since it's not possible to acquire a
|
249
|
-
lock on modified records.
|
249
|
+
lock on modified records. You can also switch of locking and transactions for events by passing in the `lock_transitions: false`
|
250
|
+
option when defining an event or globally on the state machine with the `lock_transitions: false` option. Currently
|
251
|
+
there is no option to toggle locking at runtime.
|
250
252
|
|
251
253
|
### Transitions
|
252
254
|
|
@@ -342,7 +344,10 @@ You want to break out of your transition (which is wrapped inside a lock)?
|
|
342
344
|
You can raise an error, have everything rolled back and then have your error handler take over.
|
343
345
|
**NOTE:** Unless you reload your model all assignments you did, previous to the error, should still be available in your
|
344
346
|
error handler. You can also defuse errors. This means they will not cause a rollback of the transaction during the
|
345
|
-
transition and you can actually persist changes to your model before the defused error is raised and handled.
|
347
|
+
transition and you can actually persist changes to your model before the defused error is raised and handled. You can
|
348
|
+
also switch off locking (and transactions) for events by passing the `lock_transitions: false` option when defining an event. This
|
349
|
+
can also by set globally for a state_machine by passing the `lock_transitions: false` option when setting up the state
|
350
|
+
machine.
|
346
351
|
|
347
352
|
```ruby
|
348
353
|
state_machine do
|
@@ -360,6 +365,14 @@ state_machine do
|
|
360
365
|
raise CustomError, 'This does not rollback the headline update above'
|
361
366
|
end
|
362
367
|
end
|
368
|
+
|
369
|
+
event :approve_without_lock, lock_transitions: false do
|
370
|
+
transition from: %i[written submitted deleted], to: :approved do |headline:|
|
371
|
+
# This will be saved to the database because the event does not wrap the transition in a transaction
|
372
|
+
article.update!(headline: headline)
|
373
|
+
raise StandardError, 'This does not rollback the headline update above'
|
374
|
+
end
|
375
|
+
end
|
363
376
|
|
364
377
|
on_error! CustomError from: any_state, to: :approved do |error, transition|
|
365
378
|
# You can still handle the defused Error if you want to
|
data/lib/nxt_state_machine.rb
CHANGED
@@ -25,7 +25,6 @@ require "nxt_state_machine/transition/interface"
|
|
25
25
|
require "nxt_state_machine/transition"
|
26
26
|
require "nxt_state_machine/transition/factory"
|
27
27
|
require "nxt_state_machine/transition/proxy"
|
28
|
-
require "nxt_state_machine/transition/store"
|
29
28
|
require "nxt_state_machine/transition/around_callback_chain"
|
30
29
|
require "nxt_state_machine/state_machine"
|
31
30
|
require "nxt_state_machine/integrations/active_record"
|
@@ -38,7 +37,7 @@ module NxtStateMachine
|
|
38
37
|
include NxtRegistry
|
39
38
|
|
40
39
|
def state_machine(name = :default, **opts, &config)
|
41
|
-
state_machines.resolve
|
40
|
+
state_machines.resolve(name) || state_machines.register(
|
42
41
|
name,
|
43
42
|
StateMachine.new(name, self, state_machine_event_registry, **opts).configure(&config)
|
44
43
|
)
|
@@ -76,15 +75,15 @@ module NxtStateMachine
|
|
76
75
|
end
|
77
76
|
|
78
77
|
def state_machine(name = :default)
|
79
|
-
@state_machine ||= self.class.state_machines.resolve(name)
|
78
|
+
@state_machine ||= self.class.state_machines.resolve!(name)
|
80
79
|
end
|
81
80
|
|
82
81
|
def current_state_name(name = :default)
|
83
|
-
state_machines.resolve(name).current_state_name(self)
|
82
|
+
state_machines.resolve!(name).current_state_name(self)
|
84
83
|
end
|
85
84
|
|
86
85
|
def current_state(name = :default)
|
87
|
-
state_machines.resolve(name).states.resolve(current_state_name(name))
|
86
|
+
state_machines.resolve!(name).states.resolve!(current_state_name(name))
|
88
87
|
end
|
89
88
|
|
90
89
|
def halt_transition(*args, **opts)
|
@@ -8,13 +8,13 @@ module NxtStateMachine
|
|
8
8
|
|
9
9
|
Array(from).each do |from_state|
|
10
10
|
Array(to).each do |to_state|
|
11
|
-
callbacks.from(from_state).to(to_state).kind(kind) << method_or_block
|
11
|
+
callbacks.from!(from_state).to!(to_state).kind!(kind) << method_or_block
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def resolve(transition, kind = nil)
|
17
|
-
all_callbacks = callbacks.from(transition.from.enum).to(transition.to.enum)
|
16
|
+
def resolve!(transition, kind = nil)
|
17
|
+
all_callbacks = callbacks.from!(transition.from.enum).to!(transition.to.enum)
|
18
18
|
return all_callbacks unless kind
|
19
19
|
|
20
20
|
all_callbacks.kind(kind)
|
@@ -24,8 +24,8 @@ module NxtStateMachine
|
|
24
24
|
|
25
25
|
def callbacks
|
26
26
|
@callbacks ||= registry :from do
|
27
|
-
|
28
|
-
|
27
|
+
level :to do
|
28
|
+
level :kind, default: -> { [] } do
|
29
29
|
attrs :before, :after
|
30
30
|
end
|
31
31
|
end
|
@@ -11,15 +11,15 @@ module NxtStateMachine
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def resolve(transition)
|
15
|
-
errors.from(transition.from.enum).to(transition.to.enum)
|
14
|
+
def resolve!(transition)
|
15
|
+
errors.from!(transition.from.enum).to!(transition.to.enum)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def errors
|
21
21
|
@errors ||= registry :from do
|
22
|
-
|
22
|
+
level :to, default: -> { [] }
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -7,13 +7,14 @@ module NxtStateMachine
|
|
7
7
|
@name = name
|
8
8
|
@event_transitions = registry("#{name} event transitions")
|
9
9
|
@names = Event::Names.build(name)
|
10
|
+
@options = options.with_indifferent_access
|
10
11
|
|
11
12
|
configure(&block)
|
12
13
|
|
13
14
|
ensure_event_has_transitions
|
14
15
|
end
|
15
16
|
|
16
|
-
attr_reader :name, :state_machine, :event_transitions, :names
|
17
|
+
attr_reader :name, :state_machine, :event_transitions, :names, :options
|
17
18
|
|
18
19
|
delegate :before_transition,
|
19
20
|
:after_transition,
|
@@ -38,7 +39,7 @@ module NxtStateMachine
|
|
38
39
|
alias_method :transition, :transitions
|
39
40
|
|
40
41
|
def transitions_from?(state)
|
41
|
-
event_transitions.resolve
|
42
|
+
event_transitions.resolve(state).present?
|
42
43
|
end
|
43
44
|
|
44
45
|
def to_s
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module NxtStateMachine
|
2
2
|
module ActiveRecord
|
3
3
|
module ClassMethods
|
4
|
-
def state_machine(name = :default, state_attr: :state, target: nil, &config)
|
4
|
+
def state_machine(name = :default, state_attr: :state, target: nil, lock_transitions: true, &config)
|
5
5
|
machine = super(
|
6
6
|
name,
|
7
7
|
state_attr: state_attr,
|
8
8
|
target: target,
|
9
|
+
lock_transitions: lock_transitions,
|
9
10
|
&config
|
10
11
|
)
|
11
12
|
|
@@ -49,14 +50,14 @@ module NxtStateMachine
|
|
49
50
|
result = nil
|
50
51
|
defused_error = nil
|
51
52
|
|
52
|
-
target.
|
53
|
+
with_conditional_lock(target, transition.event) do
|
53
54
|
transition.run_before_callbacks
|
54
55
|
result = execute_transition(target, transition, state_attr, save_with_method)
|
55
56
|
transition.run_after_callbacks
|
56
57
|
|
57
58
|
result
|
58
59
|
rescue StandardError => error
|
59
|
-
if machine.defuse_registry.resolve(transition).find { |error_class| error.is_a?(error_class) }
|
60
|
+
if machine.defuse_registry.resolve!(transition).find { |error_class| error.is_a?(error_class) }
|
60
61
|
defused_error = error
|
61
62
|
else
|
62
63
|
raise error
|
@@ -82,6 +83,16 @@ module NxtStateMachine
|
|
82
83
|
block ? result : set_state_result
|
83
84
|
end
|
84
85
|
end
|
86
|
+
|
87
|
+
def with_conditional_lock(target, event, &block)
|
88
|
+
return block.call unless lock_transition?(event)
|
89
|
+
|
90
|
+
target.with_lock { block.call }
|
91
|
+
end
|
92
|
+
|
93
|
+
def lock_transition?(event)
|
94
|
+
event.options.fetch(:lock_transitions) { state_machine.options.fetch(:lock_transitions) }
|
95
|
+
end
|
85
96
|
end
|
86
97
|
|
87
98
|
def self.included(base)
|
@@ -6,7 +6,7 @@ module NxtStateMachine
|
|
6
6
|
@options = opts
|
7
7
|
|
8
8
|
@states = NxtStateMachine::StateRegistry.new
|
9
|
-
@transitions =
|
9
|
+
@transitions = []
|
10
10
|
@events = event_registry
|
11
11
|
@callbacks = CallbackRegistry.new
|
12
12
|
@error_callback_registry = ErrorCallbackRegistry.new
|
@@ -89,9 +89,9 @@ module NxtStateMachine
|
|
89
89
|
Event::Names.set_state_method_map(name).each do |event_name, set_state_method|
|
90
90
|
class_context.define_method event_name do |*args, **opts|
|
91
91
|
event.state_machine.can_transition!(name, event.state_machine.current_state_name(self))
|
92
|
-
transition = event.event_transitions.resolve(event.state_machine.current_state_name(self))
|
92
|
+
transition = event.event_transitions.resolve!(event.state_machine.current_state_name(self))
|
93
93
|
# Transition is build every time and thus should be thread safe!
|
94
|
-
transition.build_transition(
|
94
|
+
transition.build_transition(event, self, set_state_method, *args, **opts)
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -101,7 +101,7 @@ module NxtStateMachine
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def can_transition?(event_name, from)
|
104
|
-
event = events.resolve(event_name)
|
104
|
+
event = events.resolve!(event_name)
|
105
105
|
event && event.event_transitions.key?(from)
|
106
106
|
end
|
107
107
|
|
@@ -161,7 +161,7 @@ module NxtStateMachine
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def run_callbacks(transition, kind, context)
|
164
|
-
current_callbacks = callbacks.resolve(transition, kind)
|
164
|
+
current_callbacks = callbacks.resolve!(transition, kind)
|
165
165
|
return unless current_callbacks.any?
|
166
166
|
|
167
167
|
current_callbacks.each do |callback|
|
@@ -5,8 +5,8 @@ module NxtStateMachine
|
|
5
5
|
def initialize(name, event:, from:, to:, state_machine:, context:, set_state_method:, &block)
|
6
6
|
@name = name
|
7
7
|
@event = event
|
8
|
-
@from = state_machine.states.resolve(from)
|
9
|
-
@to = state_machine.states.resolve(to)
|
8
|
+
@from = state_machine.states.resolve!(from)
|
9
|
+
@to = state_machine.states.resolve!(to)
|
10
10
|
@state_machine = state_machine
|
11
11
|
@set_state_method = set_state_method
|
12
12
|
@context = context
|
@@ -18,7 +18,7 @@ module NxtStateMachine
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def callbacks
|
21
|
-
@callbacks ||= state_machine.callbacks.resolve(transition).kind(:around)
|
21
|
+
@callbacks ||= state_machine.callbacks.resolve!(transition).kind(:around)
|
22
22
|
end
|
23
23
|
|
24
24
|
attr_reader :transition, :context, :state_machine
|
@@ -4,8 +4,8 @@ module NxtStateMachine
|
|
4
4
|
|
5
5
|
def initialize(name, from:, to:, state_machine:, &block)
|
6
6
|
@name = name
|
7
|
-
@from = state_machine.states.resolve(from)
|
8
|
-
@to = state_machine.states.resolve(to)
|
7
|
+
@from = state_machine.states.resolve!(from)
|
8
|
+
@to = state_machine.states.resolve!(to)
|
9
9
|
@state_machine = state_machine
|
10
10
|
@block = block
|
11
11
|
|
@@ -28,7 +28,7 @@ module NxtStateMachine
|
|
28
28
|
set_state_method: set_state_method
|
29
29
|
}
|
30
30
|
|
31
|
-
transition = Transition.new(name, **options)
|
31
|
+
transition = Transition.new(event.name, **options)
|
32
32
|
|
33
33
|
if block
|
34
34
|
# if the transition takes a block we make it available through a proxy on the transition itself!
|
data/nxt_state_machine.gemspec
CHANGED
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.require_paths = ["lib"]
|
36
36
|
|
37
37
|
spec.add_dependency "activesupport"
|
38
|
-
spec.add_dependency "nxt_registry", "~> 0.
|
38
|
+
spec.add_dependency "nxt_registry", "~> 0.3.0"
|
39
39
|
|
40
40
|
spec.add_development_dependency "bundler", "~> 2.0"
|
41
41
|
spec.add_development_dependency "rake", "~> 12.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nxt_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Robecke
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-
|
14
|
+
date: 2020-09-30 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -33,14 +33,14 @@ dependencies:
|
|
33
33
|
requirements:
|
34
34
|
- - "~>"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: 0.
|
36
|
+
version: 0.3.0
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - "~>"
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 0.3.0
|
44
44
|
- !ruby/object:Gem::Dependency
|
45
45
|
name: bundler
|
46
46
|
requirement: !ruby/object:Gem::Requirement
|
@@ -193,7 +193,6 @@ files:
|
|
193
193
|
- lib/nxt_state_machine/transition/factory.rb
|
194
194
|
- lib/nxt_state_machine/transition/interface.rb
|
195
195
|
- lib/nxt_state_machine/transition/proxy.rb
|
196
|
-
- lib/nxt_state_machine/transition/store.rb
|
197
196
|
- lib/nxt_state_machine/version.rb
|
198
197
|
- lib/railtie.rb
|
199
198
|
- lib/tasks/draw_graph.rake
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module NxtStateMachine
|
2
|
-
class Transition::Store < Array
|
3
|
-
def <<(transition)
|
4
|
-
ensure_transition_unique(transition)
|
5
|
-
super
|
6
|
-
end
|
7
|
-
|
8
|
-
alias_method :add, :<<
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def ensure_transition_unique(transition)
|
13
|
-
return unless find { |other| other.from.enum == transition.from.enum && other.to.enum == transition.to.enum }
|
14
|
-
|
15
|
-
raise NxtStateMachine::Errors::TransitionAlreadyRegistered,
|
16
|
-
"A transition from :#{transition.from.enum} to :#{transition.to.enum} was already registered"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|