File Coverage

blib/lib/RT/Client/REST/Object.pm
Criterion Covered Total %
statement 212 322 65.8
branch 50 116 43.1
condition 23 59 38.9
subroutine 52 62 83.8
pod 18 18 100.0
total 355 577 61.5


line stmt bran cond sub pod time code
1             #!perl
2             # vim: softtabstop=4 tabstop=4 shiftwidth=4 ft=perl expandtab smarttab
3             # PODNAME: RT::Client::REST::Object
4             # ABSTRACT: base class for RT objects
5              
6 9     9   98750 use strict;
  9         35  
  9         227  
7 9     9   36 use warnings;
  9         15  
  9         405  
8              
9             $RT::Client::REST::Object::VERSION = '0.70';
10              
11             use Try::Tiny;
12 9     9   3494 use Params::Validate;
  9         14434  
  9         417  
13 9     9   3584 use RT::Client::REST::Object::Exception;
  9         45421  
  9         444  
14 9     9   3428 use RT::Client::REST::SearchResult;
  9         21  
  9         65  
15 9     9   3936 use DateTime;
  9         21  
  9         209  
16 9     9   6935 use DateTime::Format::DateParse;
  9         4056362  
  9         357  
17 9     9   4223  
  9         61316  
  9         1663  
18              
19             my $class = shift;
20              
21 20     20 1 16569 if (@_ & 1) {
22             RT::Client::REST::Object::OddNumberOfArgumentsException->throw;
23 20 50       85 }
24 0         0  
25             my $self = bless {}, ref($class) || $class;
26             my %opts = @_;
27 20   33     113  
28 20         56 my $id = delete($opts{id});
29             if (defined($id)) {{
30 20         52 $self->id($id);
31 20 100       58 if ($self->can('parent_id')) {
32 2         4 # If object can parent_id, we assume that it's needed for
  2         5  
33 2 50       9 # retrieval.
34             my $parent_id = delete($opts{parent_id});
35             if (defined($parent_id)) {
36 0         0 $self->parent_id($parent_id);
37 0 0       0 } else {
38 0         0 last;
39             }
40 0         0 }
41             if ($self->autoget) {
42             $self->retrieve;
43 2 100       6 }
44 1         3 }}
45              
46             while (my ($k, $v) = each(%opts)) {
47             $self->$k($v);
48 20         92 }
49 0         0  
50             return $self;
51             }
52 20         68  
53              
54             my $class = shift;
55             my $attributes = $class->_attributes;
56              
57 12     12   136 while (my ($method, $settings) = each(%$attributes)) {
58 12         35 no strict 'refs'; ## no critic (ProhibitNoStrict)
59              
60 12         80 *{$class . '::' . $method} = sub {
61 9     9   59 my $self = shift;
  9         21  
  9         1107  
62              
63 169         699 if (@_) {
64 64     64   13582 my $caller = defined((caller(1))[3]) ? (caller(1))[3] : '';
65              
66 64 100       133 if ($settings->{validation} &&
67 22 100       79 # Don't validate values from the server
68             $caller ne __PACKAGE__ . '::from_form')
69 22 100 66     1108 {
70             my @v = @_;
71             Params::Validate::validation_options(
72             on_fail => sub {
73 19         49 no warnings 'uninitialized';
74             RT::Client::REST::Object::InvalidValueException
75             ->throw(
76 9     9   60 "'@v' is not a valid value for attribute '$method'"
  9         17  
  9         19107  
77 1     1   8 );
78             },
79             );
80             validate_pos(@_, $settings->{validation});
81             }
82 19         178  
83 19         1043 $self->{'_' . $method} = shift;
84             $self->_mark_dirty($method);
85              
86 21         190 # Let's try to autosync, shall we? Logic is a bit hairy
87 21         88 # in order to make it efficient.
88             if ($self->autosync && $self->can('store') &&
89             # OK, so id is special. This is so that 'new' would
90             # work.
91 21 50 66     53 'id' ne $method &&
      100        
      66        
      66        
92             'parent_id' ne $method &&
93              
94             # Plus we don't want to store right after retrieving
95             # (that's where from_form is called from).
96             $caller ne __PACKAGE__ . '::from_form')
97             {
98             $self->store;
99             }
100             }
101 2         6  
102             if ($settings->{list}) {
103             my $retval = $self->{'_' . $method} || [];
104             return @$retval;
105 63 100       143 } else {
106 8   50     22 return $self->{'_' . $method};
107 8         33 }
108             };
109 55         255  
110             if ($settings->{is_datetime}) {
111 169         749 *{$class. '::' . $method . '_datetime'} = sub {
112             # All dates are in UTC
113 169 100       332 # http://requesttracker.wikia.com/wiki/REST#Data_format
114 20         93  
115             my ($self) = shift;
116             if (@_) {
117             unless ($_[0]->isa('DateTime')) {
118 3     3   17601 RT::Client::REST::Object::InvalidValueException
119 3 100       13 ->throw(
120 2 100       21 "'$_[0]' is not a valid value for attribute '${method}_datetime'"
121 1         17 );
122              
123             }
124             my $z = $_[0]->clone;
125             $z->set_time_zone('UTC');
126             $self->$method($_[0]->strftime('%a %b %d %T %Y'));
127 1         5 return $z;
128 1         14 }
129 1         263  
130 1         6 return DateTime::Format::DateParse->parse_datetime($self->$method, 'UTC');
131              
132             };
133 1         4 }
134              
135 20         93 if ($settings->{list}) {
136             # Generate convenience methods for list manipulation.
137             my $add_method = $class . '::add_' . $method;
138 169 100       495 my $delete_method = $class . '::delete_' . $method;
139              
140 9         17 *$add_method = sub {
141 9         15 my $self = shift;
142              
143             unless (@_) {
144 1     1   29 RT::Client::REST::Object::NoValuesProvidedException
145             ->throw;
146 1 50       4 }
147 0         0  
148             my @values = $self->$method;
149             my %values = map { $_, 1 } @values;
150              
151 1         4 # Now add new values
152 1         3 for (@_) {
  2         6  
153             $values{$_} = 1;
154             }
155 1         2  
156 3         8 $self->$method([keys %values]);
157             };
158              
159 1         7 *$delete_method = sub {
160 9         58 my $self = shift;
161              
162             unless (@_) {
163 1     1   28 RT::Client::REST::Object::NoValuesProvidedException
164             ->throw;
165 1 50       4 }
166 0         0  
167             my @values = $self->$method;
168             my %values = map { $_, 1 } @values;
169              
170 1         5 # Now delete values
171 1         5 for (@_) {
  5         9  
172             delete $values{$_};
173             }
174 1         5  
175 1         3 $self->$method([keys %values]);
176             };
177             }
178 1         6 }
179 9         76 }
180              
181              
182             my ($self, $attr) = @_;
183             $self->{__dirty}{$attr} = 1;
184             }
185              
186 21     21   49  
187 21         54 my $self = shift;
188              
189             if (exists($self->{__dirty})) {
190             return keys %{$self->{__dirty}};
191             }
192 3     3   7  
193             return;
194 3 100       10 }
195 2         3  
  2         10  
