[Jifty-commit] r1522 - in jifty/trunk: . lib/Jifty lib/Jifty/Request

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Mon Jul 10 17:20:48 EDT 2006


Author: alexmv
Date: Mon Jul 10 17:20:47 2006
New Revision: 1522

Added:
   jifty/trunk/lib/Jifty/Manual/Continuations.pod
Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/lib/Jifty/Continuation.pm
   jifty/trunk/lib/Jifty/Request.pm
   jifty/trunk/lib/Jifty/Request/Mapper.pm

Log:
 r15161 at zoq-fot-pik:  chmrr | 2006-07-10 17:19:44 -0400
  * Continuation manual
  * Set up the output API for mapping of request parameters (input API
 already existed)
  * Change method of getting results out of response on continuation RETURN


Modified: jifty/trunk/lib/Jifty/Continuation.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Continuation.pm	(original)
+++ jifty/trunk/lib/Jifty/Continuation.pm	Mon Jul 10 17:20:47 2006
@@ -202,15 +202,8 @@
 
     # Pull state information out of the continuation and set it
     # up; we use clone so that the continuation itself is
-    # immutable.  It is vaguely possible that there are results in
-    # the response already (set up by the dispatcher) so we place
-    # results from the continuation's response into the existing
-    # response only if it wouldn't clobber something.
-    my %results = $self->response->results;
-    for (keys %results) {
-        next if Jifty->web->response->result($_);
-        Jifty->web->response->result($_,dclone($results{$_}));
-    }
+    # immutable.
+    Jifty->web->response(dclone($self->response));
 
     # Run any code in the continuation
     $self->code->(Jifty->web->request)

