[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