Refactored next/last response/section related accessors and mutators

(and tests).

Added more documentation.
This commit is contained in:
Patrick Donelan 2009-02-06 01:55:33 +00:00
parent 669e986189
commit 90d314d2f1
2 changed files with 154 additions and 108 deletions

View file

@ -18,13 +18,38 @@ Package WebGUI::Asset::Wobject::Survey::ResponseJSON
=head1 DESCRIPTION
Helper class for WebGUI::Asset::Wobject::Survey. It manages data
from the user, sets the order of questions and answers in the survey,
based on branches, and gotos, and also handles expiring the survey
due to time limits.
Helper class for WebGUI::Asset::Wobject::Survey. "Reponse" in the context of
this Wobject refers to a Survey response (not a single Question response).
ie, this class represents the complete state of a user's response to a Survey instance.
Instances of this class contain a response property that can be serialized
as JSON to the database to allow for storage and retrieval of the complete state
of a survey response.
Survey instances that allow users to record multiple responses will persist multiple
instances of this class to the database (one per distinct user response).
Data stored in this object includes the order in which questions and answers are
presented to the user (surveyOrder), a snapshot of all completed questions
from the user (responses), the most recently answered question (lastResponse), the
number of questions answered (questionsAnswered) and the Survey start time (startTime).
This package is not intended to be used by any other Asset in WebGUI.
=head2 surveyOrder
Many methods in this class operate on the surveyOrder property.
This data strucutre is a deep set of arrays, similar in structure to
L<WebGUI::Asset::Wobject::Survey::SurveyJSON/Address Parameter>.
In general, the surveyOrder data structure looks like:
[ $sectionIndex, $questionIndex, [ $answerIndex1, $answerIndex2, ....]
There is one array element for every section and address in the survey. If there are
no questions, or no addresses, those array elements will not be present.
=cut
use strict;
@ -41,7 +66,7 @@ Object constructor.
=head3 $survey
A WebGUI::Asset::Wobject::Survey::SurveyJSON object that represents the current
A L<WebGUI::Asset::Wobject::Survey::SurveyJSON> object that represents the current
survey.
=head3 $json
@ -64,6 +89,8 @@ sub new {
# First define core members..
_survey => $survey,
_session => $survey->session,
# Store all properties that are (de)serialized to/from JSON in a private variable
_response => {
# Response hash defaults..
@ -85,10 +112,13 @@ sub new {
=head2 createSurveyOrder
Computes the order of Sections, Questions and Aswers for this Survey. The order is represented as
an array of addresses (see L<"Address Parameter">), and is stored in the surveyOrder property.
Computers and stores the order of Sections, Questions and Aswers for this Survey.
The order is represented as an array of addresses
(see L<WebGUI::Asset::Wobject::Survey::SurveyJSON/Address Parameter>),
and is stored in the surveyOrder property. See also the L<"surveyOrder"> accessor).
Questions and Answers that are set to be randomized are shuffled into a random order.
The survey order leaves a record or what the user was presented with.
The surveyOrder property is useful for keeping a record of what the user was presented with.
=cut
@ -136,12 +166,12 @@ sub createSurveyOrder {
=head2 session
Accessor method for the local WebGUI::Session reference
Accessor method for the WebGUI::Session reference
=cut
sub session {
my $self = shift;
my $self = shift;
return $self->{_session};
}
@ -149,7 +179,7 @@ sub session {
=head2 freeze
Serializes the object to JSON, after deleting the log and survey objects stored in it.
Serializes the internal perl hash representing the Response to a JSON string
=cut
@ -160,12 +190,10 @@ sub freeze {
#-------------------------------------------------------------------
#Has the survey timed out?
=head2 hasTimedOut ( $limit )
Checks to see whether this survey has timed out, based on the internally stored starting
time, and $limit.
time, and the suppied $limit value.
=head3 $limit
@ -174,100 +202,90 @@ How long the user has to take the survey, in minutes.
=cut
sub hasTimedOut{
my $self=shift;
my $limit = shift;
return 1 if($self->startTime() + ($limit * 60) < time() and $limit > 0);
return 0;
my $self = shift;
my ($limit) = validate_pos(@_, {type => SCALAR});
return $limit > 0 && $self->startTime + $limit * 60 < time;
}
#-------------------------------------------------------------------
#the index of the last surveyOrder entry shown
=head2 lastResponse ([ $responseIndex ])
Mutator for the index of the last surveyOrder entry shown. With no arguments,
returns the lastResponse index.
Mutator. The lastResponse property represents the index of the most recent surveyOrder entry shown.
=head3 $responseIndex
This method returns (and optionally sets) the value of lastResponse.
If defined, sets the lastResponse to $responseIndex.
=head3 $responseIndex (optional)
If defined, lastResponse is set to $responseIndex.
=cut
sub lastResponse {
my $self = shift;
my $res = shift;
if ( defined $res ) {
$self->response->{lastResponse} = $res;
}
else {
return $self->response->{lastResponse};
my ($responseIndex) = validate_pos(@_, {type => SCALAR, optional => 1});
if ( defined $responseIndex ) {
$self->response->{lastResponse} = $responseIndex;
}
return $self->response->{lastResponse};
}
#-------------------------------------------------------------------
=head2 questionsAnswered ([ $questionsAnswered ])
Mutator for the number of questions answered. With no arguments,
does a set.
Mutator for the number of questions answered.
Returns (and optionally sets) the value of questionsAnswered.
=head3 $questionsAnswered.
=head3 $questionsAnswered (optional)
If defined, increments the number of questions by $questionsAnswered
=cut
sub questionsAnswered {
my $self = shift;
my $answered = shift;
if ( defined $answered ) {
$self->response->{questionsAnswered} += $answered;
}
else {
return $self->response->{questionsAnswered};
my $self = shift;
my ($questionsAnswered) = validate_pos(@_, {type => SCALAR, optional => 1});
if ( defined $questionsAnswered ) {
$self->response->{questionsAnswered} += $questionsAnswered;
}
return $self->response->{questionsAnswered};
}
#-------------------------------------------------------------------
=head2 startTime ([ $newStartTime ])
=head2 startTime ([ $startTime ])
Mutator for the time the user began the survey. With no arguments,
returns the startTime.
Mutator for the time the user began the survey.
Returns (and optionally sets) the value of startTime.
=head3 $newStarttime
=head3 $startTime (optional)
If defined, sets the starting time to $newStartTime.
If defined, sets the starting time to $startTime.
=cut
sub startTime {
my $self = shift;
my $newTime = shift;
if ( defined $newTime ) {
$self->response->{startTime} = $newTime;
}
else {
return $self->response->{startTime};
my ($startTime) = validate_pos(@_, {type => SCALAR, optional => 1});
if ( defined $startTime ) {
$self->response->{startTime} = $startTime;
}
return $self->response->{startTime};
}
#-------------------------------------------------------------------
#array of addresses in which the survey should be presented
=head2 surveyOrder
Accessor for the survey order data structure. It is a deep set of arrays, similar in
structure to a WebGUI::Asset::Wobject::Survey::SurveyJSON address.
[ $sectionIndex, $questionIndex, [ $answerIndex1, $answerIndex2, ....]
There is one array element for every section and address in the survey.
If there are no questions, or no addresses, those array elements will not be present.
Accessor for surveyOrder (see L<"surveyOrder">).
N.B. Use L<"createSurveyOrder"> to modify surveyOrder.
=cut
@ -278,47 +296,75 @@ sub surveyOrder {
#-------------------------------------------------------------------
=head2 nextSectionId
=head2 nextResponse
Relative to the surveyOrder and the lastResponse index, get the index of the
next section. Note, based on the number of questions in an section, this can
be the same as the current section index.
Returns the index of the next item that should be shown to the user,
that is, the index of the next item in the L<"surveyOrder"> array,
e.g. L<"lastResponse"> + 1.
=cut
sub nextSectionId {
sub nextResponse {
my $self = shift;
return $self->lastResponse + 1;
}
#-------------------------------------------------------------------
=head2 nextResponseSectionIndex
Returns the Section index of the next item that should be
shown to the user, that is, the next item in the L<"surveyOrder"> array
relative to L<"lastResponse">.
We go to the effort of calling this property "nextResponseSectionIndex"
rather than just "nextSectionIndex" to emphasize that this property is
distinct from the "next" section index in the Survey. For example, in
a Section with multiple Questions, the value of nextResponseSectionIndex
will be the same value (the current section index) for all Questions
except the last Question.
=cut
sub nextResponseSectionIndex {
my $self = shift;
return undef if $self->surveyEnd();
return $self->surveyOrder->[ $self->lastResponse + 1 ]->[0];
return $self->surveyOrder->[ $self->nextResponse ]->[0];
}
#-------------------------------------------------------------------
=head2 nextSection
=head2 nextResponseSection
Relative to the surveyOrder and the lastResponse index, gets the next section.
Note, based on the number of questions in a section, this can be the same as
the current section.
Returns the Section corresponding to the next item that should be
shown to the user, that is, the next item in the L<"surveyOrder"> array
relative to L<"lastResponse">.
As with L<"nextResponseSectionIndex">, we go to the effort of calling this property "nextResponseSection"
rather than just "nextSection" to emphasize that this property is
distinct from the "next" section in the Survey.
=cut
sub nextSection {
sub nextResponseSection {
my $self = shift;
return {} if $self->surveyEnd();
return $self->survey->section( [ $self->surveyOrder->[ $self->lastResponse + 1 ]->[0] ] );
return $self->survey->section( [ $self->nextResponseSectionIndex ] );
}
#-------------------------------------------------------------------
=head2 currentSection
=head2 lastResponseSectionIndex
Relative to the surveyOrder and the lastResponse index, get the current section.
Returns the Section index of the last item that was shown to the user,
based on the L<"surveyOrder"> array and L<"lastResponse">.
=cut
sub currentSection {
sub lastResponseSectionIndex {
my $self = shift;
return $self->survey->section( [ $self->surveyOrder->[ $self->lastResponse ]->[0] ] );
return $self->surveyOrder->[ $self->lastResponse ]->[0];
}
#-------------------------------------------------------------------
@ -343,7 +389,7 @@ A hash ref of form param data. Each element will look like:
"__aid__comment" => "answer comment",
}
where __qid__ is a question id, as described in L<nextQuestions>, and __aid__ is an
where __qid__ is a question id, as described in L<"nextQuestions">, and __aid__ is an
answer id, also described there.
=head3 terminal processing
@ -404,7 +450,7 @@ sub recordResponses {
my $goto;
my $gotoExpression;
my $section = $self->nextSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions
my $section = $self->nextResponseSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions
if ( $section->{terminal} ) {
$sterminal = 1;
@ -413,7 +459,7 @@ sub recordResponses {
#There were no questions in the section just displayed, so increment the lastResponse by one
if ( ref $questions ne 'ARRAY' ) {
$self->lastResponse( $self->lastResponse + 1 );
$self->lastResponse( $self->nextResponse );
return [ $sterminal, $terminalUrl ];
}
@ -468,7 +514,7 @@ sub recordResponses {
$terminal = 0;
}
if($sterminal and $self->nextSection != $self->currentSection){
if($sterminal and $self->nextResponseSectionIndex != $self->lastResponseSectionIndex){
$terminal = 1;
}
@ -691,7 +737,7 @@ sub getPreviousAnswer {
Returns an array ref of the next questions in the survey. The number of questions
returned is set by the questionsPerPage property of the next section, as determined
by nextSectionId rather than logical section ordering.
by nextResponseSectionIndex rather than logical section ordering.
If no questions are available, then it returns an empty array ref.
@ -713,12 +759,12 @@ sub nextQuestions {
return [] if $self->surveyEnd;
my $nextSectionId = $self->nextSectionId;
my $nextResponseSectionIndex = $self->nextResponseSectionIndex;
my $qPerPage = $self->survey->section( [ $self->nextSectionId ] )->{questionsPerPage};
my $qPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage};
#load Previous answer text
my $section = $self->nextSection();
my $section = $self->nextResponseSection();
$section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg;
my $questions;
@ -727,7 +773,7 @@ sub nextQuestions {
next
if ( !exists $$qAddy[1] ); #skip this if it doesn't have a question (for sections with no questions)
if ( $$qAddy[0] != $nextSectionId ) {
if ( $$qAddy[0] != $nextResponseSectionIndex ) {
last;
}
my %question = %{ $self->survey->question( [ $$qAddy[0], $$qAddy[1] ] ) };