File Coverage

lib/JIRA/REST/Class/Abstract.pm
Criterion Covered Total %
statement 80 129 62.0
branch 15 44 34.0
condition 2 6 33.3
subroutine 129 142 90.8
pod 13 13 100.0
total 239 334 71.5


line stmt bran cond sub pod time code
1             package JIRA::REST::Class::Abstract;
2 4     4   1925 use parent qw( Class::Accessor::Fast JIRA::REST::Class::Mixins );
  4         5  
  4         25  
3 4     4   2291 use strict;
  4         4  
  4         60  
4 4     4   14 use warnings;
  4         3  
  4         73  
5 4     4   51 use 5.010;
  4         12  
6              
7             our $VERSION = '0.10';
8             our $SOURCE = 'CPAN';
9             ## $SOURCE = 'GitHub'; # COMMENT
10             # the line above will be commented out by Dist::Zilla
11              
12             # ABSTRACT: An abstract class for L<JIRA::REST::Class|JIRA::REST::Class> that most of the other objects are based on.
13              
14 4     4   12 use Carp;
  4         4  
  4         183  
15 4     4   13 use Data::Dumper::Concise;
  4         4  
  4         162  
16 4     4   14 use Scalar::Util qw( weaken blessed reftype refaddr);
  4         4  
  4         3711  
