I finally got backtracking right. I fighted against a hard to find bug, and
it turned out, that I was modifying a snapshot (e.g. arr = []; arr
<< 1). Method freeze is your friend! Now, backtracking
is much more flexible. You can register individual objects for beeing
backtracked (nothing new), but you can now have user-defined methods for
taking or applying a snapshot of this object. For this purpose, you have to
define take_snapshot and apply_snapshot, very similar to
marshal_dump and marshal_load.
Backtracking decorations is now possible, too, but you still have to do
this on your own. This is useful, so that "call/answer" calls can
be undone correctly.
Decorations
A Delegate decoration is used to implement the call/answer mechanism. I'll
explain this by demonstrating a little example:
def render_content_on(r)
r.anchor.action(:confirm_quit).with do
r.text('Quit')
end
end
def confirm_quit
call MessageBox.new('Do you really want to quit?'), :quit
end
def quit(res)
exit if res
end
If you click on the anchor named Quit, method
confirm_quit is called. This then replaces the calling component
with a message box (or at least, only the message box is shown and can
handle actions, not the calling component), with two buttons OK
and Cancel. If you now click on one of them, the calling component
is restored and it's method quit is invoked with the return code
of the message box. How the message box can be implemented is shown below:
class MessageBox < Wee::Component
def initialize(msg)
@msg = msg
end
def ok
answer true
end
def cancel
answer false
end
def render_content_on(r)
r.text @msg
r.form do
r.submit_button.value('OK').action(:ok)
r.space
r.submit_button.value('Cancel').action(:cancel)
end
end
end
In further versions of Wee, it will be possible to register an action and
specify some default arguments. This would make the methods ok and
cancel superfluous:
Uhm, 12 minutes later... it's working ;-) Found by the way two other bugs.
So how is this call/answer mechanism working? Well, it really is only half
a dozen lines of code:
class Wee::Component
attr_accessor :caller # who is calling us?
# call another component
def call(component, return_method=nil)
component.caller = [self, return_method]
add_decoration(Wee::Delegate.new(component))
end
# return from a call
def answer(*args)
component, return_method = self.caller
component.remove_first_decoration
component.send(return_method, *args) if return_method
end
end