Skip to content

Commit 9a67e7d

Browse files
committed
Add some lifecycle callbacks to Tinky::Object
This is for the benefit of implementations that may need to persist the state to some storage. Also fix the implementation of the workflow specific role.
1 parent 9e04348 commit 9a67e7d

File tree

6 files changed

+212
-19
lines changed

6 files changed

+212
-19
lines changed

Changes

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
v0.1.2 Mon 19 Apr 14:44:33 BST 2021
2+
* Add some lifecycle callbacks to the object for the convenience of sub-classes
3+
* Fix the application of the role
14
v0.1.1 Mon 25 Jan 13:06:26 GMT 2021
25
* Some changes to make it easier for sub-classes
36
v0.1.0 Mon 18 Jan 10:15:01 GMT 2021

Documentation.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,9 @@ The same rules for execution based on the signature and the object to which the
393393
role Tinky::Object
394394
------------------
395395

396-
This is a role that should should be applied to any application object that is to have a state managed by [Tinky::Workflow](Tinky::Workflow), it provides the mechanisms for transition application and allows the transitions to be validated by the mechanisms described above for [Tinky::State](Tinky::State) and [Tinky::Transition](Tinky::Transition)
396+
This is a role that should should be applied to any application object that is to have a state managed by [Tinky::Workflow](Tinky::Workflow), it provides the mechanisms for transition application and allows the transitions to be validated by the mechanisms described above for [Tinky::State](Tinky::State) and [Tinky::Transition](Tinky::Transition).
397+
398+
There are also method traits to define methods that will be called before and after the application of the workflow to the object, and before and after the application of a transition to the object.
397399

398400
### method state
399401

@@ -440,6 +442,28 @@ This returns the transition that would place the object in the supplied state fr
440442

441443
Used to smart match the object against either a State (returns True if the state matches the current state of the object,) or a Transition (returns True if the `from` state matches the current state of the object.)
442444

445+
### trait before-apply-workflow
446+
447+
This trait can be applied to a method that will be called within the `apply-workflow` immediately after the application has been validated and before anything else occurs (primarily the setting of an initial state state if any.) The method will be called with the Workflow object as an argument.
448+
449+
This is primarily for the benefit of implementations that may want to, for instance, retrieve the object state from some storage before setting up the rest of the workflow.
450+
451+
### trait after-apply-workflow
452+
453+
This trait can be applied to a method that will be called within the `apply-workflow` immediately before the Workflow's `applied` method is called with the object, that is the initial state will have been set and other changes made. The method will be called with the Workflow object as an argument.
454+
455+
This may be helpful, for example, if one wanted to persist the initial state to some storage.
456+
457+
### trait before-apply-transition
458+
459+
This trait can be applied to a method that will be called within the `apply-transition` immediately after the validation has been done and before the state is actually changed. The method will be called with the Transition object as the argument.
460+
461+
### trait after-apply-transition
462+
463+
This trait can be applied to a method that will be called within the `apply-transition` after the state of the object has been changed and immediately before the object is passed to the transition's `applied` method. The method will be called with the Transition object as the argument.
464+
465+
This might be used, for example, to persist the change of state of the object to some storage.
466+
443467
EXCEPTIONS
444468
----------
445469

META6.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Tinky",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"auth": "github:jonathanstowe",
55
"api": "1.0",
66
"description": "An Experimental Workflow / State Management toolit",

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ An experimental workflow module for Raku
88