17              
18             __PACKAGE__->mk_ro_accessors( qw( data issue lazy_loaded ) );
19              
20             #pod =internal_method B<init>
21             #pod
22             #pod Method to perform post-instantiation initialization of the object. The first
23             #pod argument must be the factory object which created the object. Subclasses of
24             #pod C<JIRA::REST::Class::Abstract> are expected to call
25             #pod C<< $self->SUPER::init(@_); >> somewhere in their own C<init()>.
26             #pod
27             #pod =cut
28              
29             sub init {
30 35     35 1 28 my $self = shift;
31 35         32 my $factory = shift;
32              
33             # the first thing we're passed is supposed to be the factory object
34 35 50 33     175 if ( blessed $factory
35             && blessed $factory eq 'JIRA::REST::Class::Factory' ) {
36              
37             # grab the arguments that the class was called with from the factory
38             # and make new factory and class objects with the same aguments so we
39             # don't have circular dependency issues
40              
41 35         37 my $args = $factory->{args};
42 35         84 $self->factory( $args );
43 35         72 $self->jira( $args );
44             }
45             else {
46             # if we're not passed a factory, let's complain about it
47 0         0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
48 0         0 confess 'factory not passed to init!';
49             }
50              
51             # unload any lazily loaded data
52 35         88 $self->unload_lazy;
53              
54             # init() has to return the object!
55 35         46 return $self;
56             }
57              
58             #pod =internal_method B<unload_lazy>
59             #pod
60             #pod I'm using a hash to track which lazily loaded methods have already been
61             #pod loaded, and this method clears that hash (and the field that got loaded) so
62             #pod they get loaded again.
63             #pod
64             #pod =cut
65              
66             sub unload_lazy {
67 35     35 1 32 my $self = shift;
68 35 50       53 if ( $self->{lazy_loaded} ) {
69 0         0 foreach my $field ( keys %{ $self->{lazy_loaded} } ) {
  0         0  
70 0         0 delete $self->{$field};
71 0         0 delete $self->{lazy_loaded}->{$field};
72             }
73             }
74             else {
75 35         45 $self->{lazy_loaded} = {};
76             }
77 35         33 return;
78             }
79              
80             #pod =internal_method B<populate_scalar_data>
81             #pod
82             #pod Code to make instantiating objects from C<< $self->{data} >> easier. Accepts
83             #pod three unnamed parameters:
84             #pod
85             #pod =over 2
86             #pod
87             #pod =item * key in this object's hash which will hold the resulting object
88             #pod
89             #pod =item * nickname for object type being created (to be passed to C<make_object()>)
90             #pod
91             #pod =item * key under C<< $self->{data} >> that should be passed as the data to C<make_object()>
92             #pod
93             #pod =back
94             #pod
95             #pod =cut
96              
97             sub populate_scalar_data {
98 0     0 1 0 my ( $self, $name, $type, $field ) = @_;
99              
100 0 0       0 if ( defined $self->data->{$field} ) {
101             $self->{$name} = $self->make_object(
102             $type,
103             {
104 0         0 data => $self->data->{$field}
105             }
106             );
107             }
108 0         0 return;
109             }
110              
111             #pod =internal_method B<populate_date_data>
112             #pod
113             #pod Code to make instantiating DateTime objects from C<< $self->{data} >> easier.
114             #pod Accepts two unnamed parameters:
115             #pod
116             #pod =over 2
117             #pod
118             #pod =item * key in this object's hash which will hold the resulting object
119             #pod
120             #pod =item * key under C<< $self->{data} >> that should be passed as the data to C<make_date()>
121             #pod
122             #pod =back
123             #pod
124             #pod =cut
125              
126             sub populate_date_data {
127 0     0 1 0 my ( $self, $name, $field ) = @_;
128 0 0       0 if ( defined $self->data->{$field} ) {
129 0         0 $self->{$name} = $self->make_date( $self->data->{$field} );
130             }
131 0         0 return;
132             }
133              
134             #pod =internal_method B<populate_list_data>
135             #pod
136             #pod Code to make instantiating lists of objects from C<< $self->{data} >> easier.
137             #pod Like L</populate_scalar_data>, it accepts three unnamed parameters:
138             #pod
139             #pod =over 2
140             #pod
141             #pod =item * key in this object's hash which will hold the resulting list reference
142             #pod
143             #pod =item * nickname for object type being created (to be passed to C<make_object()>) as each item in the list
144             #pod
145             #pod =item * key under C<< $self->{data} >> that should be interpreted as a list reference, each element of which is passed as the data to C<make_object()>
146             #pod
147             #pod =back
148             #pod
149             #pod =cut
150              
151             sub populate_list_data {
152 0     0 1 0 my ( $self, $name, $type, $field ) = @_;
153 0 0       0 if ( defined $self->data->{$field} ) {
154             $self->{$name} = [ # stop perltidy from pulling
155             map { # these lines together
156 0         0 $self->make_object( $type, { data => $_ } )
157 0         0 } @{ $self->data->{$field} }
  0         0  
158             ];
159             }
160             else {
161 0         0 $self->{$name} = []; # rather than undefined, return an empty list
162             }
163 0         0 return;
164             }
165              
166             #pod =internal_method B<populate_scalar_field>
167             #pod
168             #pod Code to make instantiating objects from C<< $self->{data}->{fields} >> easier. Accepts
169             #pod three unnamed parameters:
170             #pod
171             #pod =over 2
172             #pod
173             #pod =item * key in this object's hash which will hold the resulting object
174             #pod
175             #pod =item * nickname for object type being created (to be passed to C<make_object()>)
176             #pod
177             #pod =item * key under C<< $self->{data}->{fields} >> that should be passed as the data to C<make_object()>
178             #pod
179             #pod =back
180             #pod
181             #pod =cut
182              
183             sub populate_scalar_field {
184 0     0 1 0 my ( $self, $name, $type, $field ) = @_;
185 0 0       0 if ( defined $self->fields->{$field} ) {
186             $self->{$name} = $self->make_object(
187             $type,
188             {
189 0         0 data => $self->fields->{$field}
190             }
191             );
192             }
193 0         0 return;
194             }
195              
196             #pod =internal_method B<populate_list_field>
197             #pod
198             #pod Code to make instantiating lists of objects from C<< $self->{data}->{fields} >> easier.
199             #pod Like L</populate_scalar_field>, it accepts three unnamed parameters:
200             #pod
201             #pod =over 2
202             #pod
203             #pod =item * key in this object's hash which will hold the resulting list reference
204             #pod
205             #pod =item * nickname for object type being created (to be passed to C<make_object()>) as each item in the list
206             #pod
207             #pod =item * key under C<< $self->{data}->{fields} >> that should be interpreted as a list reference, each element of which is passed as the data to C<make_object()>
208             #pod
209             #pod =back
210             #pod
211             #pod =cut
212              
213             sub populate_list_field {
214 0     0 1 0 my ( $self, $name, $type, $field ) = @_;
215 0 0       0 if ( defined $self->fields->{$field} ) {
216             $self->{$name} = [ # stop perltidy from pulling
217             map { # these lines together
218 0         0 $self->make_object( $type, { data => $_ } )
219 0         0 } @{ $self->fields->{$field} }
  0         0  
220             ];
221             }
222             else {
223 0         0 $self->{$name} = []; # rather than undefined, return an empty list
224             }
225 0         0 return;
226             }
227              
228             ###########################################################################
229             #
230             # the code in here is liberally borrowed from
231             # Class::Accessor, Class::Accessor::Fast, and Class::Accessor::Contextual
232             #
233              
234             if ( eval { require Sub::Name } ) {
235             Sub::Name->import;
236             }
237              
238             #pod =internal_method B<mk_contextual_ro_accessors>
239             #pod
240             #pod Because I didn't want to give up
241             #pod L<Class::Accessor::Fast|Class::Accessor::Fast>, but wanted to be able to
242             #pod make contextual accessors when it was useful. Accepts a list of accessors
243             #pod to make.
244             #pod
245             #pod =cut
246              
247             sub mk_contextual_ro_accessors {
248 28     28 1 81 my ( $class, @fields ) = @_;
249              
250 28         57 foreach my $field ( @fields ) {
251             my $accessor = sub {
252 0 0   0   0 if ( @_ == 1 ) {
        0      
        0      
        0      
        0      
        0      
        0      
        0      
253 0         0 my $ptr = $_[0];
254 0 0       0 return $ptr->{$field} unless wantarray;
255 0 0       0 return @{ $ptr->{$field} } if ref( $ptr->{$field} ) eq 'ARRAY';
  0         0  
256 0 0       0 return %{ $ptr->{$field} } if ref( $ptr->{$field} ) eq 'HASH';
  0         0  
257 0         0 return $ptr->{$field};
258             }
259             else {
260 0         0 my $caller = caller;
261 0         0 $_[0]->_croak( "'$caller' cannot alter the value of '$field' "
262             . "on objects of class '$class'" );
263             }
264 28         85 };
265              
266 28         67 $class->make_subroutine( $field, $accessor );
267             }
268              
269 28         45 return $class;
270             }
271              
272             #pod =internal_method B<mk_deep_ro_accessor>
273             #pod
274             #pod Why do accessors have to be only for the top level of the hash? Why can't
275             #pod they be several layers deep? This method takes a list of keys for the hash
276             #pod this object is based on and creates a contextual accessor that goes down
277             #pod deeper than just the first level.
278             #pod
279             #pod # create accessor for $self->{foo}->{bar}->{baz}
280             #pod __PACKAGE__->mk_deep_ro_accessor(qw/ foo bar baz /);
281             #pod
282             #pod =cut
283              
284             sub mk_deep_ro_accessor {
285 380     380 1 448 my ( $class, @field ) = @_;
286              
287             my $accessor = sub {
288 208 50   208   4520 if ( @_ == 1 ) {
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
        208      
289 208         134 my $ptr = $_[0];
290 208         179 foreach my $f ( @field ) {
291 416         426 $ptr = $ptr->{$f};
292             }
293 208 100       508 return $ptr unless wantarray;
294 15 50       21 return @$ptr if ref( $ptr ) eq 'ARRAY';
295 15 50       18 return %$ptr if ref( $ptr ) eq 'HASH';
296 15         48 return $ptr;
297             }
298             else {
299 0         0 my $caller = caller;
300 0         0 $_[0]->_croak( "'$caller' cannot alter the value of '$field[-1]' "
301             . "on objects of class '$class'" );
302             }
303 380         869 };
304              
305 380         540 $class->make_subroutine( $field[-1], $accessor );
306              
307 380         386 return $class;
308             }
309              
310             #pod =internal_method B<mk_lazy_ro_accessor>
311             #pod
312             #pod Takes two parameters: field to make a lazy accessor for, and a subroutine
313             #pod reference to construct the value of the accessor when it IS loaded.
314             #pod
315             #pod This method makes an accessor with the given name that checks to see if the
316             #pod value for the accessor has been loaded, and, if it hasn't, runs the provided
317             #pod subroutine to construct the value and stores that value for later use.
318             #pod Especially good for loading values that are objects populated by REST calls.
319             #pod
320             #pod # code to construct a lazy accessor named 'foo'
321             #pod __PACKAGE__->mk_lazy_ro_accessor('foo', sub {
322             #pod my $self = shift;
323             #pod # make the value for foo, in say, $foo
324             #pod return $foo;
325             #pod });
326             #pod
327             #pod =cut
328              
329             sub mk_lazy_ro_accessor {
330 64     64 1 99 my ( $class, $field, $constructor ) = @_;
331              
332             my $accessor = sub {
333 4 50   4   2624 if ( @_ == 1 ) {
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
        4      
334 4 100       11 unless ( $_[0]->{lazy_loaded}->{$field} ) {
335 2         7 $_[0]->{$field} = $constructor->( @_ );
336 2         4 $_[0]->{lazy_loaded}->{$field} = 1;
337             }
338 4 100       16 return $_[0]->{$field} unless wantarray;
339 1 50       6 return @{ $_[0]->{$field} } if ref( $_[0]->{$field} ) eq 'ARRAY';
  1         4  
340 0 0       0 return %{ $_[0]->{$field} } if ref( $_[0]->{$field} ) eq 'HASH';
  0         0  
341 0         0 return $_[0]->{$field};
342             }
343             else {
344 0         0 my $caller = caller;
345 0         0 $_[0]->_croak( "'$caller' cannot alter the value of '$field' "
346             . "on objects of class '$class'" );
347             }
348 64         212 };
349              
350 64         73 $class->make_subroutine( $field, $accessor );
351              
352 64         113 return $class;
353             }
354              
355             #pod =internal_method B<mk_data_ro_accessors>
356             #pod
357             #pod Makes accessors for keys under C<< $self->{data} >>
358             #pod
359             #pod =cut
360              
361             sub mk_data_ro_accessors {
362 60     60 1 282 my ( $class, @args ) = @_;
363              
364 60         329 foreach my $field ( @args ) {
365 300         440 $class->mk_deep_ro_accessor( qw( data ), $field );
366             }
367 60         101 return;
368             }
369              
370             #pod =internal_method B<mk_field_ro_accessors>
371             #pod
372             #pod Makes accessors for keys under C<< $self->{data}->{fields} >>
373             #pod
374             #pod =cut
375              
376             sub mk_field_ro_accessors {
377 8     8 1 42 my ( $class, @args ) = @_;
378              
379 8         120 foreach my $field ( @args ) {
380 80         122 $class->mk_deep_ro_accessor( qw( data fields ), $field );
381             }
382 8         15 return;
383             }
384              
385             #pod =internal_method B<make_subroutine>
386             #pod
387             #pod Takes a subroutine name and a subroutine reference, and blesses the
388             #pod subroutine into the class used to call this method. Can be called with
389             #pod either a class name or a blessed object reference.
390             #pod
391             #pod =cut
392              
393             {
394             # we're going some magic here, so we turn off our self-restrictions
395 4     4   18 no strict 'refs'; ## no critic (ProhibitNoStrict)
  4         5  
  4         382  
396              
397             sub make_subroutine {
398 472     472 1 399 my ( $proto, $name, $sub ) = @_;
399 472   33     1107 my ( $class ) = ref $proto || $proto;
400              
401 472         482 my $fullname = "${class}::$name";
402 472 50       286 unless ( defined &{$fullname} ) {
  472         1606  
403 472 50       1416 subname( $fullname, $sub ) if defined &subname;
404 472         287 *{$fullname} = $sub;
  472         1161  
405             }
406 472         506 return;
407             }
408              
409             } # end of ref no-stricture zone
410              
411             1;
412              
413             __END__
414              
415             =pod
416              
417             =encoding UTF-8
418              
419             =for :stopwords Packy Anderson Alexey Melezhik jira JRC
420              
421             =head1 NAME
422              
423             JIRA::REST::Class::Abstract - An abstract class for L<JIRA::REST::Class|JIRA::REST::Class> that most of the other objects are based on.
424              
425             =head1 VERSION
426              
427             version 0.10
428              
429             =head1 METHODS
430              
431             =head2 B<name_for_user>
432              
433             When passed a scalar that could be a
434             L<JIRA::REST::Class::User|JIRA::REST::Class::User> object, returns the name
435             of the user if it is a C<JIRA::REST::Class::User>
436             object, or the unmodified scalar if it is not.
437              
438             =head2 B<key_for_issue>
439              
440             When passed a scalar that could be a
441             L<JIRA::REST::Class::Issue|JIRA::REST::Class::Issue> object, returns the key
442             of the issue if it is a C<JIRA::REST::Class::Issue>
443             object, or the unmodified scalar if it is not.
444              
445             =head2 B<find_link_name_and_direction>
446              
447             When passed two scalars, one that could be a
448             L<JIRA::REST::Class::Issue::LinkType|JIRA::REST::Class::Issue::LinkType>
449             object and another that is a direction (inward/outward), returns the name of
450             the link type and direction if it is a C<JIRA::REST::Class::Issue::LinkType>
451             object, or attempts to determine the link type and direction from the
452             provided scalars.
453              
454             =head2 B<dump>
455              
456             Returns a stringified representation of the object's data generated somewhat
457             by L<Data::Dumper::Concise|Data::Dumper::Concise>, but not descending into
458             any objects that might be part of that data. If it finds objects in the
459             data, it will attempt to represent them in some abbreviated fashion which
460             may not display all the data in the object. For instance, if the object has
461             a C<JIRA::REST::Class::Issue> object in it for an issue with the key
462             C<'JRC-1'>, the object would be represented as the string C<<
463             'JIRA::REST::Class::Issue->key(JRC-1)' >>. The goal is to provide a gist of
464             what the contents of the object are without exhaustively dumping EVERYTHING.
465             I use it a lot for figuring out what's in the results I'm getting back from
466             the JIRA API.
467              
468             =head1 INTERNAL METHODS
469              
470             =head2 B<init>
471              
472             Method to perform post-instantiation initialization of the object. The first
473             argument must be the factory object which created the object. Subclasses of
474             C<JIRA::REST::Class::Abstract> are expected to call
475             C<< $self->SUPER::init(@_); >> somewhere in their own C<init()>.
476              
477             =head2 B<unload_lazy>
478              
479             I'm using a hash to track which lazily loaded methods have already been
480             loaded, and this method clears that hash (and the field that got loaded) so
481             they get loaded again.
482              
483             =head2 B<populate_scalar_data>
484              
485             Code to make instantiating objects from C<< $self->{data} >> easier. Accepts
486             three unnamed parameters:
487              
488             =over 2
489              
490             =item * key in this object's hash which will hold the resulting object
491              
492             =item * nickname for object type being created (to be passed to C<make_object()>)
493              
494             =item * key under C<< $self->{data} >> that should be passed as the data to C<make_object()>
495              
496             =back
497              
498             =head2 B<populate_date_data>
499              
500             Code to make instantiating DateTime objects from C<< $self->{data} >> easier.
501             Accepts two unnamed parameters:
502              
503             =over 2
504              
505             =item * key in this object's hash which will hold the resulting object
506              
507             =item * key under C<< $self->{data} >> that should be passed as the data to C<make_date()>
508              
509             =back
510              
511             =head2 B<populate_list_data>
512              
513             Code to make instantiating lists of objects from C<< $self->{data} >> easier.
514             Like L</populate_scalar_data>, it accepts three unnamed parameters:
515              
516             =over 2
517              
518             =item * key in this object's hash which will hold the resulting list reference
519              
520             =item * nickname for object type being created (to be passed to C<make_object()>) as each item in the list
521              
522             =item * key under C<< $self->{data} >> that should be interpreted as a list reference, each element of which is passed as the data to C<make_object()>
523              
524             =back
525              
526             =head2 B<populate_scalar_field>
527              
528             Code to make instantiating objects from C<< $self->{data}->{fields} >> easier. Accepts
529             three unnamed parameters:
530              
531             =over 2
532              
533             =item * key in this object's hash which will hold the resulting object
534              
535             =item * nickname for object type being created (to be passed to C<make_object()>)
536              
537             =item * key under C<< $self->{data}->{fields} >> that should be passed as the data to C<make_object()>
538              
539             =back
540              
541             =head2 B<populate_list_field>
542              
543             Code to make instantiating lists of objects from C<< $self->{data}->{fields} >> easier.
544             Like L</populate_scalar_field>, it accepts three unnamed parameters:
545              
546             =over 2
547              
548             =item * key in this object's hash which will hold the resulting list reference
549              
550             =item * nickname for object type being created (to be passed to C<make_object()>) as each item in the list
551              
552             =item * key under C<< $self->{data}->{fields} >> that should be interpreted as a list reference, each element of which is passed as the data to C<make_object()>
553              
554             =back
555              
556             =head2 B<mk_contextual_ro_accessors>
557              
558             Because I didn't want to give up
559             L<Class::Accessor::Fast|Class::Accessor::Fast>, but wanted to be able to
560             make contextual accessors when it was useful. Accepts a list of accessors
561             to make.
562              
563             =head2 B<mk_deep_ro_accessor>
564              
565             Why do accessors have to be only for the top level of the hash? Why can't
566             they be several layers deep? This method takes a list of keys for the hash
567             this object is based on and creates a contextual accessor that goes down
568             deeper than just the first level.
569              
570             # create accessor for $self->{foo}->{bar}->{baz}
571             __PACKAGE__->mk_deep_ro_accessor(qw/ foo bar baz /);
572              
573             =head2 B<mk_lazy_ro_accessor>
574              
575             Takes two parameters: field to make a lazy accessor for, and a subroutine
576             reference to construct the value of the accessor when it IS loaded.
577              
578             This method makes an accessor with the given name that checks to see if the
579             value for the accessor has been loaded, and, if it hasn't, runs the provided
580             subroutine to construct the value and stores that value for later use.
581             Especially good for loading values that are objects populated by REST calls.
582              
583             # code to construct a lazy accessor named 'foo'
584             __PACKAGE__->mk_lazy_ro_accessor('foo', sub {
585             my $self = shift;
586             # make the value for foo, in say, $foo
587             return $foo;
588             });
589              
590             =head2 B<mk_data_ro_accessors>
591              
592             Makes accessors for keys under C<< $self->{data} >>
593              
594             =head2 B<mk_field_ro_accessors>
595              
596             Makes accessors for keys under C<< $self->{data}->{fields} >>
597              
598             =head2 B<make_subroutine>
599              
600             Takes a subroutine name and a subroutine reference, and blesses the
601             subroutine into the class used to call this method. Can be called with
602             either a class name or a blessed object reference.
603              
604             =head2 B<jira>
605              
606             Returns a L<JIRA::REST::Class|JIRA::REST::Class> object with credentials for the last JIRA user.
607              
608             =head2 B<factory>
609              
610             An accessor for the L<JIRA::REST::Class::Factory|JIRA::REST::Class::Factory>.
611              
612             =head2 B<JIRA_REST>
613              
614             An accessor that returns the L<JIRA::REST|JIRA::REST> object being used.
615              
616             =head2 B<REST_CLIENT>
617              
618             An accessor that returns the L<REST::Client|REST::Client> object inside the L<JIRA::REST|JIRA::REST> object being used.
619              
620             =head2 B<JSON>
621              
622             An accessor that returns the L<JSON|JSON> object inside the L<JIRA::REST|JIRA::REST> object being used.
623              
624             =head2 B<make_object>
625              
626             A pass-through method that calls L<JIRA::REST::Class::Factory::make_object()|JIRA::REST::Class::Factory/make_object>.
627              
628             =head2 B<make_date>
629              
630             A pass-through method that calls L<JIRA::REST::Class::Factory::make_date()|JIRA::REST::Class::Factory/make_date>.
631              
632             =head2 B<class_for>
633              
634             A pass-through method that calls L<JIRA::REST::Class::Factory::get_factory_class()|JIRA::REST::Class::Factory/get_factory_class>.
635              
636             =head2 B<obj_isa>
637              
638             When passed a scalar that I<could> be an object and a class string,
639             returns whether the scalar is, in fact, an object of that class.
640             Looks up the actual class using C<class_for()>, which calls
641             L<JIRA::REST::Class::Factory::get_factory_class()|JIRA::REST::Class::Factory/get_factory_class>.
642              
643             =head2 B<cosmetic_copy> I<THING>
644              
645             A utility function to produce a "cosmetic" copy of a thing: it clones
646             the data structure, but if anything in the structure (other than the
647             structure itself) is a blessed object, it replaces it with a
648             stringification of that object that probably doesn't contain all the
649             data in the object. For instance, if the object has a
650             C<JIRA::REST::Class::Issue> object in it for an issue with the key
651             C<'JRC-1'>, the object would be represented as the string
652             C<< 'JIRA::REST::Class::Issue->key(JRC-1)' >>. The goal is to provide a
653             gist of what the contents of the object are without exhaustively dumping
654             EVERYTHING.
655              
656             =head1 RELATED CLASSES
657              
658             =over 2
659              
660             =item * L<JIRA::REST::Class|JIRA::REST::Class>
661              
662             =item * L<JIRA::REST::Class::Factory|JIRA::REST::Class::Factory>
663              
664             =item * L<JIRA::REST::Class::Mixins|JIRA::REST::Class::Mixins>
665              
666             =back
667              
668             =head1 AUTHOR
669              
670             Packy Anderson <packy@cpan.org>
671              
672             =head1 COPYRIGHT AND LICENSE
673              
674             This software is Copyright (c) 2017 by Packy Anderson.
675              
676             This is free software, licensed under:
677              
678             The Artistic License 2.0 (GPL Compatible)
679              
680             =cut