196              
197             my ($self, $cf) = @_;
198 1         3 $self->{__dirty_cf}{$cf} = 1;
199             }
200              
201              
202             my $self = shift;
203 0     0   0  
204 0         0 if (exists($self->{__dirty_cf})) {
205             return keys %{$self->{__dirty_cf}};
206             }
207              
208             return;
209 1     1   3 }
210              
211 1 50       3  
212 0         0 my ($self, $all) = @_;
  0         0  
213             my $attributes = $self->_attributes;
214              
215 1         2 my @attrs = ($all ? keys(%$attributes) : $self->_dirty);
216              
217             my %hash;
218              
219             for my $attr (@attrs) {
220 1     1 1 11 my $rest_name = (exists($attributes->{$attr}{rest_name}) ?
221 1         4 $attributes->{$attr}{rest_name} : ucfirst($attr));
222              
223 1 50       26 my $value;
224             if (exists($attributes->{$attr}{value2form})) {
225 1         2 $value = $attributes->{$attr}{value2form}($self->$attr)
226             } elsif ($attributes->{$attr}{list}) {
227 1         3 $value = join(',', $self->$attr)
228             } else {
229 0 0       0 $value = (defined($self->$attr) ? $self->$attr : '');
230             }
231 0         0  
232 0 0       0 $hash{$rest_name} = $value;
    0          
233 0         0 }
234             my @cfs = ($all ? $self->cf : $self->_dirty_cf);
235 0         0 for my $cf (@cfs) {
236             $hash{'CF-' . $cf} = $self->cf($cf);
237 0 0       0 }
238              
239             return \%hash;
240 0         0 }
241              
242 1 50       7  
243 1         2 my $self = shift;
244 0         0  
245             unless (@_) {
246             RT::Client::REST::Object::NoValuesProvidedException->throw;
247 1         8 }
248              
249             my $hash = shift;
250              
251             unless ('HASH' eq ref($hash)) {
252 0     0 1 0 RT::Client::REST::Object::InvalidValueException->throw(
253             q|Expecting a hash reference as argument to 'from_form'|,
254 0 0       0 );
255 0         0 }
256              
257             # lowercase hash keys
258 0         0 my $i = 0;
259             $hash = { map { ($i++ & 1) ? $_ : lc } %$hash };
260 0 0       0  
261 0         0 my $attributes = $self->_attributes;
262             my %rest2attr; # Mapping of REST names to our attributes;
263             while (my ($attr, $settings) = each(%$attributes)) {
264             my $rest_name = (exists($attributes->{$attr}{rest_name}) ?
265             lc($attributes->{$attr}{rest_name}) : $attr);
266             $rest2attr{$rest_name} = [ $attr, $settings ];
267 0         0 }
268 0 0       0  
  0         0  