Added: jifty/trunk/lib/Jifty/Manual/Continuations.pod
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Manual/Continuations.pod	Mon Jul 10 17:20:47 2006
@@ -0,0 +1,232 @@
+=head1 NAME
+
+Jifty::Manual::Continuations - There And Back Again
+
+=head1 DESCRIPTION
+
+Continuations are a powerful concept in computer science -- in a
+nutshell, they allow you to store away the state of of the interpreter
+at any given point.  More importantly, they allow you to return to
+that state at any later time, by calling the continuation with, and
+evaluation of that interpreter state will resume.  They're a concept
+that first arose in LISP, but has implementations these days in Ruby,
+Scheme, Haskell, Smalltalk, to name a few.
+
+Thus, continuations allow you to preserve context, and return to it
+later.  This is amazingly useful in web programming, which is limited
+to C<HTTP>, which is an inherently stateless protocol.  By passing
+around continuations, we can keep track of the context that got us to
+the current page.  While we can't construct continuations at the
+interpreter level -- because Perl doesn't support them -- we can
+implement them at the level of HTTP requests.  In technical terms,
+they are I<delimited continuations>.
+
+Continuations are more useful than session because sessions store
+information across browser windows; sessions may also break in the
+presence of the back button, as the information displayed on the
+screen, and the information stored in the session may differ.  Since
+continuations are immutable, and a new one is produced every time a
+change is made, the information displayed in the browser cannot get
+out of sync with the information contained in any associated
+continuation.
+
+=head1 USING CONTINUATIONS
+
+=head2 As simple links in templates
+
+The simplest form of continuation use is in a template, using
+L<Jifty::Web/tangent>, as follows:
+
+    <% Jifty->web->tangent( url   => "/someplace",
+                            label => "Go someplace") %>
+
+This will create a link, which, when clicked, will store the current
+request into a continuation, and jump to the url C</someplace>.  You
+can think of this as a form of C<gosub>.  In the C</someplace>
+template, you can display information, and possibly have the user
+navigate between multiple pages before returning to the previous page:
+
+    <% Jifty->web->return( label => "Back to whence you came" ) %>
+
+Sometimes, it may be possible for the user to get to a location
+without having a continuation set.  In that case, clicking on the
+"Back to whence you came" link will appear to do nothing -- which may
+be slightly confusing to the user.  To remedy this, Jifty provides a
+way to specify a default location to return to:
+
+    <% Jifty->web->return( to => "/default", label => "Go back" ) %>
+
+=head2 Using return values
+
+All of the above examples generate links, which means that they don't
+interact at all with actions.  However, continuations can also be
+useful in creating complex multi-page actions.
+
+Continuations are saved -- and the browser is redirected to the new
+URL -- just after all actions have been checked for validation but
+before any of them are run.  This means that the new request has
+access to the full validation state of its parent's actions.
+
+When a continuation is called, first checks that all actions in the
+request were successful; if any failed, then the continuation is
+B<not> called.  If the request's actions were all successful, it
+merges together the L<Jifty::Result>s of current L<Jifty::Response>
+with those in the L<Jifty::Response> stored in the continuation.  In
+doing so, parameters are mapped using L<Jifty::Request::Mapper>.  This
+makes it possible to return values from continuations into arbitrary
+places.  For example:
+
+    % my $action = Jifty->web->new_action(class => 'AddTwoNumbers');
+    <% Jifty->web->form->start %>
+    <% $action->form_field( 'first_number' ) %>
+    <% $action->form_field( 'second_number',
+           default_value => {
+               request_argument => "number",
+           }
+       ) %>
+    <% Jifty->web->tangent(
+            url    => '/pagetwo',
+            label  => 'Enter a second number',
+            submit => $action
+       ) %>
+    <% Jifty->web->form->end %>
+
+..and in C</pagetwo>:
+
+    <% Jifty->web->form->start %>
+    <input type="text" name="number" />
+    %# We use as_button to tell Jifty that we want a button, not a link
+    <% Jifty->web->return( label => 'Pick', as_button => 1 ) %>
+    <% Jifty->web->form->end %>
+
+..and assuming that C<AddTwoNumbers>'s C<take_action> resembles:
+
+    sub take_action {
+        my $self = shift;
+        my $one = $self->argument_value("first_number");
+        my $two = $self->argument_value("second_number");
+        $self->result->message("Got " . ($one + $two));
+    }
+
+The first page renders the entry box for the first number; the second
+input is hidden because Jifty notices that it is based on a mapped
+value.  Pressing the button validates the action but does not complete
+running it.  At this point, the C<second_number> argument to the
+C<AddTwoNumbers> action has no real value -- however, it knows that it
+will, at the earliest possible opportunity, fill in its value from the
+"number" request parameter.  Jifty tangents to /pagetwo, where we
+enter and submit a "number" argument.  Control then returns to the
+original page, where the request mapper maps the "number" value into
+the C<second_number> argument of the C<AddTwoNumbers> action, which
+then runs.
+
+More complex mappings are possible, including grabbing the results of
+or arguments to actions.  This would make it possible, for instance,
+to use an action on the second page to validate the number before
+returning.  This is slightly different from placing a validator on the
+C<AddTwoNumbers> action, as that validator only gets called after
+control has already returned to the first page.
+
+=head2 As dispatcher rules
+
+The L<Jifty::Web/tangent> function is context-aware -- if it is called
+in void context, it immediately saves the continuation and jumps to
+the new url.  This is particularly useful, say, for authentication
+protection in C<before> blocks:
+
+    before '/protected' => sub {
+        Jifty->web->tangent( url => '/login' )
+          unless Jifty->web->current_user->id;
+    };
+
+And in the C</login> template:
+
+    % my $action = Jifty->web->new_action(class   => 'Login',
+    %                                     moniker => 'loginbox' );
+    <% Jifty->web->form->start %>
+    <% $action->form_field('username') %>
+    <% $action->form_field('password') %>
+    <% Jifty->web->return( to     => "/protected",
+                           label  => 'Login',
+                           submit =>  $action) %>
+    <% Jifty->web->form->end %>
+
+This establishes a button, which, if the C<Login> action is
+successful, calls the stored continuation, or, lacking one, redirects
+to C</protected>.
+
+=head1 GORY DETAILS
+
+Jifty's continuations are implemented in L<Jifty::Continuation>, which
+is very little more than a place to store a L<Jifty::Request> and its
+associated L<Jifty::Response>.
+
+The following diagram diagrams the stages of continuation handling,
+and their interaction with the dispatcher.  For clarity, the page
+region handling code is included, but page regions do not currently
+interact with continuation processing.
+
+                                /--------------\
+          +---------------------v-+            |
+          |........Request........|            |
+          +-|-------------------|-+            |
+            |                   |  RETURN  +---|---------------------+
+    /----\  |                   \----------> Replace request with    |
+    |  +-|--|-+ +==============+           | request in continuation |
+    |  |.v..v.---> SETUP rules |           +-------------------------+
+    |  |......| +==============+
+    |  |..D...|
+    |  |..I...| +~~~~~~~~~~~~~~~~~~~+      +-------------------------+
+    |  |..S...---> Validate actions |      | Store current request   |
+    |  |..P...| +~~~~~|~~~~~~~~~|~~~+ SAVE | and response, redirect  |
+    |  |..A...|       |         \----------> to new scope and URL    |
+    |  |..T...|       |                    +-------------------------+
+    |  |..C...| +~~~~~v~~~~~~~~~~~~~+
+    |  |..E...| |  Run actions      |      +-------------------------+
+    |  |..R...| +~~~~~~~~~~~~~~~|~~~+ CALL | Merge results into the  |
+    |  |......|                 \----------> continuation's results; |
+    |  |......|                            | redirect to return URL  |
+    |  |......| +==============+           +-------------------------+
+    |  |......---> RUN rules   |
+    |  |......| +=====|========+
+    |  |......|       |
+    |  |......|    +--v---------------+
+    |  |......|    | Show templates   |
+    |  |......|    +-------|----------+
+    |  |......|            |
+    |  |......|    +-------v----------+
+    |  |......|    | Show page region ---------------------\
+    |  |......|    +------------------+                    |
+    |  |......|                                            |
+    |  |......| +==============+                           |
+    |  |......---> AFTER rules |                           |
+    |  +------+ +==============+                           |
+    |                                                      |
+    \------------------------------------------------------/
+
+As shown in the diagram above, there are three different operations
+that continuations use.  The first is C<SAVE>, which is triggered by
+the query parameter L<J:CREATE>.  Continuations are saved after
+validating actions; the continuation itself is attached to the user's
+session object.
+
+The current saved continuation is automatically preserved across
+requests.  When the time comes to call the continuation, the C<CALL>
+operation is performed; this is usually triggered by the presence of
+the L<J:CALL> query parameter.  This causes the stored request to be
+query-mapped using L<Jifty::Request::Mapper>, but using the B<current>
+(not continuation!) request and response as the sources for mapping
+values.  Then, the result objects are merged, with results from the
+stored response taking precedence.  This new mapped request and new
+merged response are formed into a new continuation.
+
+In order to ensure that the browser's URL matches the URL of the
+request in the continuation, Jifty then does a redirect to the URL of
+the request stored in the continuation, starting the last continuation
+operation, the C<RETURN>.  When Jifty detects the C<RETURN> operation,
+most often by the presence of C<J:RETURN>, it loads the continuation
+and reads the stored request and response into the current request and
+response.
+
+=cut
+

