[Rt-commit] rt branch, 4.2/lifecycle-roles, created. rt-4.1.5-238-ge0921ff

Thomas Sibley trs at bestpractical.com
Tue Jan 8 19:09:36 EST 2013


The branch, 4.2/lifecycle-roles has been created
        at  e0921ff1cfa1badabc6c9731fc54dd9c4e3d9ceb (commit)

- Log -----------------------------------------------------------------
commit 705acec3e1c295dcd8ad8ab3e6f9f8ad51fbf6b5
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 2 17:14:54 2013 -0800

    Avoid "Exiting eval via next" warnings
    
    These aren't currently triggered by any tests, but occur if objects are
    referred to by Name in the database instead of id and records support a
    corresponding ColumnNameObj method.

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index 1e03f7d..349970c 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -881,11 +881,13 @@ sub Update {
         do {
             no warnings "uninitialized";
             local $@;
-            eval {
+            my $name = eval {
                 my $object = $attribute . "Obj";
-                my $name = $self->$object->Name;
-                next if $name eq $value || $name eq ($value || 0);
+                $self->$object->Name;
             };
+            unless ($@) {
+                next if $name eq $value || $name eq ($value || 0);
+            }
 
             my $current = $self->$attribute();
             # RT::Queue->Lifecycle returns a Lifecycle object instead of name

commit 65ab3617c5c24f3b3c07ce14fd2d074ba691c095
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jan 3 15:05:51 2013 -0800

    Set the new status *after* setting the new queue in RT::Ticket->SetQueue
    
    This removes the need for an ugly caller() check in ValidateStatus to
    force pass if the callstack contains SetQueue anywhere.  The behaviour
    for Status now matches the other updates which depend on a successful
    queue change first.

diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 095cc18..df444ae 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1163,29 +1163,29 @@ sub SetQueue {
             unless $new_status;
     }
 
-    if ( $new_status ) {
-        my $clone = RT::Ticket->new( RT->SystemUser );
-        $clone->Load( $self->Id );
-        unless ( $clone->Id ) {
-            return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) );
-        }
-
-        my ($val, $msg) = $clone->_SetStatus(
-            Lifecycle         => $old_lifecycle,
-            NewLifecycle      => $new_lifecycle,
-            Status            => $new_status,
-            RecordTransaction => 0,
-        );
-        $RT::Logger->error( 'Status change failed on queue change: '. $msg )
-            unless $val;
-    }
-
     my ($status, $msg) = $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() );
 
     if ( $status ) {
         # Clear the queue object cache;
         $self->{_queue_obj} = undef;
 
+        if ( $new_status ) {
+            my $clone = RT::Ticket->new( RT->SystemUser );
+            $clone->Load( $self->Id );
+            unless ( $clone->Id ) {
+                return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) );
+            }
+
+            my ($val, $msg) = $clone->_SetStatus(
+                Lifecycle         => $old_lifecycle,
+                NewLifecycle      => $new_lifecycle,
+                Status            => $new_status,
+                RecordTransaction => 0,
+            );
+            RT->Logger->error("Status change failed on queue change: $msg")
+                unless $val;
+        }
+
         # Untake the ticket if we have no permissions in the new queue
         unless ( $self->OwnerObj->HasRight( Right => 'OwnTicket', Object => $NewQueueObj ) ) {
             my $clone = RT::Ticket->new( RT->SystemUser );
@@ -1203,6 +1203,11 @@ sub SetQueue {
             my ($status, $msg) = $reminder->_Set( Field => 'Queue', Value => $NewQueueObj->Id(), RecordTransaction => 0 );
             $RT::Logger->error('Queue change failed for reminder #' . $reminder->Id . ': ' . $msg) unless $status;
         }
+
+        # Pick up any changes made by the clones above
+        $self->Load( $self->id );
+        RT->Logger->error("Unable to reload ticket #" . $self->id)
+            unless $self->id;
     }
 
     return ($status, $msg);
@@ -2402,11 +2407,6 @@ sub ValidateStatus {
     #Make sure the status passed in is valid
     return 1 if $self->QueueObj->IsValidStatus($status);
 
-    my $i = 0;
-    while ( my $caller = (caller($i++))[3] ) {
-        return 1 if $caller eq 'RT::Ticket::SetQueue';
-    }
-
     return 0;
 }
 

commit 9b11d2f611391b5d3859c94917d1ee3782993775
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 2 17:19:15 2013 -0800

    Rename RT::Queue->Lifecycle to LifecycleObj for consistency
    
    All other columns which reference other records use ColumnName for the
    database value and ColumnNameObj for the inflated object.  Mimic the
    same methods in RT::Ticket as well to avoid confusion, despite
    RT::Ticket not having a Lifecycle column itself.
    
    This reverts a small part of e904afe which added special handling for
    the Lifecycle column to RT::Record->Update.  With the existence of a
    LifecycleObj method, the previously existing Name handling works
    correctly.
    
    A usage of $QueueObj->Lifecycle in share/html/Admin/Queues/Modify.html
    is left as-is to fix a bug: the intention was always to use the
    lifecycle name as the current value, but instead an object was passed to
    share/html/Widget/Select:InputOnly.  Mason helpfully coerced the object
    into an array, which by happenstance led to the correct behaviour.

diff --git a/etc/upgrade/4.0.0rc7/content b/etc/upgrade/4.0.0rc7/content
index 7b9e9c1..bad39d4 100644
--- a/etc/upgrade/4.0.0rc7/content
+++ b/etc/upgrade/4.0.0rc7/content
@@ -12,7 +12,7 @@ our @Initial = (
             return 1;
         }
 
