OK so you have the diagram declared both in the db and the model?
Wouldn’t you want to move it out of the model completely (DRYing it up
a little?).
I implemented something similar before in Java, and had it declared
outside the model (it was in xml, but could have been a db). What I
had was the model declaring just the name of the state machine. So I
had many state machines in the xml, each having its out states,
events, etc (argh, just remembering the use of the reflection api to
specify the conditions makes me vomit).
In my current app I’ve got three models with aasm, and they all happen
to have an :active state and a :discarded state (the first is a
composite, whose components are different in the different models; the
latter one is a leaf state, but each model has different inbound
transitions for it) for instance.
Do you also have a reference to the model type in the states table?
(so I could have a :closed for CurriculumVitae and another for Job and
JobApplication).
Did you put your models withing plugin? I had troubles doing that :(.
Had to copy the model to my app/models.
I didn’t get your point about zombie states… (after a change in the
state machine).
The thing about parameterized events is that I always managed to
implement what I was trying to do in some other way (and liked that
more). I assume in your example that :captured means “partially
completed” (if you will). I would simply have (I’ll use the aasm
idioms, which I’ve seen you slightly modified).
def capture(amount)
self.pay(amount) # increment the “paid” counter and raises an
exception if it needs so (e.g. paid+amount > total).
self.capture! # triggers the event, without parameters
end
and the transitions would be the same… the guard, of course, has
access to the transition. So why does it need “amount” to begin with?
Hmm, that’s an interesting idea, but I was actually referring to the
fact that states, events, and transitions defined in a class are only
valid and accessible by that class and all its subclasses.
Oops sorry, I don’t use STI (if I need something like that, I create
modules; it led me to much better naming which just smells good)…
There is a scenario I’m using [dynamic initial states] for, although it may be invalid since
it may call for inheritance where the subclass overrides the
superclass’s initial state, but this is something I have to re-
evaluate.
I will always advocate for composition over inheritance.
it. If it seems that no more scenarios come up, I’ll axe it out of
the implementation.
That’s not my intention. I’m trying to find uses I haven’t thought of,
a clearer view of another perspective.
Callbacks canceling events are meant to parallel the structure used in
callbacks for ActiveRecord.
Now I see that what you mean with “Callbacks” is not what I though (I
guess your “capture” is a callback; some magic must be involved in
order to wrap state-changing behaviour to that method). You are having
problems with side-effects in guards/callbacks.
Some time ago I made a choice (when I had already implemented my java
version and before coming to ruby, so it never made it into any
implementation, sadly). I would have “preconditions” (can stop the
event, must not have side effects, determine whether a user can even
“see” an event), “postconditions” (can stop the event, can have side
effects) and “filter” (cannot stop the event, should have side
effects). Your web-service example would be a “postcondition”.
Something I opted to not consider was atomicity: if one “action” with
side effect is followed by another, and the last one fails, it’s up to
my client to rollback the first one (once I had to consume TWO web-
services, notifying something; later, they could notify an error and
the other one was required to be “unnotified” if it was previously
notified… kinds of an extreme case I ended up managing from other
place).
Making the separation I did (pre, post, filter) is useful in this:
only “postconditions” are dangerous wrt to atomicity.
(…) This
really couldn’t be done as a guard, because you only want to talk to
the gateway if all other guards have passed. You could possibly hack
this by making sure it’s the last guard, but I don’t personally find
that as clear.
Really? what about
transition :from => :foo, :to => :bar, :postcondition => lambda { |
record| record.still_not_complete? && record.notify! }
Please notice that :still_not_complete? does not have side effects,
but :notify! does have. They will always be executed in that order,
and thanks to short-circuit boolean evaluation, if the first fails,
notify! will never be executed…
This is currently implemented via two methods.
Both are awesome! (your side-effects callbacks get triggered when they
need to be regardless of access, plus you never get an inconsistent
record, perfect). Perhaps we could try to simplify this, but I don’t
see how… someone?
OK, now, I really, really promise to look into your plugin’s, and make
more educated comments the next time
Thanks!
nachokb