269             # Now set attributes:
270 0         0 while (my ($key, $value) = each(%$hash)) {
271 0         0 # Handle custom fields, ideally /(?(1)})/ would be appened to RE
272 0         0 if ( $key =~ m%^(?:cf|customfield)(?:-|\.\{)([#\s\w_:()?/-]+)% ){
273             $key = $1;
274 0 0       0  
275 0         0 # XXX very sketchy. Will fail on long form data e.g; wiki CF
276             if ($value =~ /,/) {
277             $value = [ split(/\s*,\s*/, $value) ];
278             }
279 0         0  
280             $self->cf($key, $value);
281 0 0       0 next;
282 0         0 }
283              
284             unless (exists($rest2attr{$key})) {
285 0 0       0 warn "Unknown key: $key\n";
286 0         0 next;
287             }
288              
289 0         0 my ( $method, $settings) = @{$rest2attr{$key}};
290 0         0  
291             if ($settings->{is_datetime} and $value eq 'Not set') {
292             $value = undef;
293 0 0       0 }
294 0         0  
295 0         0 if (exists($attributes->{$method}{form2value})) {
296             $value = $attributes->{$method}{form2value}($value);
297             } elsif ($attributes->{$method}{list}) {
298 0         0 $value = [split(/\s*,\s*/, $value)],
  0         0  
299             }
300 0 0 0     0 $self->$method($value);
301 0         0 }
302              
303             return;
304 0 0       0 }
    0          
305 0         0  
306             my $self = shift;
307 0         0  
308             $self->_assert_rt_and_id;
309 0         0  
310             my $rt = $self->rt;
311              
312 0         0 my ($hash) = $rt->show(type => $self->rt_type, id => $self->id);
313             $self->from_form($hash);
314              
315             $self->{__dirty} = {};
316 3     3 1 1550 $self->{__dirty_cf} = {};
317              
318 3         12 return $self;
319             }
320 1         6  
321             my $self = shift;
322 1         4  
323 0         0 $self->_assert_rt;
324              
325 0         0 my $rt = $self->rt;
326 0         0  
327             if (defined($self->id)) {
328 0         0 $rt->edit(
329             type => $self->rt_type,
330             id => $self->id,
331             set => $self->to_form,
332 2     2 1 639 );
333             }
334 2         10 else {
335             my $id = $rt->create(
336 1         3 type => $self->rt_type,
337             set => $self->to_form,
338 1 50       3 @_,
339 1         9 );
340             $self->id($id);
341             }
342              
343             $self->{__dirty} = {};
344              
345             return $self;
346 0         0 }
347              
348             my $self = shift;
349              
350             if (@_ & 1) {
351 0         0 RT::Client::REST::Object::OddNumberOfArgumentsException->throw;
352             }
353              
354 0         0 $self->_assert_rt;
355              
356 0         0 my %opts = @_;
357              
358             my $limits = delete($opts{limits}) || [];
359             my $query = '';
360 3     3 1 609  
361             for my $limit (@$limits) {
362 3 50       8 my $kw;
363 0         0 try {
364             $kw = $self->_attr2keyword($limit->{attribute});
365             }
366 3         8 catch {
367             die $_ unless blessed $_ && $_->can('rethrow');
368 2         4  
369             if ($_->isa('RT::Clite::REST::Object::InvalidAttributeException')) {
370 2   50     9 RT::Client::REST::Object::InvalidSearchParametersException
371 2         3 ->throw(shift->message);
372             }
373 2         8 else {
374 0         0 $_->rethrow
375             }
376 0     0   0 };
377             my $op = $limit->{operator};
378             my $val = $limit->{value};
379 0 0 0 0   0 my $agg = $limit->{aggregator} || 'and';
380              
381 0 0       0 if (length($query)) {
382 0         0 $query = "($query) $agg $kw $op '$val'";
383             } else {
384             $query = "$kw $op '$val'";
385             }
386 0         0 }
387              
388 0         0 my $orderby;
389 0         0 try {
390 0         0 # Defaults to 'id' at the moment. Do not rely on this --
391 0   0     0 # implementation may change!
392             $orderby = (delete($opts{reverseorder}) ? '-' : '+') .
393 0 0       0 ($self->_attr2keyword(delete($opts{orderby}) || 'id'));
394 0         0 }
395             catch {
396 0         0 die $_ unless blessed $_ && $_->can('rethrow');
397              
398             if ($_->isa('RT::Client::REST::Object::InvalidAttributeException')) {
399             RT::Client::REST::Object::InvalidSearchParametersException->throw(
400 2         3 shift->message,
401             )
402             }
403             else {
404             $_->rethrow;
405 2 50 50 2   204 }
406             };
407              
408 0 0 0 0   0 my $rt = $self->rt;
409             my @results;
410 0 0       0 try {
411 0         0 @results = $rt->search(
412             type => $self->rt_type,
413             query => $query,
414             orderby => $orderby,
415             );
416 0         0 }
417             catch {
418 2         19 die $_ unless blessed $_ && $_->can('rethrow');
419              
420 2         38 if ($_->isa('RT::Client::REST::InvalidQueryException')) {
421 2         3 RT::Client::REST::Object::InvalidSearchParametersException->throw;
422             }
423 2     2   152 else {
424             $_->rethrow;
425             }
426             };
427              
428             return RT::Client::REST::SearchResult->new(
429             ids => \@results,
430 2 50 33 2   2006 object => sub { $self->new(id => shift, rt => $rt) },
431             );
432 2 50       19 }
433 0         0  
434             my $self = shift;
435             $self->_assert_rt;
436 2         7 return $self->search(@_)->count;
437             }
438 2         13  
439             my ($self, $attr) = @_;
440             my $attributes = $self->_attributes;
441              
442 0     0   0 unless (exists($attributes->{$attr})) {
443 0         0 no warnings 'uninitialized';
444             RT::Clite::REST::Object::InvalidAttributeException->throw(
445             "Attribute '$attr' does not exist in object type '" .
446             ref($self) . "'"
447 2     2 1 616 );
448 2         7 }
449 1         11  
450             return (exists($attributes->{$attr}{rest_name}) ?
451             $attributes->{$attr}{rest_name} :
452             ucfirst($attr));
453 2     2   7 }
454 2         8  
455             my $self = shift;
456 2 50       12 my $method = shift || (caller(1))[3];
457 9     9   74  
  9         19  
  9         5649  