99
This is quite long [skip to the Description](#description) if you are impatient.
1010

11-
```perl6
11+
```raku
1212

1313
use Tinky;
1414

lib/Tinky.pm

+125-13
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,11 @@ This is a role that should should be applied to any application object
582582
that is to have a state managed by L<Tinky::Workflow>, it provides the
583583
mechanisms for transition application and allows the transitions to
584584
be validated by the mechanisms described above for L<Tinky::State>
585-
and L<Tinky::Transition>
585+
and L<Tinky::Transition>.
586+
587+
There are also method traits to define methods that will be called before
588+
and after the application of the workflow to the object, and before and
589+
after the application of a transition to the object.
586590
587591
=head3 method state
588592
@@ -662,6 +666,47 @@ the state matches the current state of the object,) or a Transition
662666
(returns True if the C<from> state matches the current state of the
663667
object.)
664668
669+
=head3 trait before-apply-workflow
670+
671+
This trait can be applied to a method that will be called within the
672+
C<apply-workflow> immediately after the application has been validated
673+
and before anything else occurs (primarily the setting of an initial
674+
state state if any.) The method will be called with the Workflow
675+
object as an argument.
676+
677+
This is primarily for the benefit of implementations that may want
678+
to, for instance, retrieve the object state from some storage before
679+
setting up the rest of the workflow.
680+
681+
=head3 trait after-apply-workflow
682+
683+
This trait can be applied to a method that will be called within the
684+
C<apply-workflow> immediately before the Workflow's C<applied> method
685+
is called with the object, that is the initial state will have been
686+
set and other changes made. The method will be called with the Workflow
687+
object as an argument.
688+
689+
This may be helpful, for example, if one wanted to persist the initial
690+
state to some storage.
691+
692+
=head3 trait before-apply-transition
693+
694+
This trait can be applied to a method that will be called within the
695+
C<apply-transition> immediately after the validation has been done and
696+
before the state is actually changed. The method will be called with
697+
the Transition object as the argument.
698+
699+
=head3 trait after-apply-transition
700+
701+
This trait can be applied to a method that will be called within the
702+
C<apply-transition> after the state of the object has been changed and
703+
immediately before the object is passed to the transition's C<applied>
704+
method. The method will be called with the Transition object as the
705+
argument.
706+
707+
This might be used, for example, to persist the change of state of the
708+
object to some storage.
709+
665710
=head2 EXCEPTIONS
666711
667712
The methods for applying a transition to an object will signal an
@@ -727,7 +772,7 @@ current state on the object.
727772
728773
=end pod
729774

730-
module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
775+
module Tinky:ver<0.1.2>:auth<github:jonathanstowe>:api<1.0> {
731776

732777
# Stub here, definition below
733778
class State { ... };
@@ -739,7 +784,7 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
739784
# This is a handy type definition to save having to type
740785
# this out all over the place so we can pretend a role
741786
# is a type.
742-
subset Role of Mu where { $_.HOW.archetypes.composable };
787+
subset Role of Mu where { $_.DEFINITE && $_.HOW.archetypes.composable };
743788

744789
# Traits for user defined state and transition classes
745790
# The roles are only used to indicate the purpose of the
@@ -793,6 +838,42 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
793838
@meths;
794839
}
795840

841+
# This is a base role for the Object apply-workflow callbacks
842+
role ObjectWorkflowPhase {
843+
}
844+
845+
# indicate the 'before-apply-workflow' callbacks for a Tinky::Object
846+
role ObjectWorkflowBefore does ObjectWorkflowPhase {
847+
}
848+
multi sub trait_mod:<is> (Method $m, :$before-apply-workflow! ) is export {
849+
$m does ObjectWorkflowBefore;
850+
}
851+
852+
# indicate the 'after-apply-workflow' callbacks for a Tinky::Object
853+
role ObjectWorkflowAfter does ObjectWorkflowPhase {
854+
}
855+
multi sub trait_mod:<is> (Method $m, :$after-apply-workflow! ) is export {
856+
$m does ObjectWorkflowAfter;
857+
}
858+
859+
# This is a base role for the Object apply-transition callbacks
860+
role ObjectTransitionPhase {
861+
}
862+
863+
# indicate the 'before-apply-transition' callbacks for a Tinky::Object
864+
role ObjectTransitionBefore does ObjectTransitionPhase {
865+
}
866+
multi sub trait_mod:<is> (Method $m, :$before-apply-transition! ) is export {
867+
$m does ObjectTransitionBefore;
868+
}
869+
870+
# indicate the 'after-apply-transition' callbacks for a Tinky::Object
871+
role ObjectTransitionAfter does ObjectTransitionPhase {
872+
}
873+
multi sub trait_mod:<is> (Method $m, :$after-apply-transition! ) is export {
874+
$m does ObjectTransitionAfter;
875+
}
876+
796877
class Tinky::X::Fail is Exception {
797878
}
798879