Modified: jifty/trunk/lib/Jifty/Request.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Request.pm	(original)
+++ jifty/trunk/lib/Jifty/Request.pm	Mon Jul 10 17:20:47 2006
@@ -977,8 +977,9 @@
 
 The current continuation set by passing the parameter C<J:C>, which is
 set to the id of the continuation.  To create a new continuation, the
-parameter C<J:CREATE> is passed.  Calling a continuation is a ssimple
-as passing C<J:CALL> with the id of the continuation to call.
+parameter C<J:CREATE> is passed.  Calling a continuation is a simple
+as passing C<J:CALL> with the id of the continuation to call; this
+will redirect to the appropriate url, with L<J:RETURN> set.
 
 =head3 request options
 

Modified: jifty/trunk/lib/Jifty/Request/Mapper.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Request/Mapper.pm	(original)
+++ jifty/trunk/lib/Jifty/Request/Mapper.pm	Mon Jul 10 17:20:47 2006
@@ -47,6 +47,11 @@
 the result of the C<ACTION>.  C<ACTION> may either be a L<Jifty::Action>
 object, or a moniker thereof.
 
+=item C<< KEY => { request_argument => STRING } >>
+
+The C<KEY> will take on the value of the argument named C<STRING> from
+the request.
+
 =item C<< KEY => { argument => ACTION } >>
 
 The C<KEY> will take on the value of the argument named C<KEY> from
@@ -75,7 +80,11 @@
         if (ref $parameters{$key} eq "HASH") {
             my %mapping = %{$parameters{$key}};
 
-            for (grep {/(result(_of)?|argument(_to)?)/} keys %mapping) {
+            if ($mapping{request_argument}) {
+                $return{"J:M-$key"} = join("`","A", $mapping{request_argument});
+            }
+
+            for (grep {/^(result(_of)?|argument(_to)?)$/} keys %mapping) {
                 my $action  = $mapping{$_};
                 my $moniker = ref $action ? $action->moniker : $action;
                 my $name = $mapping{name} || $key;


More information about the Jifty-commit mailing list