458 0         0 unless (defined($self->rt)) {
459             RT::Client::REST::Object::RequiredAttributeUnsetException
460             ->throw("Cannot '$method': 'rt' attribute of the object ".
461             "is not set");
462             }
463              
464             unless (defined($self->id)) {
465             RT::Client::REST::Object::RequiredAttributeUnsetException
466 2 50       13 ->throw("Cannot '$method': 'id' attribute of the object ".
467             "is not set");
468             }
469             }
470 30     30   40  
471 30   66     79 my $self = shift;
472             my $method = shift || (caller(1))[3];
473 30 100       360  
474 8         69 unless (defined($self->rt)) {
475             RT::Client::REST::Object::RequiredAttributeUnsetException
476             ->throw("Cannot '$method': 'rt' attribute of the object ".
477             "is not set");
478             }
479 22 100       57 }
480 8         53  
481              
482             my $self = shift;
483              
484             unless (@_) {
485             RT::Client::REST::Object::NoValuesProvidedException->throw;
486             }
487 7     7   10  
488 7   33     44 my $name = shift;
489              
490 7 100       273 if (@_) {
491 3         15 $self->{__param}{$name} = shift;
492             }
493              
494             return $self->{__param}{$name};
495             }
496              
497              
498             my $self = shift;
499 0     0 1 0  
500             unless (@_) {
501 0 0       0 # Return a list of CFs.
502 0         0 return keys %{$self->{__cf}};
503             }
504              
505 0         0 my $name = shift;
506             if ('HASH' eq ref($name)) {
507 0 0       0 while (my ($k, $v) = each(%$name)) {
508 0         0 $self->{__cf}{lc($k)} = $v;
509             $self->_mark_dirty_cf($k);
510             }
511 0         0 return keys %{$self->{__cf}};
512             } else {
513             $name = lc $name;
514             if (@_) {
515             $self->{__cf}{$name} = shift;
516 0     0 1 0 $self->_mark_dirty_cf($name);
517             }
518 0 0       0 return $self->{__cf}{$name};
519             }
520 0         0 }
  0         0  