@@ -977,7 +1058,7 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
9771058
validate-helper($object, ( @!validators, validate-methods(self, $object, ApplyValidator)).flat);
9781059
}
9791060

980-
has $!role;
1061+
has Mu $.role;
9811062

9821063
method states() {
9831064
if not @!states.elems {
@@ -1042,9 +1123,9 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
10421123
self.transitions-for-state($from).first({ $_.to ~~ $to });
10431124
}
10441125

1045-
method role( --> Role ) {
1126+
method role( --> Mu ) {
10461127
if not $!role ~~ Role {
1047-
$!role = role { };
1128+
$!role := Metamodel::ParametricRoleHOW.new_type(name => self.^name ~ '::' ~ ( self.name // 'role' ) );
10481129
for @.transitions.classify(-> $t { $t.name }).kv -> $name, $transitions {
10491130
my $method = method (|c) {
10501131
if $transitions.grep(self.state).head -> $tran {
@@ -1054,8 +1135,11 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
10541135
Tinky::X::InvalidTransition.new(message => "No transition '$name' for state '{ self.state.Str }'").throw;
10551136
}
10561137
}
1138+
$method.set_name($name);
10571139
$!role.^add_method($name, $method);
10581140
}
1141+
$!role.^set_body_block( -> |c { });
1142+
$!role.^compose;
10591143
}
10601144
$!role;
10611145
}
@@ -1080,11 +1164,13 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
10801164
$SELF!state = $val;
10811165
}
10821166
else {
1083-
if $SELF.transition-for-state($val) -> $trans {
1084-
$SELF.apply-transition($trans);
1085-
}
1086-
else {
1087-
Tinky::X::NoTransition.new(from => $SELF.state, to => $val).throw;
1167+
if $val !~~ $SELF!state {
1168+
if $SELF.transition-for-state($val) -> $trans {
1169+
$SELF.apply-transition($trans);
1170+
}
1171+
else {
1172+
Tinky::X::NoTransition.new(from => $SELF.state, to => $val).throw;
1173+
}
10881174
}
10891175
}
10901176
$SELF!state;
@@ -1095,12 +1181,14 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
10951181
method apply-workflow(Workflow $wf) {
10961182
my $p = $wf.validate-apply(self);
10971183
if $p.result {
1184+
self.before-apply-workflow($wf);
10981185
$!workflow = $wf;
10991186
if not $!state.defined and $!workflow.initial-state.defined {
11001187
$!state = $!workflow.initial-state;
11011188
}
1102-
try self does $wf.role;
1103-
$wf.applied(self);;
1189+
self.^mixin($wf.role);
1190+
self.after-apply-workflow($wf);
1191+
$wf.applied(self);
11041192
}
11051193
else {
11061194
Tinky::X::ObjectRejected.new(workflow => $wf).throw;
@@ -1146,7 +1234,9 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
11461234
if $!state.defined {
11471235
if self ~~ $trans {
11481236
if await $trans.validate-apply(self) {
1237+
self.before-apply-transition($trans);
11491238
$!state = $trans.to;
1239+
self.after-apply-transition($trans);
11501240
$trans.applied(self);
11511241
$!state;
11521242
}
@@ -1168,6 +1258,28 @@ module Tinky:ver<0.1.1>:auth<github:jonathanstowe>:api<1.0> {
11681258
Tinky::X::NoState.new.throw;
11691259
}
11701260
}
1261+
1262+
method before-apply-workflow(Workflow $workflow --> Nil) {
1263+
for self.^methods.grep(ObjectWorkflowBefore) -> $method {
1264+
self.$method($workflow);
1265+
}
1266+
}
1267+
method after-apply-workflow(Workflow $workflow --> Nil) {
1268+
for self.^methods.grep(ObjectWorkflowAfter) -> $method {
1269+
self.$method($workflow);
1270+
}
1271+
}
1272+
1273+
method before-apply-transition(Transition $transition --> Nil) {
1274+
for self.^methods.grep(ObjectTransitionBefore) -> $method {
1275+
self.$method($transition);
1276+
}
1277+
}
1278+
method after-apply-transition(Transition $transition --> Nil) {
1279+
for self.^methods.grep(ObjectTransitionAfter) -> $method {
1280+
self.$method($transition);
1281+
}
1282+
}
11711283
}
11721284
}
11731285
# vim: expandtab shiftwidth=4 ft=raku

t/040-workflow-object.t

+57-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ throws-like { Tinky::Workflow.new.states }, Tinky::X::NoTransitions, ".states th
1616

1717
my Tinky::Workflow $wf;
1818

19-
lives-ok { $wf = Tinky::Workflow.new(:@transitions) }, "create new workflow with transitions";
19+
lives-ok { $wf = Tinky::Workflow.new(name => 'first-test-workflow', :@transitions) }, "create new workflow with transitions";
2020
is $wf.transitions.elems, @transitions.elems, "and got the right number of transitions";
2121
is $wf.states.elems, @states.elems, "and calculated the right number of states";
2222

@@ -64,8 +64,8 @@ subtest {
6464
my $wf = Tinky::Workflow.new(:@transitions, initial-state => @states[0]);
6565
ok $wf.initial-state ~~ @states[0], "just check the initial-state got set";
6666
my $obj = FooTest.new();
67-
#lives-ok {
68-
$obj.apply-workflow($wf); # }, "apply workflow with an initial-state (object has no state)";
67+
lives-ok {
68+
$obj.apply-workflow($wf); }, "apply workflow with an initial-state (object has no state)";
6969
ok $obj.state ~~ @states[0], "and the new object now has that state";
7070
my $new-state = Tinky::State.new(name => 'new-state');
7171
$obj = FooTest.new(state => $new-state);
@@ -75,5 +75,59 @@ subtest {
7575

7676
}, "initial state on Workflow";
7777

78+
subtest {
79+
my class BarTest does Tinky::Object {
80+
has Str $.before;
81+
has Str $.after;
82+
83+
method before-method(Tinky::Workflow $wf) is before-apply-workflow {
84+
$!before = $wf.name;
85+
}
86+
method after-method(Tinky::Workflow $wf) is after-apply-workflow {
87+
$!after = $wf.name;
88+
}
89+
}
90+
91+
my $wf = Tinky::Workflow.new(name => 'apply-callback-test-workflow', :@transitions, initial-state => @states[0]);
92+
93+
my $object = BarTest.new;
94+
95+
lives-ok { $object.apply-workflow($wf) }, 'apply-workflow with apply callbacks';
96+
97+
is $object.before, $wf.name, "saw the before";
98+
is $object.after, $wf.name, "saw the after";
99+
100+
101+
102+
}, "apply-workflow callbacks";
103+
104+
subtest {
105+
my class ZubTest does Tinky::Object {
106+
has Str $.before;
107+
has Str $.after;
108+
109+
method before-method(Tinky::Transition $trans) is before-apply-transition {
110+
$!before = $trans.name;
111+
}
112+
method after-method(Tinky::Transition $trans) is after-apply-transition {
113+
$!after = $trans.name;
114+
}
115+
}
116+
117+
my $wf = Tinky::Workflow.new(name => 'apply-test-transition-workflow', :@transitions, initial-state => @states[0]);
118+
119+
my $object = ZubTest.new;
120+
121+
lives-ok { $object.apply-workflow($wf) }, 'apply-workflow';
122+
123+
lives-ok { $object.state = @states[1]; }, "apply a transition";
124+
125+
is $object.before, 'one-two', "saw the before";
126+
is $object.after, 'one-two', "saw the after";
127+
128+
129+
130+
}, "apply-transition callbacks";
131+
78132
done-testing;
79133
# vim: expandtab shiftwidth=4 ft=raku

0 commit comments

Comments
 (0)