-        return 1 if $queue->Lifecycle->Name eq 'approvals';
+        return 1 if $queue->Lifecycle eq 'approvals';
 
         my ($status, $msg) = $queue->SetLifecycle('approvals');
         unless ( $status ) {
diff --git a/lib/RT/Action/AutoOpen.pm b/lib/RT/Action/AutoOpen.pm
index 5112dba..72da9e5 100644
--- a/lib/RT/Action/AutoOpen.pm
+++ b/lib/RT/Action/AutoOpen.pm
@@ -87,7 +87,7 @@ sub Prepare {
 
     # no change if the ticket is in initial status and the message is a mail
     # from a requestor
-    return 1 if $ticket->Lifecycle->IsInitial($ticket->Status)
+    return 1 if $ticket->LifecycleObj->IsInitial($ticket->Status)
         && $self->TransactionObj->IsInbound;
 
     if ( my $msg = $self->TransactionObj->Message->First ) {
diff --git a/lib/RT/Action/SetStatus.pm b/lib/RT/Action/SetStatus.pm
index 571ac05..186c372 100644
--- a/lib/RT/Action/SetStatus.pm
+++ b/lib/RT/Action/SetStatus.pm
@@ -101,7 +101,7 @@ sub Prepare {
     my $self = shift;
 
     my $ticket = $self->TicketObj;
-    my $lifecycle = $ticket->Lifecycle;
+    my $lifecycle = $ticket->LifecycleObj;
     my $status = $ticket->Status;
 
     my $argument = $self->Argument;
diff --git a/lib/RT/Approval/Rule/Passed.pm b/lib/RT/Approval/Rule/Passed.pm
index 19f790e..fb5dcde 100644
--- a/lib/RT/Approval/Rule/Passed.pm
+++ b/lib/RT/Approval/Rule/Passed.pm
@@ -96,7 +96,7 @@ sub Commit {
     $top->Correspond( MIMEObj => $template->MIMEObj );
 
     if ($passed) {
-        my $new_status = $top->Lifecycle->DefaultStatus('approved') || 'open';
+        my $new_status = $top->LifecycleObj->DefaultStatus('approved') || 'open';
         if ( $new_status ne $top->Status ) {
             $top->SetStatus( $new_status );
         }
diff --git a/lib/RT/Approval/Rule/Rejected.pm b/lib/RT/Approval/Rule/Rejected.pm
index 387b763..6021991 100644
--- a/lib/RT/Approval/Rule/Rejected.pm
+++ b/lib/RT/Approval/Rule/Rejected.pm
@@ -75,7 +75,7 @@ sub Commit {    # XXX: from custom prepare code
 
         $rejected->Correspond( MIMEObj => $template->MIMEObj );
         $rejected->SetStatus(
-            Status => $rejected->Lifecycle->DefaultStatus('denied') || 'rejected',
+            Status => $rejected->LifecycleObj->DefaultStatus('denied') || 'rejected',
             Force  => 1,
         );
     }
diff --git a/lib/RT/Condition/StatusChange.pm b/lib/RT/Condition/StatusChange.pm
index f3a267b..4232da9 100644
--- a/lib/RT/Condition/StatusChange.pm
+++ b/lib/RT/Condition/StatusChange.pm
@@ -118,7 +118,7 @@ sub IsApplicable {
         return 0;
     }
 
-    my $lifecycle = $self->TicketObj->Lifecycle;
+    my $lifecycle = $self->TicketObj->LifecycleObj;
     if ( $new_must_be ) {
         return 0 unless grep lc($new) eq lc($_),
             map {m/^(initial|active|inactive)$/i? $lifecycle->Valid(lc $_): $_ }
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 8f2f46b..e1959b5 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2582,7 +2582,7 @@ sub ProcessTicketReminders {
 
     if ( $args->{'update-reminders'} ) {
         while ( my $reminder = $reminder_collection->Next ) {
-            my $resolve_status = $reminder->Lifecycle->ReminderStatusOnResolve;
+            my $resolve_status = $reminder->LifecycleObj->ReminderStatusOnResolve;
             my ( $status, $msg, $old_subject, @subresults );
             if (   $reminder->Status ne $resolve_status
                 && $args->{ 'Complete-Reminder-' . $reminder->id } )
diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm
index 5a459b9..30c8a7f 100644
--- a/lib/RT/Queue.pm
+++ b/lib/RT/Queue.pm
@@ -241,14 +241,13 @@ sub RightCategories {
 }
 
 
-sub Lifecycle {
+sub LifecycleObj {
     my $self = shift;
     unless (ref $self && $self->id) {
         return RT::Lifecycle->Load( Type => 'ticket')
     }
 
-    my $name = $self->_Value( Lifecycle => @_ );
-    $name ||= 'default';
+    my $name = $self->Lifecycle || 'default';
 
     my $res = RT::Lifecycle->Load( Name => $name );
     unless ( $res ) {
@@ -291,7 +290,7 @@ Returns an array of all ActiveStatuses for this queue
 
 sub ActiveStatusArray {
     my $self = shift;
-    return $self->Lifecycle->Valid('initial', 'active');
+    return $self->LifecycleObj->Valid('initial', 'active');
 }
 
 =head2 InactiveStatusArray
@@ -302,7 +301,7 @@ Returns an array of all InactiveStatuses for this queue
 
 sub InactiveStatusArray {
     my $self = shift;
-    return $self->Lifecycle->Inactive;
+    return $self->LifecycleObj->Inactive;
 }
 
 =head2 StatusArray
@@ -313,7 +312,7 @@ Returns an array of all statuses for this queue
 
 sub StatusArray {
     my $self = shift;
-    return $self->Lifecycle->Valid( @_ );
+    return $self->LifecycleObj->Valid( @_ );
 }
 
 =head2 IsValidStatus value
@@ -324,7 +323,7 @@ Returns true if value is a valid status.  Otherwise, returns 0.
 
 sub IsValidStatus {
     my $self  = shift;
-    return $self->Lifecycle->IsValid( shift );
+    return $self->LifecycleObj->IsValid( shift );
 }
 
 =head2 IsActiveStatus value
@@ -335,7 +334,7 @@ Returns true if value is a Active status.  Otherwise, returns 0
 
 sub IsActiveStatus {
     my $self  = shift;
-    return $self->Lifecycle->IsValid( shift, 'initial', 'active');
+    return $self->LifecycleObj->IsValid( shift, 'initial', 'active');
 }
 
 
@@ -349,7 +348,7 @@ Returns true if value is a Inactive status.  Otherwise, returns 0
 
 sub IsInactiveStatus {
     my $self  = shift;
-    return $self->Lifecycle->IsInactive( shift );
+    return $self->LifecycleObj->IsInactive( shift );
 }
 
 
diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index 349970c..bcda8f4 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -889,11 +889,8 @@ sub Update {
                 next if $name eq $value || $name eq ($value || 0);
             }
 
-            my $current = $self->$attribute();
-            # RT::Queue->Lifecycle returns a Lifecycle object instead of name
-            $current = eval { $current->Name } if ref $current;
-            next if $value eq $current;
-            next if ( $value || 0 ) eq $current;
+            next if $value eq $self->$attribute();
+            next if ($value || 0) eq $self->$attribute();
         };
 
         $new_values{$attribute} = $value;
diff --git a/lib/RT/Reminders.pm b/lib/RT/Reminders.pm
index a901384..a3d3359 100644
--- a/lib/RT/Reminders.pm
+++ b/lib/RT/Reminders.pm
@@ -149,7 +149,7 @@ sub Open {
     my $reminder = shift;
 
     my ( $status, $msg ) =
-      $reminder->SetStatus( $reminder->Lifecycle->ReminderStatusOnOpen );
+      $reminder->SetStatus( $reminder->LifecycleObj->ReminderStatusOnOpen );
     $self->TicketObj->_NewTransaction(
         Type => 'OpenReminder',
         Field => 'RT::Ticket',
@@ -162,7 +162,7 @@ sub Resolve {
     my $self = shift;
     my $reminder = shift;
     my ( $status, $msg ) =
-      $reminder->SetStatus( $reminder->Lifecycle->ReminderStatusOnResolve );
+      $reminder->SetStatus( $reminder->LifecycleObj->ReminderStatusOnResolve );
     $self->TicketObj->_NewTransaction(
         Type => 'ResolveReminder',
         Field => 'RT::Ticket',
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index df444ae..e1e28b9 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -262,7 +262,7 @@ sub Create {
             $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name));
     }
 
-    my $cycle = $QueueObj->Lifecycle;
+    my $cycle = $QueueObj->LifecycleObj;
     unless ( defined $args{'Status'} && length $args{'Status'} ) {
         $args{'Status'} = $cycle->DefaultOnCreate;
     }
@@ -1152,8 +1152,8 @@ sub SetQueue {
     }
 
     my $new_status;
-    my $old_lifecycle = $self->Lifecycle;
-    my $new_lifecycle = $NewQueueObj->Lifecycle;
+    my $old_lifecycle = $self->LifecycleObj;
+    my $new_lifecycle = $NewQueueObj->LifecycleObj;
     if ( $old_lifecycle->Name ne $new_lifecycle->Name ) {
         unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) {
             return ( 0, $self->loc("There is no mapping for statuses between these queues. Contact your system administrator.") );
@@ -1312,14 +1312,24 @@ sub ResolvedObj {
 
 =head2 Lifecycle
 
+Returns the L<RT::Lifecycle/Name> for this ticket.
+
+=cut
+
+sub Lifecycle {
+    return shift->LifecycleObj->Name;
+}
+
+=head2 LifecycleObj
+
 Returns the L<RT::Lifecycle> for this ticket, which is picked up from
 the ticket's current queue.
 
 =cut
 
-sub Lifecycle {
+sub LifecycleObj {
     my $self = shift;
-    return $self->QueueObj->Lifecycle;
+    return $self->QueueObj->LifecycleObj;
 }
 
 =head2 FirstActiveStatus
@@ -1334,7 +1344,7 @@ This is used in L<RT::Action::AutoOpen>, for instance.
 sub FirstActiveStatus {
     my $self = shift;
 
-    my $lifecycle = $self->Lifecycle;
+    my $lifecycle = $self->LifecycleObj;
     my $status = $self->Status;
     my @active = $lifecycle->Active;
     # no change if no active statuses in the lifecycle
@@ -1359,7 +1369,7 @@ This is used in resolve action in UnsafeEmailCommands, for instance.
 sub FirstInactiveStatus {
     my $self = shift;
 
-    my $lifecycle = $self->Lifecycle;
+    my $lifecycle = $self->LifecycleObj;
     my $status = $self->Status;
     my @inactive = $lifecycle->Inactive;
     # no change if no inactive statuses in the lifecycle
@@ -2042,7 +2052,7 @@ sub _MergeInto {
     }
 
 
-    my $force_status = $self->Lifecycle->DefaultOnMerge;
+    my $force_status = $self->LifecycleObj->DefaultOnMerge;
     if ( $force_status && $force_status ne $self->__Value('Status') ) {
         my ( $status_val, $status_msg )
             = $self->__Set( Field => 'Status', Value => $force_status );
@@ -2438,7 +2448,7 @@ sub SetStatus {
     $args{SetStarted} = 1 unless exists $args{SetStarted};
 
 
-    my $lifecycle = $self->Lifecycle;
+    my $lifecycle = $self->LifecycleObj;
 
     my $new = $args{'Status'};
     unless ( $lifecycle->IsValid( $new ) ) {
@@ -2475,7 +2485,7 @@ sub _SetStatus {
         Status => undef,
         SetStarted => 1,
         RecordTransaction => 1,
-        Lifecycle => $self->Lifecycle,
+        Lifecycle => $self->LifecycleObj,
         @_,
     );
     $args{NewLifecycle} ||= $args{Lifecycle};
@@ -2533,7 +2543,7 @@ Takes no arguments. Marks this ticket for garbage collection
 
 sub Delete {
     my $self = shift;
-    unless ( $self->Lifecycle->IsValid('deleted') ) {
+    unless ( $self->LifecycleObj->IsValid('deleted') ) {
         return (0, $self->loc('Delete operation is disabled by lifecycle configuration') ); #loc
     }
     return ( $self->SetStatus('deleted') );
diff --git a/share/html/Approvals/Elements/Approve b/share/html/Approvals/Elements/Approve
index d557e2b..b23bc89 100644
--- a/share/html/Approvals/Elements/Approve
+++ b/share/html/Approvals/Elements/Approve
@@ -106,5 +106,5 @@ $ticket => undef
 </%ARGS>
 <%INIT>
 my $status = $ticket->Status;
-my $inactive = $ticket->Lifecycle->IsInactive( $status );
+my $inactive = $ticket->LifecycleObj->IsInactive( $status );
 </%INIT>
diff --git a/share/html/Elements/QueueSummaryByLifecycle b/share/html/Elements/QueueSummaryByLifecycle
index 79e3a59..9605840 100644
--- a/share/html/Elements/QueueSummaryByLifecycle
+++ b/share/html/Elements/QueueSummaryByLifecycle
@@ -99,7 +99,7 @@ $m->callback( CallbackName => 'Filter', Queues => \@queues );
     {  id          => $_->Id,
        Name        => $_->Name,
        Description => $_->Description || '',
-       Lifecycle   => $_->Lifecycle->Name,
+       Lifecycle   => $_->Lifecycle,
     }
 } grep $_, @queues;
 
diff --git a/share/html/Elements/QueueSummaryByStatus b/share/html/Elements/QueueSummaryByStatus
index cd8b277..c1a6114 100644
--- a/share/html/Elements/QueueSummaryByStatus
+++ b/share/html/Elements/QueueSummaryByStatus
@@ -97,7 +97,7 @@ $m->callback( CallbackName => 'Filter', Queues => \@queues );
     {  id          => $_->Id,
        Name        => $_->Name,
        Description => $_->Description || '',
-       Lifecycle   => $_->Lifecycle->Name,
+       Lifecycle   => $_->Lifecycle,
     }
 } grep $_, @queues;
 
diff --git a/share/html/Elements/RT__Queue/ColumnMap b/share/html/Elements/RT__Queue/ColumnMap
index 6b52763..926e6c0 100644
--- a/share/html/Elements/RT__Queue/ColumnMap
+++ b/share/html/Elements/RT__Queue/ColumnMap
@@ -87,7 +87,7 @@ my $COLUMN_MAP = {
     Lifecycle => {
         title => 'Lifecycle',
         attribute => 'Lifecycle',
-        value => sub { return $_[0]->Lifecycle->Name },
+        value => sub { return $_[0]->Lifecycle },
     },
     ScripStage => {
         title => 'Stage', # loc
diff --git a/share/html/Elements/SelectStatus b/share/html/Elements/SelectStatus
index fa0d8e5..744e84c 100644
--- a/share/html/Elements/SelectStatus
+++ b/share/html/Elements/SelectStatus
@@ -71,7 +71,7 @@ if ( @Statuses ) {
     $statuses_by_lifecycle{''} = \@Statuses;
 } else {
     if ( $Object ) {
-        my $lifecycle = $Object->Lifecycle;
+        my $lifecycle = $Object->LifecycleObj;
         if ($Object->_Accessible("Status", "read")) {
             my $current = $Object->Status;
             my @status;
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 03f9a2b..caa9798 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -648,7 +648,7 @@ my $build_main_nav = sub {
                     && $obj->HasUnresolvedDependencies;
 
                 my $current   = $obj->Status;
-                my $lifecycle = $obj->Lifecycle;
+                my $lifecycle = $obj->LifecycleObj;
                 my $i         = 1;
                 foreach my $info ( $lifecycle->Actions($current) ) {
                     my $next = $info->{'to'};
diff --git a/share/html/Ticket/Elements/Reminders b/share/html/Ticket/Elements/Reminders
index 906074b..d2b48d2 100644
--- a/share/html/Ticket/Elements/Reminders
+++ b/share/html/Ticket/Elements/Reminders
@@ -55,7 +55,7 @@ $ShowSave => 1
 <%init>
 
 $Ticket = LoadTicket($id) if ($id);
-my $resolve_status = $Ticket->Lifecycle->ReminderStatusOnResolve;
+my $resolve_status = $Ticket->LifecycleObj->ReminderStatusOnResolve;
 
 my $count_reminders = RT::Reminders->new($session{'CurrentUser'});
 $count_reminders->Ticket($Ticket->id);
@@ -154,10 +154,10 @@ $Index
 <td class="entry">
 % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
 <input name="Complete-Reminder-<% $Reminder->id %>" type="hidden" 
-value=<% $Reminder->Status eq $Reminder->Lifecycle->ReminderStatusOnResolve ? 1 : 0 %> />
+value=<% $Reminder->Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 1 : 0 %> />
 % }
 
-<input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %>
+<input type="checkbox" value="1" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %>
 % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
 disabled="disabled" 
 % }
@@ -197,9 +197,9 @@ $Index
 <td class="collection-as-table">
 % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
 <input name="Complete-Reminder-<% $Reminder->id %>" type="hidden" 
-value=<% $Reminder->Status eq $Reminder->Lifecycle->ReminderStatusOnResolve ? 1 : 0 %> />
+value=<% $Reminder->Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 1 : 0 %> />
 % }
-<input type="checkbox" value="1" id="Complete-Reminder-<% $Reminder->id %>" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %>
+<input type="checkbox" value="1" id="Complete-Reminder-<% $Reminder->id %>" name="Complete-Reminder-<% $Reminder->id %>" <% $Reminder->Status eq $Reminder->LifecycleObj->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %>
 % unless ( $Reminder->CurrentUserHasRight('ModifyTicket') ) {
 disabled="disabled" 
 % }
diff --git a/share/html/Ticket/Elements/SelectStatus b/share/html/Ticket/Elements/SelectStatus
index f1e3cad..7eebe5f 100644
--- a/share/html/Ticket/Elements/SelectStatus
+++ b/share/html/Ticket/Elements/SelectStatus
@@ -57,7 +57,7 @@ my @Lifecycles;
 for my $id (keys %Queues) {
     my $queue = RT::Queue->new($session{'CurrentUser'});
     $queue->Load($id);
-    push @Lifecycles, $queue->Lifecycle if $queue->id;
+    push @Lifecycles, $queue->LifecycleObj if $queue->id;
 }
 
 if ($TicketObj) {
@@ -69,7 +69,7 @@ if ($TicketObj) {
     }
 } elsif ($QueueObj) {
     $ARGS{DefaultValue} = 0;
-    $ARGS{Default} ||= $DECODED_ARGS->{Status} || $QueueObj->Lifecycle->DefaultOnCreate;
+    $ARGS{Default} ||= $DECODED_ARGS->{Status} || $QueueObj->LifecycleObj->DefaultOnCreate;
 }
 </%INIT>
 <%ARGS>
diff --git a/t/lifecycles/basics.t b/t/lifecycles/basics.t
index b4828f7..e18bea3 100644
--- a/t/lifecycles/basics.t
+++ b/t/lifecycles/basics.t
@@ -17,7 +17,7 @@ my $tstatus = sub {
 
 diag "check basic API";
 {
-    my $schema = $general->Lifecycle;
+    my $schema = $general->LifecycleObj;
     isa_ok($schema, 'RT::Lifecycle');
     is $schema->Name, 'default', "it's a default schema";
     is_deeply [$schema->Valid],
@@ -78,7 +78,7 @@ diag "check status input on create";
 
     my $valid = 1;
     foreach ( @form_values ) {
-        next if $general->Lifecycle->IsValid($_);
+        next if $general->LifecycleObj->IsValid($_);
         $valid = 0;
         diag("$_ doesn't appear to be a valid status, but it was in the form");
     }
diff --git a/t/lifecycles/dates.t b/t/lifecycles/dates.t
index 80c24fc..af81d9e 100644
--- a/t/lifecycles/dates.t
+++ b/t/lifecycles/dates.t
@@ -23,11 +23,11 @@ my $tstatus = sub {
 
 diag "check basic API";
 {
-    my $schema = $general->Lifecycle;
+    my $schema = $general->LifecycleObj;
     isa_ok($schema, 'RT::Lifecycle');
     is $schema->Name, 'default', "it's a default schema";
 
-    $schema = $delivery->Lifecycle;
+    $schema = $delivery->LifecycleObj;
     isa_ok($schema, 'RT::Lifecycle');
     is $schema->Name, 'delivery', "it's a delivery schema";
 }

commit 29339864d289cdf212fef5a7ad35186d97b65258
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 2 17:46:42 2013 -0800

    Refactor Lifecycle methods and behaviours into roles
    
    The first use of roles in RT!  With support for extensions adding
    lifecycles on arbitrary object types, it's extremely useful to abstract
    the common behaviour patterns into roles that can be consumed by many
    classes.
    
    Role::Basic provides a very straightforward implementation without the
    prerequisite of shoehorning all our classes into Moose's view of the
    world.  The most notable missing feature is parameterized roles.
    Instead, roles can require a method in the consuming class which returns
    the value of the "parameter".  This does limit individual role
    application to one-per-class instead of potentially many instances of
    the same parameterized role, but it is a sufficient workaround for now.

diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm
index 30c8a7f..99829c9 100644
--- a/lib/RT/Queue.pm
+++ b/lib/RT/Queue.pm
@@ -69,8 +69,12 @@ use strict;
 use warnings;
 use base 'RT::Record';
 
+use Role::Basic 'with';
+with "RT::Role::Record::Lifecycle";
+
 sub Table {'Queues'}
 
+sub LifecycleType { "ticket" }
 
 
 use RT::Groups;
@@ -155,7 +159,6 @@ $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
 
 __PACKAGE__->AddRights(%$RIGHTS);
 __PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
-require RT::Lifecycle;
 
 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
 
@@ -240,122 +243,6 @@ sub RightCategories {
     return $RIGHT_CATEGORIES;
 }
 
-
-sub LifecycleObj {
-    my $self = shift;
-    unless (ref $self && $self->id) {
-        return RT::Lifecycle->Load( Type => 'ticket')
-    }
-
-    my $name = $self->Lifecycle || 'default';
-
-    my $res = RT::Lifecycle->Load( Name => $name );
-    unless ( $res ) {
-        $RT::Logger->error("Lifecycle '$name' for queue '".$self->Name."' doesn't exist");
-        return RT::Lifecycle->Load( Name => 'default');
-    }
-    return $res;
-}
-
-sub SetLifecycle {
-    my $self = shift;
-    my $value = shift || 'default';
-
-    return ( 0, $self->loc( '[_1] is not a valid lifecycle', $value ) )
-      unless $self->ValidateLifecycle($value);
-
-    return $self->_Set( Field => 'Lifecycle', Value => $value, @_ );
-}
-
-=head2 ValidateLifecycle NAME
-
-Takes a lifecycle name. Returns true if it's an ok name and such
-lifecycle is configured. Returns undef otherwise.
-
-=cut
-
-sub ValidateLifecycle {
-    my $self = shift;
-    my $value = shift;
-    return undef unless RT::Lifecycle->Load( Name => $value );
-    return 1;
-}
-
-
-=head2 ActiveStatusArray
-
-Returns an array of all ActiveStatuses for this queue
-
-=cut
-
-sub ActiveStatusArray {
-    my $self = shift;
-    return $self->LifecycleObj->Valid('initial', 'active');
-}
-
-=head2 InactiveStatusArray
-
-Returns an array of all InactiveStatuses for this queue
-
-=cut
-
-sub InactiveStatusArray {
-    my $self = shift;
-    return $self->LifecycleObj->Inactive;
-}
-
-=head2 StatusArray
-
-Returns an array of all statuses for this queue
-
-=cut
-
-sub StatusArray {
-    my $self = shift;
-    return $self->LifecycleObj->Valid( @_ );
-}
-
-=head2 IsValidStatus value
-
-Returns true if value is a valid status.  Otherwise, returns 0.
-
-=cut
-
-sub IsValidStatus {
-    my $self  = shift;
-    return $self->LifecycleObj->IsValid( shift );
-}
-
-=head2 IsActiveStatus value
-
-Returns true if value is a Active status.  Otherwise, returns 0
-
-=cut
-
-sub IsActiveStatus {
-    my $self  = shift;
-    return $self->LifecycleObj->IsValid( shift, 'initial', 'active');
-}
-
-
-
-=head2 IsInactiveStatus value
-
-Returns true if value is a Inactive status.  Otherwise, returns 0
-
-
-=cut
-
-sub IsInactiveStatus {
-    my $self  = shift;
-    return $self->LifecycleObj->IsInactive( shift );
-}
-
-
-
-
-
-
 =head2 Create(ARGS)
 
 Arguments: ARGS is a hash of named parameters.  Valid parameters are:
diff --git a/lib/RT/Role/Record.pm b/lib/RT/Role/Record.pm
new file mode 100644
index 0000000..1826023
--- /dev/null
+++ b/lib/RT/Role/Record.pm
@@ -0,0 +1,77 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Role::Record;
+use Role::Basic;
+
+=head1 NAME
+
+RT::Role::Record - Common requirements for roles which are consumed by records
+
+=head1 DESCRIPTION
+
+Various L<RT::Record> (and by inheritance L<DBIx::SearchBuilder::Record>)
+methods are required by this role.  It provides no methods on its own but is
+simply a contract for other roles to require (usually under the
+I<RT::Role::Record::> namespace).
+
+=cut
+
+requires $_ for qw(
+    id
+    loc
+    CurrentUser
+
+    _Set
+    _Accessible
+);
+
+1;
diff --git a/lib/RT/Role/Record/Lifecycle.pm b/lib/RT/Role/Record/Lifecycle.pm
new file mode 100644
index 0000000..a739a5b
--- /dev/null
+++ b/lib/RT/Role/Record/Lifecycle.pm
@@ -0,0 +1,219 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Role::Record::Lifecycle;
+use Role::Basic;
+use Scalar::Util qw(blessed);
+
+=head1 NAME
+
+RT::Role::Record::Lifecycle - Common methods for records which have a Lifecycle column
+
+=head1 REQUIRES
+
+=head2 L<RT::Role::Record>
+
+=head2 LifecycleType
+
+Used as a role parameter.  Must return a string of the type of lifecycles the
+record consumes, i.e.  I<ticket> for L<RT::Queue>.
+
+=head2 Lifecycle
+
+A Lifecycle method which returns a lifecycle name is required.  Currently
+unenforced at compile-time due to poor interactions with
+L<DBIx::SearchBuilder::Record/AUTOLOAD>.  You'll hit run-time errors if this
+method isn't available in consuming classes, however.
+
+=cut
+
+with 'RT::Role::Record';
+requires 'LifecycleType';
+
+# XXX: can't require column methods due to DBIx::SB::Record's AUTOLOAD
+#requires 'Lifecycle';
+
+=head1 PROVIDES
+
+=head2 LifecycleObj
+
+Returns an L<RT::Lifecycle> object for this record's C<Lifecycle>.  If called
+as a class method, returns an L<RT::Lifecycle> object which is an aggregation
+of all lifecycles of the appropriate type.
+
+=cut
+
+sub LifecycleObj {
+    my $self = shift;
+    my $type = $self->LifecycleType;
+    my $fallback = $self->_Accessible( Lifecycle => "default" );
+
+    unless (blessed($self) and $self->id) {
+        return RT::Lifecycle->Load( Type => $type );
+    }
+
+    my $name = $self->Lifecycle || $fallback;
+    my $res  = RT::Lifecycle->Load( Name => $name, Type => $type );
+    unless ( $res ) {
+        RT->Logger->error(
+            sprintf "Lifecycle '%s' of type %s for %s #%d doesn't exist",
+                    $name, $type, ref($self), $self->id);
+        return RT::Lifecycle->Load( Name => $fallback, Type => $type );
+    }
+    return $res;
+}
+
+=head2 SetLifecycle
+
+Validates that the specified lifecycle exists before updating the record.
+
+Takes a lifecycle name.
+
+=cut
+
+sub SetLifecycle {
+    my $self  = shift;
+    my $value = shift || $self->_Accessible( Lifecycle => "default" );
+
+    return (0, $self->loc('[_1] is not a valid lifecycle', $value))
+        unless $self->ValidateLifecycle($value);
+
+    return $self->_Set( Field => 'Lifecycle', Value => $value, @_ );
+}
+
+=head2 ValidateLifecycle
+
+Takes a lifecycle name.  Returns true if it's an OK name and such lifecycle is
+configured.  Returns false otherwise.
+
+=cut
+
+sub ValidateLifecycle {
+    my $self  = shift;
+    my $value = shift;
+    return unless $value;
+    return unless RT::Lifecycle->Load( Name => $value, Type => $self->LifecycleType );
+    return 1;
+}
+
+=head2 ActiveStatusArray
+
+Returns an array of all ActiveStatuses for the lifecycle
+
+=cut
+
+sub ActiveStatusArray {
+    my $self = shift;
+    return $self->LifecycleObj->Valid('initial', 'active');
+}
+
+=head2 InactiveStatusArray
+
+Returns an array of all InactiveStatuses for the lifecycle
+
+=cut
+
+sub InactiveStatusArray {
+    my $self = shift;
+    return $self->LifecycleObj->Inactive;
+}
+
+=head2 StatusArray
+
+Returns an array of all statuses for the lifecycle
+
+=cut
+
+sub StatusArray {
+    my $self = shift;
+    return $self->LifecycleObj->Valid( @_ );
+}
+
+=head2 IsValidStatus
+
+Takes a status.
+
+Returns true if STATUS is a valid status.  Otherwise, returns 0.
+
+=cut
+
+sub IsValidStatus {
+    my $self  = shift;
+    return $self->LifecycleObj->IsValid( shift );
+}
+
+=head2 IsActiveStatus
+
+Takes a status.
+
+Returns true if STATUS is a Active status.  Otherwise, returns 0
+
+=cut
+
+sub IsActiveStatus {
+    my $self  = shift;
+    return $self->LifecycleObj->IsValid( shift, 'initial', 'active');
+}
+
+=head2 IsInactiveStatus
+
+Takes a status.
+
+Returns true if STATUS is a Inactive status.  Otherwise, returns 0
+
+=cut
+
+sub IsInactiveStatus {
+    my $self  = shift;
+    return $self->LifecycleObj->IsInactive( shift );
+}
+
+1;
diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index 023f92d..f7b5508 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -228,6 +228,7 @@ Regexp::Common::net::CIDR
 Regexp::IPv6
 Symbol::Global::Name
 HTML::Entities
+Role::Basic 0.12
 .
 
 $deps{'MASON'} = [ text_to_hash( << '.') ];

commit aee2eae47f60373af151faf9bb200c6c8b3755ba
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jan 4 17:39:37 2013 -0800

    RT::Lifecycle->Type accessor
    
    Stashes the "effective" type of the lifecycle at the top level of the
    object, including for virtual aggregate lifecycles (i.e. lifecycles
    loaded with no name, only a type).  In cases when a lifecycle is
    requested by name with a type that conflicts with reality, ->Type will
    return the requested type, not the *declared* type of the lifecycle from
    the config.  This ensures ->Type matches the blessed lifecycle class;
    hence the "effective" type.

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index b533746..c140a35 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -147,11 +147,13 @@ sub Load {
     if (defined $args{Name} and exists $LIFECYCLES_CACHE{ $args{Name} }) {
         $self->{'name'} = $args{Name};
         $self->{'data'} = $LIFECYCLES_CACHE{ $args{Name} };
+        $self->{'type'} = $args{Type};
 
         my $found_type = $self->{'data'}{'type'};
         warn "Found type of $found_type ne $args{Type}" if $found_type ne $args{Type};
     } elsif (not $args{Name} and exists $LIFECYCLES_TYPES{ $args{Type} }) {
         $self->{'data'} = $LIFECYCLES_TYPES{ $args{Type} };
+        $self->{'type'} = $args{Type};
     } else {
         return undef;
     }
@@ -186,6 +188,14 @@ Returns name of the loaded lifecycle.
 
 sub Name { return $_[0]->{'name'} }
 
+=head2 Type
+
+Returns the type of the loaded lifecycle.
+
+=cut
+
+sub Type { return $_[0]->{'type'} }
+
 =head2 Getting statuses and validating.
 
 Methods to get statuses in different sets or validating them.
@@ -532,8 +542,7 @@ move map from this cycle to provided.
 sub MoveMap {
     my $from = shift; # self
     my $to = shift;
-    my $type = $from->{'data'}{'type'};
-    $to = RT::Lifecycle->Load( Name => $to, Type => $type ) unless ref $to;
+    $to = RT::Lifecycle->Load( Name => $to, Type => $from->Type ) unless ref $to;
     return $LIFECYCLES{'__maps__'}{ $from->Name .' -> '. $to->Name } || {};
 }
 
@@ -561,7 +570,7 @@ move maps.
 
 sub NoMoveMaps {
     my $self = shift;
-    my $type = $self->{'data'}{'type'};
+    my $type = $self->Type;
     my @list = $self->List( $type );
     my @res;
     foreach my $from ( @list ) {

commit c029f8973448c726eb8964268c4f882055b75d3e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jan 4 17:35:54 2013 -0800

    Refactor ticket Status methods and behaviours into a role
    
    A few error messages have changed slightly, but most behaviour is
    unchanged.  The most notable change is that tickets now record a Status
    transaction when the Status is automatically updated (via lifecycle
    maps) after a queue change.

diff --git a/docs/UPGRADING-4.2 b/docs/UPGRADING-4.2
index 4b165ff..25c183f 100644
--- a/docs/UPGRADING-4.2
+++ b/docs/UPGRADING-4.2
@@ -61,3 +61,8 @@ UPGRADING FROM RT 4.0.0 and greater
   you had copied @JSFiles to add extra entries in your RT_SiteConfig,
   remove the core JS from the list, or RT will serve those files
   multiple times.
+
+* A Status transaction is now recorded when a ticket status changes as a
+  result of a queue change.  Scrips with conditions relying on Status changes
+  may start to trigger on these transitions; previously these Status changes
+  never triggered scrips.
diff --git a/lib/RT/Role/Record/Status.pm b/lib/RT/Role/Record/Status.pm
new file mode 100644
index 0000000..afcab13
--- /dev/null
+++ b/lib/RT/Role/Record/Status.pm
@@ -0,0 +1,302 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Role::Record::Status;
+use Role::Basic;
+use Scalar::Util qw(blessed);
+
+=head1 NAME
+
+RT::Role::Record::Status - Common methods for records which have a Status column
+
+=head1 DESCRIPTION
+
+Lifecycles are generally set on container records, and Statuses on records
+which belong to one of those containers.  L<RT::Role::Record::Lifecycle>
+handles the containers with the I<Lifecycle> column.  This role is for the
+records with a I<Status> column within those containers.  It includes
+convenience methods for grabbing an L<RT::Lifecycle> object as well setters for
+validating I<Status> and the column which points to the container object.
+
+=head1 REQUIRES
+
+=head2 L<RT::Role::Record>
+
+=head2 LifecycleColumn
+
+Used as a role parameter.  Must return a string of the column name which points
+to the container object that consumes L<RT::Role::Record::Lifecycle> (or
+conforms to it).  The resulting string is used to construct two method names:
+as-is to fetch the column value and suffixed with "Obj" to fetch the object.
+
+=head2 Status
+
+A Status method which returns a lifecycle name is required.  Currently
+unenforced at compile-time due to poor interactions with
+L<DBIx::SearchBuilder::Record/AUTOLOAD>.  You'll hit run-time errors if this
+method isn't available in consuming classes, however.
+
+=cut
+
+with 'RT::Role::Record';
+requires 'LifecycleColumn';
+
+# XXX: can't require column methods due to DBIx::SB::Record's AUTOLOAD
+#requires 'Status';
+
+=head1 PROVIDES
+
+=head2 LifecycleObj
+
+Returns an L<RT::Lifecycle> object for this record's C<Lifecycle>.  If called
+as a class method, returns an L<RT::Lifecycle> object which is an aggregation
+of all lifecycles of the appropriate type.
+
+=cut
+
+sub LifecycleObj {
+    my $self = shift;
+    my $obj  = $self->LifecycleColumn . "Obj";
+    return $self->$obj->LifecycleObj;
+}
+
+=head2 Lifecycle
+
+Returns the L<RT::Lifecycle/Name> of this record's L</LifecycleObj>.
+
+=cut
+
+sub Lifecycle {
+    my $self = shift;
+    return $self->LifecycleObj->Name;
+}
+
+=head2 ValidateStatus
+
+Takes a status.  Returns true if that status is a valid status for this record,
+otherwise returns false.
+
+=cut
+
+sub ValidateStatus {
+    my $self = shift;
+    return $self->LifecycleObj->IsValid(@_);
+}
+
+=head2 ValidateStatusChange
+
+Validates the new status with the current lifecycle.  Returns a tuple of (OK,
+message).
+
+Expected to be called from this role's L</SetStatus> or the consuming class'
+equivalent.
+
+=cut
+
+sub ValidateStatusChange {
+    my $self = shift;
+    my $new  = shift;
+    my $old  = $self->Status;
+
+    my $lifecycle = $self->LifecycleObj;
+
+    unless ( $lifecycle->IsValid( $new ) ) {
+        return (0, $self->loc("Status '[_1]' isn't a valid status for this [_2].", $self->loc($new), $self->loc($lifecycle->Type)));
+    }
+
+    unless ( $lifecycle->IsTransition( $old => $new ) ) {
+        return (0, $self->loc("You can't change status from '[_1]' to '[_2]'.", $self->loc($old), $self->loc($new)));
+    }
+
+    my $check_right = $lifecycle->CheckRight( $old => $new );
+    unless ( $self->CurrentUser->HasRight( Right => $check_right, Object => $self ) ) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
+
+    return 1;
+}
+
+=head2 SetStatus
+
+Validates the status transition before updating the Status column.  This method
+may want to be overridden by a more specific method in the consuming class.
+
+=cut
+
+sub SetStatus {
+    my $self = shift;
+    my $new  = shift;
+
+    my ($valid, $error) = $self->ValidateStatusChange($new);
+    return ($valid, $error) unless $valid;
+
+    return $self->_SetStatus( Status => $new );
+}
+
+=head2 _SetStatus
+
+Sets the Status column without validating the change.  Intended to be used
+as-is by methods provided by the role, or overridden in the consuming class to
+take additional action.  For example, L<RT::Ticket/_SetStatus> sets the Started
+and Resolved dates on the ticket as necessary.
+
+Takes a paramhash where the only required key is Status.  Other keys may
+include Lifecycle and NewLifecycle when called from L</_SetLifecycleColumn>,
+which may assist consuming classes.  NewLifecycle defaults to Lifecycle if not
+provided; this indicates the lifecycle isn't changing.
+
+=cut
+
+sub _SetStatus {
+    my $self = shift;
+    my %args = (
+        Status      => undef,
+        Lifecycle   => $self->LifecycleObj,
+        @_,
+    );
+    $args{NewLifecycle} ||= $args{Lifecycle};
+
+    return $self->_Set(
+        Field   => 'Status',
+        Value   => $args{Status},
+    );
+}
+
+=head2 _SetLifecycleColumn
+
+Validates and updates the column named by L</LifecycleColumn>.  The Status
+column is also updated if necessary (via lifecycle transition maps).
+
+On success, returns a tuple of (1, I<message>, I<new status>) where I<new
+status> is the status that was transitioned to, if any.  On failure, returns
+(0, I<error message>).
+
+Takes a paramhash with keys I<Value> and (optionally) I<RequireRight>.
+I<RequireRight> is a right name which the current user must have on the new
+L</LifecycleColumn> object in order for the method to succeed.
+
+This method is expected to be used from within another method such as
+L<RT::Ticket/SetQueue>.
+
+=cut
+
+sub _SetLifecycleColumn {
+    my $self = shift;
+    my %args = @_;
+
+    my $column     = $self->LifecycleColumn;
+    my $column_obj = "${column}Obj";
+
+    my $current = $self->$column_obj;
+    my $class   = blessed($current);
+
+    my $new = $class->new( $self->CurrentUser );
+    $new->Load($args{Value});
+
+    return (0, $self->loc("[_1] [_2] does not exist", $self->loc($column), $args{Value}))
+        unless $new->id;
+
+    my $name = eval { $current->Name } || $current->id;
+
+    return (0, $self->loc("[_1] [_2] is disabled", $self->loc($column), $name))
+        if $new->Disabled;
+
+    return (0, $self->loc("[_1] is already set to [_2]", $self->loc($column), $name))
+        if $new->id == $current->id;
+
+    return (0, $self->loc("Permission Denied"))
+        if $args{RequireRight} and not $self->CurrentUser->HasRight(
+            Right   => $args{RequireRight},
+            Object  => $new,
+        );
+
+    my $new_status;
+    my $old_lifecycle = $current->LifecycleObj;
+    my $new_lifecycle = $new->LifecycleObj;
+    if ( $old_lifecycle->Name ne $new_lifecycle->Name ) {
+        unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) {
+            return ( 0, $self->loc("There is no mapping for statuses between lifecycle [_1] and [_2]. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) );
+        }
+        $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ $self->Status };
+        return ( 0, $self->loc("Mapping between lifecycle [_1] and [_2] is incomplete. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) )
+            unless $new_status;
+    }
+
+    my ($ok, $msg) = $self->_Set( Field => $column, Value => $new->id );
+    if ($ok) {
+        if ( $new_status ) {
+            my $as_system = blessed($self)->new( RT->SystemUser );
+            $as_system->Load( $self->Id );
+            unless ( $as_system->Id ) {
+                return ( 0, $self->loc("Couldn't load copy of [_1] #[_2]", blessed($self), $self->Id) );
+            }
+
+            my ($val, $msg) = $as_system->_SetStatus(
+                Lifecycle       => $old_lifecycle,
+                NewLifecycle    => $new_lifecycle,
+                Status          => $new_status,
+            );
+
+            if ($val) {
+                # Pick up the change made by the clone above
+                $self->Load( $self->id );
+            } else {
+                RT->Logger->error("Status change to $new_status failed on $column change: $msg");
+                undef $new_status;
+            }
+        }
+        return (1, $msg, $new_status);
+    } else {
+        return (0, $msg);
+    }
+}
+
+1;
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index e1e28b9..06f7f54 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -68,6 +68,8 @@ package RT::Ticket;
 use strict;
 use warnings;
 
+use Role::Basic 'with';
+with "RT::Role::Record::Status";
 
 use RT::Queue;
 use RT::User;
@@ -84,6 +86,8 @@ use RT::URI;
 use MIME::Entity;
 use Devel::GlobalDestruction;
 
+sub LifecycleColumn { "Queue" }
+
 my %ROLES = (
     # name    =>  description
     Owner     => 'The owner of a ticket',                             # loc_pair
@@ -1125,69 +1129,26 @@ sub ValidateQueue {
     }
 }
 
-
-
 sub SetQueue {
-    my $self     = shift;
-    my $NewQueue = shift;
+    my $self  = shift;
+    my $value = shift;
 
-    #Redundant. ACL gets checked in _Set;
     unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
         return ( 0, $self->loc("Permission Denied") );
     }
 
-    my $NewQueueObj = RT::Queue->new( $self->CurrentUser );
-    $NewQueueObj->Load($NewQueue);
-
-    unless ( $NewQueueObj->Id() ) {
-        return ( 0, $self->loc("That queue does not exist") );
-    }
-
-    if ( $NewQueueObj->Id == $self->QueueObj->Id ) {
-        return ( 0, $self->loc('That is the same value') );
-    }
-    unless ( $self->CurrentUser->HasRight( Right    => 'CreateTicket', Object => $NewQueueObj)
-         and $NewQueueObj->Disabled != 1) {
-        return ( 0, $self->loc("You may not create requests in that queue.") );
-    }
-
-    my $new_status;
-    my $old_lifecycle = $self->LifecycleObj;
-    my $new_lifecycle = $NewQueueObj->LifecycleObj;
-    if ( $old_lifecycle->Name ne $new_lifecycle->Name ) {
-        unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) {
-            return ( 0, $self->loc("There is no mapping for statuses between these queues. Contact your system administrator.") );
-        }
-        $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ $self->Status };
-        return ( 0, $self->loc("Mapping between queues' lifecycles is incomplete. Contact your system administrator.") )
-            unless $new_status;
-    }
-
-    my ($status, $msg) = $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() );
+    my ($ok, $msg, $status) = $self->_SetLifecycleColumn(
+        Value           => $value,
+        RequireRight    => "CreateTicket"
+    );
 
-    if ( $status ) {
+    if ($ok) {
         # Clear the queue object cache;
         $self->{_queue_obj} = undef;
-
-        if ( $new_status ) {
-            my $clone = RT::Ticket->new( RT->SystemUser );
-            $clone->Load( $self->Id );
-            unless ( $clone->Id ) {
-                return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) );
-            }
-
-            my ($val, $msg) = $clone->_SetStatus(
-                Lifecycle         => $old_lifecycle,
-                NewLifecycle      => $new_lifecycle,
-                Status            => $new_status,
-                RecordTransaction => 0,
-            );
-            RT->Logger->error("Status change failed on queue change: $msg")
-                unless $val;
-        }
+        my $queue = $self->QueueObj;
 
         # Untake the ticket if we have no permissions in the new queue
-        unless ( $self->OwnerObj->HasRight( Right => 'OwnTicket', Object => $NewQueueObj ) ) {
+        unless ($self->OwnerObj->HasRight( Right => 'OwnTicket', Object => $queue )) {
             my $clone = RT::Ticket->new( RT->SystemUser );
             $clone->Load( $self->Id );
             unless ( $clone->Id ) {
@@ -1200,7 +1161,7 @@ sub SetQueue {
         # On queue change, change queue for reminders too
         my $reminder_collection = $self->Reminders->Collection;
         while ( my $reminder = $reminder_collection->Next ) {
-            my ($status, $msg) = $reminder->_Set( Field => 'Queue', Value => $NewQueueObj->Id(), RecordTransaction => 0 );
+            my ($status, $msg) = $reminder->_Set( Field => 'Queue', Value => $queue->Id(), RecordTransaction => 0 );
             $RT::Logger->error('Queue change failed for reminder #' . $reminder->Id . ': ' . $msg) unless $status;
         }
 
@@ -1210,7 +1171,7 @@ sub SetQueue {
             unless $self->id;
     }
 
-    return ($status, $msg);
+    return ($ok, $msg);
 }
 
 
@@ -1310,28 +1271,6 @@ sub ResolvedObj {
     return $time;
 }
 
-=head2 Lifecycle
-
-Returns the L<RT::Lifecycle/Name> for this ticket.
-
-=cut
-
-sub Lifecycle {
-    return shift->LifecycleObj->Name;
-}
-
-=head2 LifecycleObj
-
-Returns the L<RT::Lifecycle> for this ticket, which is picked up from
-the ticket's current queue.
-
-=cut
-
-sub LifecycleObj {
-    my $self = shift;
-    return $self->QueueObj->LifecycleObj;
-}
-
 =head2 FirstActiveStatus
 
 Returns the first active status that the ticket could transition to,
@@ -2399,29 +2338,6 @@ sub Steal {
 
 }
 
-
-
-
-
-=head2 ValidateStatus STATUS
-
-Takes a string. Returns true if that status is a valid status for this ticket.
-Returns false otherwise.
-
-=cut
-
-sub ValidateStatus {
-    my $self   = shift;
-    my $status = shift;
-
-    #Make sure the status passed in is valid
-    return 1 if $self->QueueObj->IsValidStatus($status);
-
-    return 0;
-}
-
-
-
 =head2 SetStatus STATUS
 
 Set this ticket's status.
@@ -2447,27 +2363,14 @@ sub SetStatus {
     # this option was added for rtir initially
     $args{SetStarted} = 1 unless exists $args{SetStarted};
 
+    my ($valid, $msg) = $self->ValidateStatusChange($args{Status});
+    return ($valid, $msg) unless $valid;
 
     my $lifecycle = $self->LifecycleObj;
 
-    my $new = $args{'Status'};
-    unless ( $lifecycle->IsValid( $new ) ) {
-        return (0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", $self->loc($new)));
-    }
-
-    my $old = $self->__Value('Status');
-    unless ( $lifecycle->IsTransition( $old => $new ) ) {
-        return (0, $self->loc("You can't change status from '[_1]' to '[_2]'.", $self->loc($old), $self->loc($new)));
-    }
-
-    my $check_right = $lifecycle->CheckRight( $old => $new );
-    unless ( $self->CurrentUserHasRight( $check_right ) ) {
-        return ( 0, $self->loc('Permission Denied') );
-    }
-
     if (   !$args{Force}
         && !$lifecycle->IsInactive($self->Status)
-        && $lifecycle->IsInactive($new)
+        && $lifecycle->IsInactive($args{Status})
         && $self->HasUnresolvedDependencies )
     {
         return ( 0, $self->loc('That ticket has unresolved dependencies') );
diff --git a/t/web/command_line.t b/t/web/command_line.t
index 8285552..c49f6be 100644
--- a/t/web/command_line.t
+++ b/t/web/command_line.t
@@ -158,7 +158,7 @@ expect_send("show ticket/$ticket_id -f queue", 'Verifying change...');
 expect_like(qr/Queue: EditedQueue$$/, 'Verified change');
 # cannot move ticket to a nonexistent queue
 expect_send("edit ticket/$ticket_id set queue=nonexistent-$$", 'Changing to nonexistent queue...');
-expect_like(qr/queue does not exist/i, 'Errored out');
+expect_like(qr/Queue nonexistent-$$ does not exist/i, 'Errored out');
 expect_send("show ticket/$ticket_id -f queue", 'Verifying lack of change...');
 expect_like(qr/Queue: EditedQueue$$/, 'Verified lack of change');
 

commit e0a20849366a330c5c4117dc56d0f9488fb9e278
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jan 4 23:36:35 2013 -0800

    Setup RT::Record's base classes before use-ing subclasses of itself
    
    RT::Record was attempting to load and compile subclasses of itself before it
    had declared its own base classes.  This meant those subclasses were missing
    all of the RT::Base and DBIx::SearchBuilder inherited methods at compile-time.
    
    Since by run-time everything was hunky dory, the ordering bug flew under the
    radar until Role::Basic introduced compile-time method checks on RT::Ticket and
    RT::Queue.  As it happens, those classes get loaded (quite indirectly) when
    RT::User is use-d.
    
    Module::Install::RTx::Factory ran face first into the bug (during `make
    initdb`) when attempting to require RT::System individually (which loads
    RT::Record as a base class, which loads RT::User, et cetera).
    
    Normal RT initialization didn't trigger the bug because RT::CurrentUser is
    loaded before RT::System in RT::InitSystemObjects().  RT::CurrentUser loads
    RT::User by way of a base class, which loads RT::Record by way a base class,
    which is able to finish compiling and setting up its parents before RT::User
    finishes loading the dependency chain which eventually invokes Role::Basic.
    
    The general bug is loading subclasses of yourself before declaring a base
    class, and it appears RT does that in a number of classes.  Another commit
    fixing those is forthcoming.
    
    The extra "use RT;" line is necessary since we're relying on RT->Config
    at compile-time in the first "use base" line.  If RT.pm is already
    loaded, as in normal system initialization, it'll do no harm; if RT.pm
    isn't loaded already, it'll load it up to avoid a compile error.

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index bcda8f4..e5d6b5b 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -66,6 +66,9 @@ package RT::Record;
 use strict;
 use warnings;
 
+use RT;
+use base RT->Config->Get('RecordBaseClass');
+use base 'RT::Base';
 
 use RT::Date;
 use RT::User;
@@ -74,8 +77,6 @@ use RT::Link;
 use Encode qw();
 
 our $_TABLE_ATTR = { };
-use base RT->Config->Get('RecordBaseClass');
-use base 'RT::Base';
 
 
 sub _Init {

commit 1d241ebe8554d70f6ad0f6f3aeb28d7732bdf753
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Jan 7 13:07:18 2013 -0800

    Always declare base class before use-ing other RT:: classes
    
    This will help avoid weird, hard to track down load ordering bugs in the
    future.

diff --git a/lib/RT/ACL.pm b/lib/RT/ACL.pm
index c1a0d42..8054bbe 100644
--- a/lib/RT/ACL.pm
+++ b/lib/RT/ACL.pm
@@ -65,10 +65,10 @@ my $ACL = RT::ACL->new($CurrentUser);
 
 
 package RT::ACL;
-use RT::ACE;
-
 use base 'RT::SearchBuilder';
 
+use RT::ACE;
+
 sub Table { 'ACL'}
 
 use strict;
diff --git a/lib/RT/Action/NotifyGroupAsComment.pm b/lib/RT/Action/NotifyGroupAsComment.pm
index 1511890..aee1ee5 100644
--- a/lib/RT/Action/NotifyGroupAsComment.pm
+++ b/lib/RT/Action/NotifyGroupAsComment.pm
@@ -62,8 +62,6 @@ package RT::Action::NotifyGroupAsComment;
 use strict;
 use warnings;
 
-use RT::Action::NotifyGroup;
-
 use base qw(RT::Action::NotifyGroup);
 
 sub SetReturnAddress {
diff --git a/lib/RT/Attachments.pm b/lib/RT/Attachments.pm
index 2bdbc24..e28bf9b 100644
--- a/lib/RT/Attachments.pm
+++ b/lib/RT/Attachments.pm
@@ -71,11 +71,10 @@ package RT::Attachments;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Attachment;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Attachments'}
 
 
diff --git a/lib/RT/Attributes.pm b/lib/RT/Attributes.pm
index 003f2e8..42b180c 100644
--- a/lib/RT/Attributes.pm
+++ b/lib/RT/Attributes.pm
@@ -68,11 +68,10 @@ package RT::Attributes;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Attribute;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Attributes'}
 
 
diff --git a/lib/RT/CachedGroupMembers.pm b/lib/RT/CachedGroupMembers.pm
index f76fc5d..21f3a5c 100644
--- a/lib/RT/CachedGroupMembers.pm
+++ b/lib/RT/CachedGroupMembers.pm
@@ -69,11 +69,10 @@ package RT::CachedGroupMembers;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::CachedGroupMember;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'CachedGroupMembers'}
 
 # {{{ LimitToUsers
diff --git a/lib/RT/CurrentUser.pm b/lib/RT/CurrentUser.pm
index b1ed3fa..cac5645 100644
--- a/lib/RT/CurrentUser.pm
+++ b/lib/RT/CurrentUser.pm
@@ -88,14 +88,13 @@ passed to Load method.
 
 package RT::CurrentUser;
 
-use RT::I18N;
-
 use strict;
 use warnings;
 
-
 use base qw/RT::User/;
 
+use RT::I18N;
+
 #The basic idea here is that $self->CurrentUser is always supposed
 # to be a CurrentUser object. but that's hard to do when we're trying to load
 # the CurrentUser object
diff --git a/lib/RT/CustomFieldValue.pm b/lib/RT/CustomFieldValue.pm
index 15654aa..9546bc2 100644
--- a/lib/RT/CustomFieldValue.pm
+++ b/lib/RT/CustomFieldValue.pm
@@ -54,8 +54,8 @@ package RT::CustomFieldValue;
 no warnings qw/redefine/;
 
 
-use RT::CustomField;
 use base 'RT::Record';
+use RT::CustomField;
 
 sub Table {'CustomFieldValues'}
 
diff --git a/lib/RT/CustomFieldValues.pm b/lib/RT/CustomFieldValues.pm
index 90a1637..008cc45 100644
--- a/lib/RT/CustomFieldValues.pm
+++ b/lib/RT/CustomFieldValues.pm
@@ -51,12 +51,10 @@ package RT::CustomFieldValues;
 use strict;
 use warnings;
 
-
+use base 'RT::SearchBuilder';
 
 use RT::CustomFieldValue;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'CustomFieldValues'}
 
 sub _Init {
diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index a837f82..a7d96b0 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -68,12 +68,12 @@ package RT::CustomFields;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
+
 use DBIx::SearchBuilder::Unique;
 
 use RT::CustomField;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'CustomFields'}
 
 sub _Init {
diff --git a/lib/RT/Dashboard.pm b/lib/RT/Dashboard.pm
index 2e2bbc4..11156a9 100644
--- a/lib/RT/Dashboard.pm
+++ b/lib/RT/Dashboard.pm
@@ -67,13 +67,13 @@
 
 package RT::Dashboard;
 
-use RT::SavedSearch;
-
 use strict;
 use warnings;
 
 use base qw/RT::SharedSetting/;
 
+use RT::SavedSearch;
+
 use RT::System;
 RT::System::AddRights(
     SubscribeDashboard => 'Subscribe to dashboards', #loc_pair
diff --git a/lib/RT/Dashboards.pm b/lib/RT/Dashboards.pm
index 6560cab..96aebff 100644
--- a/lib/RT/Dashboards.pm
+++ b/lib/RT/Dashboards.pm
@@ -67,12 +67,12 @@
 
 package RT::Dashboards;
 
-use RT::Dashboard;
-
 use strict;
 use warnings;
 use base 'RT::SharedSettings';
 
+use RT::Dashboard;
+
 sub RecordClass {
     return 'RT::Dashboard';
 }
diff --git a/lib/RT/GroupMembers.pm b/lib/RT/GroupMembers.pm
index 0b56c02..0d2cefb 100644
--- a/lib/RT/GroupMembers.pm
+++ b/lib/RT/GroupMembers.pm
@@ -69,10 +69,10 @@ package RT::GroupMembers;
 use strict;
 use warnings;
 
-use RT::GroupMember;
-
 use base 'RT::SearchBuilder';
 
+use RT::GroupMember;
+
 sub Table { 'GroupMembers'}
 
 
diff --git a/lib/RT/Groups.pm b/lib/RT/Groups.pm
index 0e3cc98..d474a43 100644
--- a/lib/RT/Groups.pm
+++ b/lib/RT/Groups.pm
@@ -74,14 +74,11 @@ package RT::Groups;
 use strict;
 use warnings;
 
-
-
-use RT::Group;
-
 use base 'RT::SearchBuilder';
 
 sub Table { 'Groups'}
 
+use RT::Group;
 use RT::Users;
 
 # XXX: below some code is marked as subject to generalize in Groups, Users classes.
diff --git a/lib/RT/Links.pm b/lib/RT/Links.pm
index ccc72d7..52b8c8d 100644
--- a/lib/RT/Links.pm
+++ b/lib/RT/Links.pm
@@ -70,11 +70,10 @@ package RT::Links;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Link;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Links'}
 
 
diff --git a/lib/RT/ObjectCustomFieldValue.pm b/lib/RT/ObjectCustomFieldValue.pm
index 98714a0..3da79bc 100644
--- a/lib/RT/ObjectCustomFieldValue.pm
+++ b/lib/RT/ObjectCustomFieldValue.pm
@@ -50,6 +50,7 @@ package RT::ObjectCustomFieldValue;
 
 use strict;
 use warnings;
+use base 'RT::Record';
 
 use RT::Interface::Web;
 use Regexp::Common qw(RE_net_IPv4);
@@ -60,10 +61,7 @@ require Net::CIDR;
 # Allow the empty IPv6 address
 $IPv6_re = qr/(?:$IPv6_re|::)/;
 
-
-
 use RT::CustomField;
-use base 'RT::Record';
 
 sub Table {'ObjectCustomFieldValues'}
 
diff --git a/lib/RT/ObjectCustomFieldValues.pm b/lib/RT/ObjectCustomFieldValues.pm
index df22b65..decee02 100644
--- a/lib/RT/ObjectCustomFieldValues.pm
+++ b/lib/RT/ObjectCustomFieldValues.pm
@@ -51,11 +51,10 @@ package RT::ObjectCustomFieldValues;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::ObjectCustomFieldValue;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'ObjectCustomFieldValues'}
 
 sub _Init {
diff --git a/lib/RT/ObjectTopic.pm b/lib/RT/ObjectTopic.pm
index 24b5956..ac577ae 100644
--- a/lib/RT/ObjectTopic.pm
+++ b/lib/RT/ObjectTopic.pm
@@ -63,12 +63,10 @@ use strict;
 use warnings;
 no warnings 'redefine';
 
-use RT::Record; 
-use RT::Topic;
-
-
 use base qw( RT::Record );
 
+use RT::Topic;
+
 sub _Init {
   my $self = shift; 
 
diff --git a/lib/RT/Principals.pm b/lib/RT/Principals.pm
index 69e49ef..aa140a1 100644
--- a/lib/RT/Principals.pm
+++ b/lib/RT/Principals.pm
@@ -70,10 +70,10 @@ use strict;
 use warnings;
 
 
-use RT::Principal;
-
 use base 'RT::SearchBuilder';
 
+use RT::Principal;
+
 sub Table { 'Principals'}
 
 sub _Init {
diff --git a/lib/RT/Queues.pm b/lib/RT/Queues.pm
index feb3491..551b70c 100644
--- a/lib/RT/Queues.pm
+++ b/lib/RT/Queues.pm
@@ -69,11 +69,10 @@ package RT::Queues;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Queue;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Queues'}
 
 # {{{ sub _Init
diff --git a/lib/RT/SavedSearches.pm b/lib/RT/SavedSearches.pm
index ebceef4..da43a29 100644
--- a/lib/RT/SavedSearches.pm
+++ b/lib/RT/SavedSearches.pm
@@ -67,12 +67,12 @@
 
 package RT::SavedSearches;
 
-use RT::SavedSearch;
-
 use strict;
 use warnings;
 use base 'RT::SharedSettings';
 
+use RT::SavedSearch;
+
 sub RecordClass {
     return 'RT::SavedSearch';
 }
diff --git a/lib/RT/Scrip.pm b/lib/RT/Scrip.pm
index 9edf40c..84a4fe4 100644
--- a/lib/RT/Scrip.pm
+++ b/lib/RT/Scrip.pm
@@ -67,7 +67,7 @@ package RT::Scrip;
 
 use strict;
 use warnings;
-
+use base 'RT::Record';
 
 use RT::Queue;
 use RT::Template;
@@ -76,8 +76,6 @@ use RT::ScripAction;
 use RT::Scrips;
 use RT::ObjectScrip;
 
-use base 'RT::Record';
-
 sub Table {'Scrips'}
 
 # {{{ sub Create
diff --git a/lib/RT/ScripConditions.pm b/lib/RT/ScripConditions.pm
index 145d94d..a247a33 100644
--- a/lib/RT/ScripConditions.pm
+++ b/lib/RT/ScripConditions.pm
@@ -70,11 +70,10 @@ package RT::ScripConditions;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::ScripCondition;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'ScripConditions'}
 
 sub LimitToType  {
diff --git a/lib/RT/Scrips.pm b/lib/RT/Scrips.pm
index 17bffcd..aa0e59b 100644
--- a/lib/RT/Scrips.pm
+++ b/lib/RT/Scrips.pm
@@ -69,11 +69,11 @@ package RT::Scrips;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
+
 use RT::Scrip;
 use RT::ObjectScrips;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Scrips'}
 
 sub _Init {
diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 47c09a6..115e37b 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -64,15 +64,14 @@
 
 package RT::SearchBuilder;
 
-use RT::Base;
-use DBIx::SearchBuilder "1.40";
-
 use strict;
 use warnings;
 
-
 use base qw(DBIx::SearchBuilder RT::Base);
 
+use RT::Base;
+use DBIx::SearchBuilder "1.40";
+
 sub _Init  {
     my $self = shift;
     
diff --git a/lib/RT/SharedSetting.pm b/lib/RT/SharedSetting.pm
index e3856f2..8bff4c0 100644
--- a/lib/RT/SharedSetting.pm
+++ b/lib/RT/SharedSetting.pm
@@ -64,10 +64,10 @@ It consists of an ID, a name, and some arbitrary data.
 package RT::SharedSetting;
 use strict;
 use warnings;
+use base qw/RT::Base/;
 
 use RT::Attribute;
 use Scalar::Util 'blessed';
-use base qw/RT::Base/;
 
 =head1 METHODS
 
diff --git a/lib/RT/SharedSettings.pm b/lib/RT/SharedSettings.pm
index 65acb3b..d3e131f 100644
--- a/lib/RT/SharedSettings.pm
+++ b/lib/RT/SharedSettings.pm
@@ -67,12 +67,12 @@
 
 package RT::SharedSettings;
 
-use RT::SharedSetting;
-
 use strict;
 use warnings;
 use base 'RT::Base';
 
+use RT::SharedSetting;
+
 sub new  {
     my $proto = shift;
     my $class = ref($proto) || $proto;
diff --git a/lib/RT/Template.pm b/lib/RT/Template.pm
index c8ed773..8a8e594 100644
--- a/lib/RT/Template.pm
+++ b/lib/RT/Template.pm
@@ -70,7 +70,9 @@ package RT::Template;
 use strict;
 use warnings;
 
+use base 'RT::Record';
 
+use RT::Queue;
 
 use Text::Template;
 use MIME::Entity;
@@ -825,9 +827,6 @@ sub CurrentUserCanRead {
 
 1;
 
-use RT::Queue;
-use base 'RT::Record';
-
 sub Table {'Templates'}
 
 
diff --git a/lib/RT/Templates.pm b/lib/RT/Templates.pm
index be571f0..053c9a3 100644
--- a/lib/RT/Templates.pm
+++ b/lib/RT/Templates.pm
@@ -68,10 +68,10 @@ package RT::Templates;
 use strict;
 use warnings;
 
-use RT::Template;
-
 use base 'RT::SearchBuilder';
 
+use RT::Template;
+
 sub Table { 'Templates'}
 
 
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 06f7f54..0d8f46a 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -67,6 +67,7 @@ package RT::Ticket;
 
 use strict;
 use warnings;
+use base 'RT::Record';
 
 use Role::Basic 'with';
 with "RT::Role::Record::Status";
@@ -3069,10 +3070,6 @@ RT
 
 =cut
 
-
-use RT::Queue;
-use base 'RT::Record';
-
 sub Table {'Tickets'}
 
 
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 2efa234..fc3a7f1 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -81,11 +81,10 @@ package RT::Tickets;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Ticket;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Tickets'}
 
 use RT::CustomFields;
diff --git a/lib/RT/Transactions.pm b/lib/RT/Transactions.pm
index 86a05f7..d0f93fd 100644
--- a/lib/RT/Transactions.pm
+++ b/lib/RT/Transactions.pm
@@ -69,11 +69,10 @@ package RT::Transactions;
 use strict;
 use warnings;
 
+use base 'RT::SearchBuilder';
 
 use RT::Transaction;
 
-use base 'RT::SearchBuilder';
-
 sub Table { 'Transactions'}
 
 # {{{ sub _Init  
diff --git a/lib/RT/URI/a.pm b/lib/RT/URI/a.pm
index b88af26..ad812b4 100644
--- a/lib/RT/URI/a.pm
+++ b/lib/RT/URI/a.pm
@@ -51,8 +51,8 @@ package RT::URI::a;
 use strict;
 use warnings;
 
-use RT::Article;
 use base qw/RT::URI::fsck_com_article/;
+use RT::Article;
 
 my $scheme = "a";
 
diff --git a/lib/RT/URI/fsck_com_article.pm b/lib/RT/URI/fsck_com_article.pm
index 0c09b7c..f278c98 100644
--- a/lib/RT/URI/fsck_com_article.pm
+++ b/lib/RT/URI/fsck_com_article.pm
@@ -52,8 +52,8 @@ use strict;
 use warnings;
 no warnings 'redefine';
 
-use RT::Article;
 use base qw/RT::URI::base/;
+use RT::Article;
 
 =head2 LocalURIPrefix 
 
diff --git a/lib/RT/Users.pm b/lib/RT/Users.pm
index ffb7d85..836a7c8 100644
--- a/lib/RT/Users.pm
+++ b/lib/RT/Users.pm
@@ -69,10 +69,10 @@ package RT::Users;
 use strict;
 use warnings;
 
-use RT::User;
-
 use base 'RT::SearchBuilder';
 
+use RT::User;
+
 sub Table { 'Users'}
 
 

commit e0921ff1cfa1badabc6c9731fc54dd9c4e3d9ceb
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Jan 7 20:44:42 2013 -0500

    Move RT::Record dependencies to runtime, not compile-time
    
    Some of the "use RT::Foo" modules listed in RT::Record are themselves
    subclasses of RT::Record.  This means that RT::Record's methods are not
    necessarily initialized before the subclasses are loaded.  In tern, this
    causes the methods of RT::Record itself to be invisible to Role::Basic's
    "with" -- though methods of its superclass (DBIx::SearchBuilder::Record)
    are, as hey are pulled in earlier by the compile-time "use base" above.
    
    Move the import of RT::Foo classes to runtime, not compile-time.  This
    simplifies load order during compile-time, ensuring that a class loads
    all of its parent classes and roles without possibly creating load-order
    loops.  This also requires that any roles also declare their module
    dependencies using "require" as well.  As there are no notable "import"
    methods to RT's object classes, and (save for Role::Basic method
    resolution) compile-time checking is necessary, this is safe.

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index e5d6b5b..4132ae6 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -70,10 +70,11 @@ use RT;
 use base RT->Config->Get('RecordBaseClass');
 use base 'RT::Base';
 
-use RT::Date;
-use RT::User;
-use RT::Attributes;
-use RT::Link;
+require RT::Date;
+require RT::User;
+require RT::Attributes;
+require RT::Transactions;
+require RT::Link;
 use Encode qw();
 
 our $_TABLE_ATTR = { };
@@ -529,7 +530,6 @@ It takes no options. Arguably, this is a bug
 
 sub _SetLastUpdated {
     my $self = shift;
-    use RT::Date;
     my $now = RT::Date->new( $self->CurrentUser );
     $now->SetToNow();
 
@@ -1301,7 +1301,6 @@ sub _AddLink {
     }
 
     # Check if the link already exists - we don't want duplicates
-    use RT::Link;
     my $old_link = RT::Link->new( $self->CurrentUser );
     $old_link->LoadByParams( Base   => $args{'Base'},
                              Type   => $args{'Type'},
@@ -1595,7 +1594,6 @@ Returns an L<RT::Transactions> object of all transactions on this record object
 sub Transactions {
     my $self = shift;
 
-    use RT::Transactions;
     my $transactions = RT::Transactions->new( $self->CurrentUser );
     $transactions->Limit(
         FIELD => 'ObjectId',

-----------------------------------------------------------------------


More information about the Rt-commit mailing list