521              
522              
523 0         0 my $self = shift;
524 0 0       0  
525 0         0 if (@_) {
526 0         0 my $rt = shift;
527 0         0 unless (UNIVERSAL::isa($rt, 'RT::Client::REST')) {
528             RT::Client::REST::Object::InvalidValueException->throw;
529 0         0 }
  0         0  
530             $self->{__rt} = $rt;
531 0         0 }
532 0 0       0  
533 0         0 return $self->{__rt};
534 0         0 }
535              
536 0         0  
537             my ($class, $rt) = @_;
538              
539             unless (UNIVERSAL::isa($rt, 'RT::Client::REST')) {
540             RT::Client::REST::Object::InvalidValueException->throw;
541             }
542 68     68 1 10457  
543             no strict 'refs'; ## no critic (ProhibitNoStrict)
544 68 100       137 no warnings 'redefine';
545 18         31 *{(ref($class) || $class) . '::rt'} = sub { $rt };
546 18 100       78 }
547 7         34  
548              
549 11         26  
550             my ($class, $autostore) = @_;
551              
552 61         168 no strict 'refs'; ## no critic (ProhibitNoStrict)
553             no warnings 'redefine';
554             *{(ref($class) || $class) . '::autostore'} = sub { $autostore };
555             }
556              
557 2     2 1 4 my $self = shift;
558              
559 2 100       7 $self->autostore && $self->can('store') && $self->store;
560 1         17 }
561              
562              
563 9     9   68  
  9         21  
  9         365  
