[Rt-commit] rt branch, 4.0/apply-scrips-to-multiple-queues, updated. rt-4.0.4-222-g647f208

Ruslan Zakirov ruz at bestpractical.com
Fri Sep 7 08:06:13 EDT 2012


The branch, 4.0/apply-scrips-to-multiple-queues has been updated
       via  647f208e5e148d773d3ad276256001eacd51ba81 (commit)
      from  d894a8b3772fbb2dd842e621d18a7745e799ae1c (commit)

Summary of changes:
 lib/RT/ObjectScrip.pm                |  71 ++++++
 lib/RT/ObjectScrips.pm               |  24 +++
 lib/RT/Record/ApplyAndSort.pm        | 406 +++++++++++++++++++++++++----------
 lib/RT/SearchBuilder/ApplyAndSort.pm |  96 +++++++--
 4 files changed, 467 insertions(+), 130 deletions(-)

- Log -----------------------------------------------------------------
commit 647f208e5e148d773d3ad276256001eacd51ba81
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Sep 7 04:21:20 2012 +0400

    documentation for new ApplyAndSort API

diff --git a/lib/RT/ObjectScrip.pm b/lib/RT/ObjectScrip.pm
index 309a50f..5a66515 100644
--- a/lib/RT/ObjectScrip.pm
+++ b/lib/RT/ObjectScrip.pm
@@ -7,9 +7,74 @@ use base 'RT::Record::ApplyAndSort';
 use RT::Scrip;
 use RT::ObjectScrips;
 
+=head1 NAME
+
+RT::ObjectScrip - record representing application of a scrip to a queue
+
+=head1 DESCRIPTION
+
+This record is created if you want to apply a scrip to a queue or globally.
+
+Inherits methods from L<RT::Record::ApplyAndSort>.
+
+For most operations it's better to use methods in L<RT::Scrip>.
+
+=head1 METHODS
+
+=head2 Table
+
+Returns table name for records of this class.
+
+=cut
+
 sub Table {'ObjectScrips'}
+
+=head2 ObjectCollectionClass
+
+Returns class name of collection of records scrips can be applied to.
+Now it's only L<RT::Queue>, so 'RT::Queues' is returned.
+
+=cut
+
 sub ObjectCollectionClass {'RT::Queues'}
 