564 9     9   52 my ($class, $autoget) = @_;
  9         16  
  9         878  
565 1   33 1   3  
  1         9  
  1         7  
566             no strict 'refs'; ## no critic (ProhibitNoStrict)
567             no warnings 'redefine';
568             *{(ref($class) || $class) . '::autoget'} = sub { $autoget };
569       41 1   }
570              
571              
572 0     0 1 0  
573             my ($class, $autosync) = @_;
574 9     9   60  
  9         19  
  9         270  
575 9     9   58 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         31  
  9         1205  
576 0   0 0   0 no warnings 'redefine';
  0         0  
  0         0  
577             *{(ref($class) || $class) . '::autosync'} = sub { $autosync };
578             }
579              
580 38     38   13886  
581             my ($class, $rt) = @_;
582 38 50 33     139 $class->use_autosync(1);
583             $class->use_autoget(1);
584             $class->use_single_rt($rt);
585             }
586       21 1    
587              
588             1;
589 2     2 1 4  
590              
591 9     9   67 =pod
  9         15  
  9         306  
592 9     9   50  
  9         19  
  9         873  
593 2   33 2   6 =encoding UTF-8
  2         10  
  2         6  
594              
595             =head1 NAME
596              
597       19 1   RT::Client::REST::Object - base class for RT objects
598              
599             =head1 VERSION
600 2     2 1 3  
601             version 0.70
602 9     9   62  
  9         14  
  9         328  
603 9     9   57 =head1 SYNOPSIS
  9         21  
  9         1003  
604 2   33 5   6  
  2         27  
  5         37  