+=head2 Create
+
+Creates a record, e.g. applies scrip to a queue or globally.
+
+It's better to use L<RT::Scrip/AddToObject> method instead.
+
+Takes:
+
+=over 4
+
+=item Scrip
+
+Scrip object or id.
+
+=item Stage
+
+Stage of the scrip. The same scrip can be run in different stages.
+
+=item ObjectId
+
+Id or an object this scrip should be applied to. For now it's a queue.
+Use 0 to mean globally.
+
+=item Disabled
+
+Boolean indicator whether this new application is active or not. For now
+all applications of the same Scrip should be either disabled or active.
+
+=item Created, Creator, LastUpdated, LastUpdatedBy
+
+Generated automatically from CurrentUser and the current time, but can be
+overriden.
+
+=back
+
+=cut
+
 sub Create {
     my $self = shift;
     my %args = (@_);
@@ -38,6 +103,12 @@ sub ScripObj {
     return $obj;
 }
 
+=head2 Neighbors
+
+Stage splits scrips into neighborhoods. See L<RT::Record::ApplyAndSort/Neighbors and Siblings>.
+
+=cut
+
 sub Neighbors {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/ObjectScrips.pm b/lib/RT/ObjectScrips.pm
index be67985..b21b30c 100644
--- a/lib/RT/ObjectScrips.pm
+++ b/lib/RT/ObjectScrips.pm
@@ -7,14 +7,38 @@ use base 'RT::SearchBuilder::ApplyAndSort';
 use RT::Scrips;
 use RT::ObjectScrip;
 
+=head1 NAME
+
+RT::ObjectScrips - collection of RT::ObjectScrip records
+
+=head1 DESCRIPTION
+
+Collection of L<RT::ObjectScrip> records. Inherits methods from L<RT::SearchBuilder::ApplyAndSort>.
+
+=head1 METHODS
+
+=cut
+
 sub _Init {
     my $self = shift;
     $self->{'with_disabled_column'} = 1;
     return $self->SUPER::_Init( @_ );
 }
 
+=head2 Table
+
+Returns name of the table where records are stored.
+
+=cut
+
 sub Table { 'ObjectScrips'}
 
+=head2 LimitToScrip
+
+Takes id of a L<RT::Scrip> object and limits this collection.
+
+=cut
+
 sub LimitToScrip {
     my $self = shift;
     my $id = shift;
diff --git a/lib/RT/Record/ApplyAndSort.pm b/lib/RT/Record/ApplyAndSort.pm
index a8a5e40..0f74917 100644
--- a/lib/RT/Record/ApplyAndSort.pm
+++ b/lib/RT/Record/ApplyAndSort.pm
@@ -4,11 +4,42 @@ use warnings;
 package RT::Record::ApplyAndSort;
 use base 'RT::Record';
 
+=head1 NAME
+
+RT::Record::ApplyAndSort - base class for records that can be applied and sorted
+
+=head1 DESCRIPTION
+
+Base class for L<RT::ObjectCustomField> and L<RT::ObjectScrip> that unifies
+application of L<RT::CustomField>s and L<RT::Scrip>s to various objects. Also,
+deals with order of the records.
+
+=head1 METHODS
+
+=head2 Meta information
+
+=head3 CollectionClass
+
+Returns class representing collection for this record class. Basicly adds 's'
+at the end. Should be overriden if default doesn't work.
+
+For example returns L<RT::ObjectCustomFields> when called on L<RT::ObjectCustomField>.
+
+=cut
+
 sub CollectionClass {
     return (ref($_[0]) || $_[0]).'s';
 }
 
-sub ObjectCollectionClass { die "should be subclassed" }
+=head3 TargetField
+
+Returns name of the field in the table where id of object we apply is stored.
+By default deletes everything up to '::Object' from class name.
+This method allows to use friendlier argument names and methods.
+
+For example returns 'Scrip' for L<RT::ObjectScrip>.
+
+=cut
 
 sub TargetField {
     my $class = ref($_[0]) || $_[0];
@@ -16,6 +47,42 @@ sub TargetField {
     return $class;
 }
 
+=head3 ObjectCollectionClass
+
+Takes an object under L</TargetField> name and should return class
+name representing collection the object can be applied to.
+
+Must be overriden by sub classes.
+
+See L<RT::ObjectScrip/ObjectCollectionClass> and L<RT::ObjectCustomField/CollectionClass>.
+
+=cut
+
+sub ObjectCollectionClass { die "should be subclassed" }
+
+=head2 Manipulation
+
+=head3 Create
+
+Takes 'ObjectId' with id of an object we apply to, object we apply to under L</TargetField>
+name, Disabled and SortOrder.
+
+This method doesn't create duplicates. If record already exists then it's not created, but
+loaded instead. Note that nothing is updated if record exist.
+
+If SortOrder is not defined then it's calculated to place new record last. If it's
+provided then it's caller's duty to make sure it is correct value.
+
+Example:
+
+    my $ocf = RT::ObjectCustomField->new( RT->SystemUser );
+    my ($id, $msg) = $ocf->Create( CustomField => 1, ObjectId => 0 );
+
+See L</Apply> which has more error checks. Also, L<RT::Scrip> and L<RT::CustomField>
+have more appropriate methods that B<should be> prefered over calling this directly.
+
+=cut
+
 sub Create {
     my $self = shift;
     my %args = (
@@ -53,6 +120,15 @@ sub Create {
     );
 }
 
+=head3 Apply
+
+Helper method that wraps L</Create> and does more checks to make sure result
+is consistent. Doesn't allow to apply a record to an object if the record
+is already global. Un-applies record from particular objects when asked
+to apply the record globally.
+
+=cut
+
 sub Apply {
     my $self = shift;
     my %args = (@_);
@@ -85,98 +161,12 @@ sub Apply {
     );
 }
 
-sub IsApplied {
-    my $self = shift;
-    my ($tid, $oid) = @_;
-    my $record = $self->new( $self->CurrentUser );
-    $record->LoadByCols( $self->TargetField => $tid, ObjectId => $oid );
-    return $record->id;
-}
-
-=head1 AppliedTo
+=head3 Delete
 
-Returns collection with objects this custom field is applied to.
-Class of the collection depends on L</LookupType>.
-See all L</NotAppliedTo> .
-
-Doesn't takes into account if object is applied globally.
+Deletes this record.
 
 =cut
 
-sub AppliedTo {
-    my $self = shift;
-
-    my ($res, $alias) = $self->_AppliedTo( @_ );
-    return $res unless $res;
-
-    $res->Limit(
-        ALIAS     => $alias,
-        FIELD     => 'id',
-        OPERATOR  => 'IS NOT',
-        VALUE     => 'NULL',
-    );
-
-    return $res;
-}
-
-=head1 NotAppliedTo
-
-Returns collection with objects this custom field is not applied to.
-Class of the collection depends on L</LookupType>.
-See all L</AppliedTo> .
-
-Doesn't takes into account if object is applied globally.
-
-=cut
-
-sub NotAppliedTo {
-    my $self = shift;
-
-    my ($res, $alias) = $self->_AppliedTo( @_ );
-    return $res unless $res;
-
-    $res->Limit(
-        ALIAS     => $alias,
-        FIELD     => 'id',
-        OPERATOR  => 'IS',
-        VALUE     => 'NULL',
-    );
-
-    return $res;
-}
-
-sub _AppliedTo {
-    my $self = shift;
-    my %args = (@_);
-
-    my $field = $self->TargetField;
-    my $target = $args{ $field } || $self->TargetObj;
-
-    my ($class) = $self->ObjectCollectionClass( $field => $target );
-    return undef unless $class;
-
-    my $res = $class->new( $self->CurrentUser );
-
-    # If target applied to a Group, only display user-defined groups
-    $res->LimitToUserDefinedGroups if $class eq 'RT::Groups';
-
-    $res->OrderBy( FIELD => 'Name' );
-    my $alias = $res->Join(
-        TYPE   => 'LEFT',
-        ALIAS1 => 'main',
-        FIELD1 => 'id',
-        TABLE2 => $self->Table,
-        FIELD2 => 'ObjectId',
-    );
-    $res->Limit(
-        LEFTJOIN => $alias,
-        ALIAS    => $alias,
-        FIELD    => $field,
-        VALUE    => $target->id,
-    );
-    return ($res, $alias);
-}
-
 sub Delete {
     my $self = shift;
 
@@ -193,6 +183,17 @@ sub Delete {
     return $self->SUPER::Delete;
 }
 
+=head3 DeleteAll
+
+Helper method to delete all applications for one target (Scrip, CustomField, ...).
+Target can be provided in arguments. If it's not then L</TargetObj> is used.
+
+    $object_scrip->DeleteAll;
+
+    $object_scrip->DeleteAll( Scrip => $scrip );
+
+=cut
+
 sub DeleteAll {
     my $self = shift;
     my %args = (@_);
@@ -208,6 +209,12 @@ sub DeleteAll {
     $_->Delete foreach @{ $list->ItemsArrayRef };
 }
 
+=head3 SetDisabledOnAll
+
+Like L</DeleteAll>, but changes Disabled field on all records.
+
+=cut
+
 sub SetDisabledOnAll {
     my $self = shift;
     my %args = (@_);
@@ -232,22 +239,17 @@ sub SetDisabledOnAll {
     return (1, $self->loc("Disabled all applications") );
 }
 
-=head2 Sorting scrips applications
-
-scrips sorted on multiple layers. First of all custom
-fields with different lookup type are sorted independently. All
-global scrips have fixed order for all objects, but you
-can insert object specific scrips between them. Object
-specific scrips can be applied to several objects and
-be on different place. For example you have GCF1, GCF2, LCF1,
-LCF2 and LCF3 that applies to tickets. You can place GCF2
-above GCF1, but they will be in the same order in all queues.
-However, LCF1 and other local can be placed at any place
-for particular queue: above global, between them or below.
+sub IsApplied {
+    my $self = shift;
+    my ($tid, $oid) = @_;
+    my $record = $self->new( $self->CurrentUser );
+    $record->LoadByCols( $self->TargetField => $tid, ObjectId => $oid );
+    return $record->id;
+}
 
 =head3 MoveUp
 
-Moves scrip up. See </Sorting scrips applications>.
+Moves record up.
 
 =cut
 
@@ -255,12 +257,18 @@ sub MoveUp { return shift->Move( Up => @_ ) }
 
 =head3 MoveDown
 
-Moves scrip down. See </Sorting scrips applications>.
+Moves record down.
 
 =cut
 
 sub MoveDown { return shift->Move( Down => @_ ) }
 
+=head3 Move
+
+Takes 'up' or 'down'. One method that implements L</MoveUp> and L</MoveDown>.
+
+=cut
+
 sub Move {
     my $self = shift;
     my $dir = lc(shift || 'up');
@@ -353,6 +361,117 @@ sub Move {
     return (1,"Moved");
 }
 
+=head2 Accessors, instrospection and traversing.
+
+=head3 TargetObj
+
+Returns target object of this record. Returns L<RT::Scrip> object for
+L<RT::ObjectScrip>.
+
+=cut
+
+sub TargetObj {
+    my $self = shift;
+    my $id   = shift;
+
+    my $method = $self->TargetField .'Obj';
+    return $self->$method( $id );
+}
+
+=head1 AppliedTo
+
+Returns collection with objects target of this record is applied to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</NotAppliedTo>.
+
+For example returns L<RT::Queues> collection if the target is L<RT::Scrip>.
+
+Returns empty collection if target is applied globally.
+
+=cut
+
+sub AppliedTo {
+    my $self = shift;
+
+    my ($res, $alias) = $self->_AppliedTo( @_ );
+    return $res unless $res;
+
+    $res->Limit(
+        ALIAS     => $alias,
+        FIELD     => 'id',
+        OPERATOR  => 'IS NOT',
+        VALUE     => 'NULL',
+    );
+
+    return $res;
+}
+
+=head1 NotAppliedTo
+
+Returns collection with objects target of this record is not applied to.
+Class of the collection depends on L</ObjectCollectionClass>.
+See all L</AppliedTo>.
+
+Returns empty collection if target is applied globally.
+
+=cut
+
+sub NotAppliedTo {
+    my $self = shift;
+
+    my ($res, $alias) = $self->_AppliedTo( @_ );
+    return $res unless $res;
+
+    $res->Limit(
+        ALIAS     => $alias,
+        FIELD     => 'id',
+        OPERATOR  => 'IS',
+        VALUE     => 'NULL',
+    );
+
+    return $res;
+}
+
+sub _AppliedTo {
+    my $self = shift;
+    my %args = (@_);
+
+    my $field = $self->TargetField;
+    my $target = $args{ $field } || $self->TargetObj;
+
+    my ($class) = $self->ObjectCollectionClass( $field => $target );
+    return undef unless $class;
+
+    my $res = $class->new( $self->CurrentUser );
+
+    # If target applied to a Group, only display user-defined groups
+    $res->LimitToUserDefinedGroups if $class eq 'RT::Groups';
+
+    $res->OrderBy( FIELD => 'Name' );
+    my $alias = $res->Join(
+        TYPE   => 'LEFT',
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => $self->Table,
+        FIELD2 => 'ObjectId',
+    );
+    $res->Limit(
+        LEFTJOIN => $alias,
+        ALIAS    => $alias,
+        FIELD    => $field,
+        VALUE    => $target->id,
+    );
+    return ($res, $alias);
+}
+
+=head3 NextSortOrder
+
+Returns next available SortOrder value in the L<neighborhood|/Neighbors>.
+Pass arguments to L</Neighbors> and can take optional ObjectId argument,
+calls ObjectId if it's not provided.
+
+=cut
+
 sub NextSortOrder {
     my $self = shift;
     my %args = (@_);
@@ -373,6 +492,12 @@ sub NextSortOrder {
     return $first->SortOrder + 1;
 }
 
+=head3 IsSortOrderShared
+
+Returns true if this record shares SortOrder value with a L<neighbor|/Neighbors>.
+
+=cut
+
 sub IsSortOrderShared {
     my $self = shift;
     return 0 unless $self->ObjectId;
@@ -383,19 +508,82 @@ sub IsSortOrderShared {
     return $neighbors->Count;
 }
 
-sub TargetObj {
-    my $self = shift;
-    my $id   = shift;
+=head2 Neighbors and Siblings
 
-    my $method = $self->TargetField .'Obj';
-    return $self->$method( $id );
-}
+These two methods should only be understood by developers who wants
+to implement new classes of records that can be applied to other records
+and sorted.
+
+Main purpose is to maintain SortOrder values.
+
+Let's take a look at custom fields. A custom field can be created for tickets,
+queues, transactions, users... Custom fields created for tickets can
+be applied globally or to particular set of queues. Custom fields for
+tickets are neighbors. Neighbor custom fields applied to the same objects
+are siblings. Custom field applied globally is sibling to all neighbors.
+
+For scrips Stage defines neighborhood.
+
+Let's look at the three scrips in create stage S1, S2 and S3, queues Q1 and Q2 and
+G for global.
+
+    S1 at Q1, S3 at Q2 0
+    S2 at G         1
+    S1 at Q2        2
+
+Above table says that S2 is applied globally, S1 is applied to Q1 and executed
+before S2 in this queue, also S1 is applied to Q1, but exectued after S2 in this
+queue, S3 is only applied to Q2 and executed before S2 and S1.
+
+Siblings are scrips applied to an object including globally applied or only
+globally applied. In our example there are three different collection
+of siblings: (S2) - global, (S1, S2) for Q1, (S3, S2, S1) for Q2.
+
+Sort order can be shared between neighbors, but can not be shared between siblings.
+
+Here is what happens with sort order if we move S1 at Q2 one position up:
+
+           S3 at Q2 0
+    S1 at Q1, S1 at Q2 1
+    S2 at G         2
+
+One position more:
+
+           S1 at Q2 0
+    S1 at Q1, S3 at Q2 1
+    S2 at G         2
+
+Hopefuly it's enough to understand how it works.
+
+Targets from different neighborhood can not be sorted against each other.
+
+=head3 Neighbors
+
+Returns collection of records of this class with all
+neighbors. By default all possible targets are neighbors.
+
+Takes the same arguments as L</Create> method. If arguments are not passed
+then uses the current record.
+
+See L</Neighbors and Siblings> for detailed description.
+
+See L<RT::ObjectCustomField/Neighbors> for example.
+
+=cut
 
 sub Neighbors {
     my $self = shift;
     return $self->CollectionClass->new( $self->CurrentUser );
 }
 
+=head3 Siblings
+
+Returns collection of records of this class with siblings.
+
+Takes the same arguments as L</Neighbors>. Siblings is subset of L</Neighbors>.
+
+=cut
+
 sub Siblings {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/SearchBuilder/ApplyAndSort.pm b/lib/RT/SearchBuilder/ApplyAndSort.pm
index 8d335da..3837adb 100644
--- a/lib/RT/SearchBuilder/ApplyAndSort.pm
+++ b/lib/RT/SearchBuilder/ApplyAndSort.pm
@@ -4,11 +4,21 @@ use warnings;
 package RT::SearchBuilder::ApplyAndSort;
 use base 'RT::SearchBuilder';
 
-sub RecordClass {
-    my $class = ref($_[0]) || $_[0];
-    $class =~ s/s$// or return undef;
-    return $class;
-}
+=head1 NAME
+
+RT::SearchBuilder::ApplyAndSort - base class for 'apply and sort' collections
+
+=head1 DESCRIPTION
+
+Base class for collections where records can be applied to objects with order.
+See also L<RT::Record::ApplyAndSort>. Used by L<RT::ObjectScrips> and
+L<RT::ObjectCustomFields>.
+
+As it's about sorting then collection is sorted by SortOrder field.
+
+=head1 METHODS
+
+=cut
 
 sub _Init {
     my $self = shift;
@@ -26,17 +36,53 @@ sub _Init {
     return $self->SUPER::_Init(@_);
 }
 
+=head2 RecordClass
+
+Returns class name of records in this collection. This generic implementation
+just strips trailing 's'.
+
+=cut
+
+sub RecordClass {
+    my $class = ref($_[0]) || $_[0];
+    $class =~ s/s$// or return undef;
+    return $class;
+}
+
+=head2 LimitToObjectId
+
+Takes id of an object and limits collection.
+
+=cut
+
 sub LimitToObjectId {
     my $self = shift;
     my $id = shift || 0;
     $self->Limit( FIELD => 'ObjectId', VALUE => $id );
 }
 
+=head2 NewItem
+
+Returns an empty new collection's item
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    return $self->RecordClass->new( $self->CurrentUser );
+}
+
+=head1 METHODS FOR TARGETS
+
+Rather than implementing a base class for targets (L<RT::Scrip>,
+L<RT::CustomField>) and its collections. This class provides
+class methods to limit target collections.
+
 =head2 LimitTargetToNotApplied
 
-Takes either list of object ids or nothing. Limits collection
-to custom fields to listed objects or any corespondingly. Use
-zero to mean global.
+Takes a collection object and optional list of object ids. Limits
+the collection to records not applied to listed objects or if
+the list is empty then any object. Use 0 (zero) to mean global.
 
 =cut
 
@@ -59,8 +105,8 @@ sub LimitTargetToNotApplied {
 
 =head2 LimitTargetToApplied
 
-Limits collection to custom fields to listed objects or any corespondingly. Use
-zero to mean global.
+L</LimitTargetToNotApplied> with reverse meaning. Takes the same
+arguments.
 
 =cut
 
@@ -81,6 +127,16 @@ sub LimitTargetToApplied {
     return $alias;
 }
 
+=head2 JoinTargetToApplied
+
+Joins collection to this table using left join, limits joined table
+by ids if those are provided.
+
+Returns alias of the joined table. Join is cached and re-used for
+multiple calls.
+
+=cut
+
 sub JoinTargetToApplied {
     my $self = shift;
     my $collection = shift;
@@ -104,6 +160,15 @@ sub JoinTargetToApplied {
     return $alias;
 }
 
+=head3 JoinTargetToThis
+
+Joins target collection to this table using TargetField.
+
+Takes New and Left arguments. Use New to avoid caching and re-using
+this join. Use Left to create LEFT JOIN rather than inner.
+
+=cut
+
 sub JoinTargetToThis {
     my $self = shift;
     my $collection = shift;
@@ -125,17 +190,6 @@ sub JoinTargetToThis {
     return $collection->{ $key } = $alias;
 }
 
-=head2 NewItem
-
-Returns an empty new collection's item
-
-=cut
-
-sub NewItem {
-    my $self = shift;
-    return $self->RecordClass->new( $self->CurrentUser );
-}
-
 RT::Base->_ImportOverlays();
 
 1;

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


More information about the Rt-commit mailing list