605             # Create a new type
606             package RT::Client::REST::MyType;
607              
608             use parent qw(RT::Client::REST::Object);
609 2     2 1 109  
610 2         9 sub _attributes {{
611 2         9 myattribute => {
612 2         9 validation => {
613             type => SCALAR,
614             },
615             },
616             }}
617              
618             sub rt_type { "mytype" }
619              
620             1;
621              
622             =head1 DESCRIPTION
623              
624             The RT::Client::REST::Object module is a superclass providing a whole
625             bunch of class and object methods in order to streamline the development
626             of RT's REST client interface.
627              
628             =head1 ATTRIBUTES
629              
630             Attributes are defined by method C<_attributes> that should be defined
631             in your class. This method returns a reference to a hash whose keys are
632             the attributes. The values of the hash are attribute settings, which are
633             as follows:
634              
635             =over 2
636              
637             =item list
638              
639             If set to true, this is a list attribute. See
640             L</LIST ATTRIBUTE PROPERTIES> below.
641              
642             =item validation
643              
644             A hash reference. This is passed to validation routines when associated
645             mutator is called. See L<Params::Validate> for reference.
646              
647             =item rest_name
648              
649             =for stopwords FinalPriority
650              
651             This specifies this attribute's REST name. For example, attribute
652             "final_priority" corresponds to RT REST's "FinalPriority". This option
653             may be omitted if the two only differ in first letter capitalization.
654              
655             =item form2value
656              
657             Convert form value (one that comes from the server) into attribute-digestible
658             format.
659              
660             =item value2form
661              
662             Convert value into REST form format.
663              
664             =back
665              
666             Example:
667              
668             sub _attributes {{
669             id => {
670             validation => {
671             type => SCALAR,
672             regex => qr/^\d+$/,
673             },
674             form2value => sub {
675             shift =~ m~^ticket/(\d+)$~i;
676             return $1;
677             },
678             value2form => sub {
679             return 'ticket/' . shift;
680             },
681             },
682             admin_cc => {
683             validation => {
684             type => ARRAYREF,
685             },
686             list => 1,
687             rest_name => 'AdminCc',
688             },
689             }}
690              
691             =head1 LIST ATTRIBUTE PROPERTIES
692              
693             List attributes have the following properties:
694              
695             =over 2
696              
697             =item *
698              
699             When called as accessors, return a list of items
700              
701             =item *
702              
703             When called as mutators, only accept an array reference
704              
705             =item *
706              
707             Convenience methods "add_attr" and "delete_attr" are available. For
708             example:
709              
710             # Get the list
711             my @requestors = $ticket->requestors;
712              
713             # Replace with a new list
714             $ticket->requestors( [qw(dude@localhost)] );
715              
716             # Add some random guys to the current list
717             $ticket->add_requestors('randomguy@localhost', 'evil@local');
718              
719             =back
720              
721             =for stopwords autoget autostore autosync
722              
723             =head1 SPECIAL ATTRIBUTES
724              
725             B<id> and B<parent_id> are special attributes. They are used by
726             various DB-related methods and are especially relied upon by:
727              
728             =over 2
729              
730             =item autostore
731              
732             =item autosync
733              
734             =item autoget
735              
736             =back
737              
738             =head1 METHODS
739              
740             =over 2
741              
742             =item new
743              
744             Constructor
745              
746             =item _generate_methods
747              
748             This class method generates accessors and mutators based on
749             B<_attributes> method which your class should provide. For items
750             that are lists, 'add_' and 'delete_' methods are created. For instance,
751             the following two attributes specified in B<_attributes> will generate
752             methods 'creator', 'cc', 'add_cc', and 'delete_cc':
753              
754             creator => {
755             validation => { type => SCALAR },
756             },
757             cc => {
758             list => 1,
759             validation => { type => ARRAYREF },
760             },
761              
762             =item _mark_dirty($attrname)
763              
764             Mark an attribute as dirty.
765              
766             =item _dirty
767              
768             Return the list of dirty attributes.
769              
770             =item _mark_dirty_cf($attrname)
771              
772             Mark an custom flag as dirty.
773              
774             =item _dirty_cf
775              
776             Return the list of dirty custom flags.
777              
778             =item to_form($all)
779              
780             Convert the object to 'form' (used by REST protocol). This is done based on
781             B<_attributes> method. If C<$all> is true, create a form from all of the
782             object's attributes and custom flags, otherwise use only dirty (see B<_dirty>
783             method) attributes and custom flags. Defaults to the latter.
784              
785             =item from_form
786              
787             Set object's attributes from form received from RT server.
788              
789             =item param($name, $value)
790              
791             Set an arbitrary parameter.
792              
793             =item cf([$name, [$value]])
794              
795             Given no arguments, returns the list of custom field names. With
796             one argument, returns the value of custom field C<$name>. With two
797             arguments, sets custom field C<$name> to C<$value>. Given a reference
798             to a hash, uses it as a list of custom fields and their values, returning
799             the new list of all custom field names.
800              
801             =item rt
802              
803             Get or set the 'rt' object, which should be of type L<RT::Client::REST>.
804              
805             =back
806              
807             =head1 DB METHODS
808              
809             The following are methods that have to do with reading, creating, updating,
810             and searching objects.
811              
812             =over 2
813              
814             =item count
815              
816             Takes the same arguments as C<search()> but returns the actual count of
817             the found items. Throws the same exceptions.
818              
819             =item retrieve
820              
821             Retrieve object's attributes. Note that 'id' attribute must be set for this
822             to work.
823              
824             =item search (%opts)
825              
826             This method is used for searching objects. It returns an object of type
827             L<RT::Client::REST::SearchResult>, which can then be used to process
828             results. C<%opts> is a list of key-value pairs, which are as follows:
829              
830             =over 2
831              
832             =item limits
833              
834             This is a reference to array containing hash references with limits to
835             apply to the search (think SQL limits).
836              
837             =for stopwords orderby reverseorder
838              
839             =item orderby
840              
841             Specifies attribute to sort the result by (in ascending order).
842              
843             =item reverseorder
844              
845             If set to a true value, sorts by attribute specified by B<orderby> in
846             descending order.
847              
848             =back
849              
850             If the client cannot construct the query from the specified arguments,
851             or if the server cannot make it out,
852             C<RT::Client::REST::Object::InvalidSearchParametersException> is thrown.
853              
854             =item store
855              
856             Store the object. If 'id' is set, this is an update; otherwise, a new
857             object is created and the 'id' attribute is set. Note that only changed
858             (dirty) attributes are sent to the server.
859              
860             =back
861              
862             =head1 CLASS METHODS
863              
864             =over 2
865              
866             =item use_single_rt
867              
868             =for stopwords instantiations
869              
870             This method takes a single argument -- L<RT::Client::REST> object
871             and makes this class use it for all instantiations. For example:
872              
873             my $rt = RT::Client::REST->new(%args);
874              
875             # Make all tickets use this RT:
876             RT::Client::REST::Ticket->use_single_rt($rt);
877              
878             # Now make all objects use it:
879             RT::Client::REST::Object->use_single_rt($rt);
880              
881             =for stopwords autostoring autostore
882              
883             =item use_autostore
884              
885             Turn I<autostoring> on and off. I<Autostoring> means that you do not have
886             to explicitly call C<store()> on an object - it will be called when
887             the object goes out of scope.
888              
889             # Autostore tickets:
890             RT::Client::REST::Ticket->use_autostore(1);
891             my $ticket = RT::Client::REST::Ticket->new(%opts)->retrieve;
892             $ticket->priority(10);
893             # Don't have to call store().
894              
895             =item use_autoget
896              
897             Turn I<autoget> feature on or off (off by default). When set to on,
898             C<retrieve()> will be automatically called from the constructor if
899             it is called with that object's special attributes (see
900             L</SPECIAL ATTRIBUTES> above).
901              
902             RT::Client::Ticket->use_autoget(1);
903             my $ticket = RT::Client::Ticket->new(id => 1);
904             # Now all attributes are available:
905             my $subject = $ticket->subject;
906              
907             =for stopwords autosync
908              
909             =item use_autosync
910              
911             Turn I<autosync> feature on or off (off by default). When set, every time
912             an attribute is changed, C<store()> method is invoked. This may be pretty
913             expensive.
914              
915             =item be_transparent
916              
917             This turns on B<autosync> and B<autoget>. Transparency is a neat idea,
918             but it may be expensive and slow. Depending on your circumstances, you
919             may want a finer control of your objects. Transparency makes
920             C<retrieve()> and C<store()> calls invisible:
921              
922             RT::Client::REST::Ticket->be_transparent($rt);
923              
924             my $ticket = RT::Client::REST::Ticket->new(id => $id); # retrieved
925             $ticket->add_cc('you@localhost.localdomain'); # stored
926             $ticket->status('stalled'); # stored
927              
928             # etc.
929              
930             Do not forget to pass RT::Client::REST object to this method.
931              
932             =back
933              
934             =head1 SEE ALSO
935              
936             L<RT::Client::REST::Ticket>,
937             L<RT::Client::REST::SearchResult>.
938              
939             =head1 AUTHOR
940              
941             Dean Hamstead <dean@fragfest.com.au>
942              
943             =head1 COPYRIGHT AND LICENSE
944              
945             This software is copyright (c) 2022, 2020 by Dmitri Tikhonov.
946              
947             This is free software; you can redistribute it and/or modify it under
948             the same terms as the Perl 5 programming language system itself.
949              
950             =cut