File Coverage

blib/lib/WebService/ISBNDB/API/Authors.pm
Criterion Covered Total %
statement 55 107 51.4
branch 6 34 17.6
condition 2 6 33.3
subroutine 14 20 70.0
pod 9 9 100.0
total 86 176 48.8


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # This file copyright (c) 2006-2008 by Randy J. Ray, all rights reserved
4             #
5             # See "LICENSE" in the documentation for licensing and redistribution terms.
6             #
7             ###############################################################################
8             #
9             # $Id: Authors.pm 49 2008-04-06 10:45:43Z $
10             #
11             # Description: Specialization of the API class for author data.
12             #
13             # Functions: BUILD
14             # new
15             # copy
16             # find
17             # set_id
18             # get_categories
19             # set_categories
20             # get_subjects
21             # set_subjects
22             # normalize_args
23             #
24             # Libraries: Class::Std
25             # Error
26             # WebService::ISBNDB::API
27             #
28             # Global Consts: $VERSION
29             #
30             ###############################################################################
31              
32             package WebService::ISBNDB::API::Authors;
33              
34 3     3   11201 use 5.006;
  3         10  
  3         113  
35 3     3   25 use strict;
  3         7  
  3         115  
36 3     3   14 use warnings;
  3         7  
  3         111  
37 3     3   15 no warnings 'redefine';
  3         4  
  3         125  
38 3     3   15 use vars qw($VERSION);
  3         5  
  3         132  
39 3     3   14 use base 'WebService::ISBNDB::API';
  3         6  
  3         278  
40              
41 3     3   13 use Class::Std;
  3         5  
  3         21  
42 3     3   285 use Error;
  3         13  
  3         23  
43              
44             $VERSION = "0.21";
45              
46             my %id : ATTR(:init_arg :get :default<>);
47             my %name : ATTR(:name :default<>);
48             my %first_name : ATTR(:name :default<>);
49             my %last_name : ATTR(:name :default<>);
50             my %dates : ATTR(:name :default<>);
51             my %has_books : ATTR(:name :default<>);
52             my %categories : ATTR(:init_arg :default<>);
53             my %subjects : ATTR(:init_arg :default<>);
54              
55             ###############################################################################
56             #
57             # Sub Name: new
58             #
59             # Description: Pass off to the super-class constructor, which handles
60             # the special cases for arguments.
61             #
62             ###############################################################################
63             sub new
64             {
65 3     3   813 shift->SUPER::new(@_);
66             }
67              
68             ###############################################################################
69             #
70             # Sub Name: BUILD
71             #
72             # Description: Builder for this class. See Class::Std.
73             #
74             # Arguments: NAME IN/OUT TYPE DESCRIPTION
75             # $self in ref Object
76             # $id in scalar This object's unique ID
77             # $args in hashref The set of arguments currently
78             # being considered for the
79             # constructor.
80             #
81             # Returns: Success: void
82             # Failure: throws Error::Simple
83             #
84             ###############################################################################
85             sub BUILD
86             {
87 2     2 1 125 my ($self, $id, $args) = @_;
88              
89 2         8 $self->set_type('Authors');
90              
91 2 50 33     8 throw Error::Simple("'categories' must be a list-reference")
92             if ($args->{categories} and (ref($args->{categories}) ne 'ARRAY'));
93              
94 2 50 33     11 throw Error::Simple("'subjects' must be a list-reference")
95             if ($args->{subjects} and (ref($args->{subjects}) ne 'ARRAY'));
96              
97 2         5 return;
98             }
99              
100             ###############################################################################
101             #
102             # Sub Name: copy
103             #
104             # Description: Copy the Authors-specific attributes over from target
105             # object to caller.
106             #
107             # Arguments: NAME IN/OUT TYPE DESCRIPTION
108             # $self in ref Object
109             # $target in ref Object of the same class
110             #
111             # Globals: %id
112             # %name
113             # %first_name
114             # %last_name
115             # %dates
116             # %has_books
117             # %subjects
118             # %categories
119             #
120             # Returns: Success: void
121             # Failure: throws Error::Simple
122             #
123             ###############################################################################
124             sub copy : CUMULATIVE
125             {
126 0     0 1 0 my ($self, $target) = @_;
127              
128 0 0       0 throw Error::Simple("Argument to 'copy' must be the same class as caller")
129             unless (ref($self) eq ref($target));
130              
131 0         0 my $id1 = ident $self;
132 0         0 my $id2 = ident $target;
133              
134             # Do the simple (scalar) attributes first
135 0         0 $id{$id1} = $id{$id2};
136 0         0 $name{$id1} = $name{$id2};
137 0         0 $first_name{$id1} = $first_name{$id2};
138 0         0 $last_name{$id1} = $last_name{$id2};
139 0         0 $dates{$id1} = $dates{$id2};
140 0         0 $has_books{$id1} = $has_books{$id2};
141              
142             # This must be tested and copied by value
143 0 0       0 $categories{$id1} = [ @{$categories{$id2}} ] if ref($categories{$id2});
  0         0  
144 0 0       0 $subjects{$id1} = [ @{$subjects{$id2}} ] if ref($subjects{$id2});
  0         0  
145              
146 0         0 return;
147 3     3   1448 }
  3         7  
  3         17  
148              
149             ###############################################################################
150             #
151             # Sub Name: set_id
152             #
153             # Description: Set the ID attribute on the object. Done manually so that
154             # we can restrict it to this package.
155             #
156             # Arguments: NAME IN/OUT TYPE DESCRIPTION
157             # $self in ref Object
158             # $id in scalar ID, taken from isbndb.com data
159             #
160             # Globals: %id
161             #
162             # Returns: $self
163             #
164             ###############################################################################
165             sub set_id : RESTRICTED
166             {
167 0     0 1 0 my ($self, $id) = @_;
168              
169 0         0 $id{ident $self} = $id;
170 0         0 $self;
171 3     3   816 }
  3         7  
  3         11  
172              
173             ###############################################################################
174             #
175             # Sub Name: set_categories
176             #
177             # Description: Set the list of Categories objects for this instance. The
178             # list will initially be a list of IDs, taken from the
179             # attributes of the XML. Only upon read-access (via
180             # get_categories) will the list be turned into real objects.
181             #
182             # Arguments: NAME IN/OUT TYPE DESCRIPTION
183             # $self in ref Object
184             # $list in ref List-reference of category data
185             #
186             # Globals: %categories
187             #
188             # Returns: Success: $self
189             # Failure: throws Error::Simple
190             #
191             ###############################################################################
192             sub set_categories
193             {
194 0     0 1 0 my ($self, $list) = @_;
195              
196 0 0       0 throw Error::Simple("Argument to 'set_categories' must be a list " .
197             "reference")
198             unless (ref($list) eq 'ARRAY');
199              
200             # Make a copy of the list
201 0         0 $categories{ident $self} = [ @$list ];
202              
203 0         0 $self;
204             }
205              
206             ###############################################################################
207             #
208             # Sub Name: get_categories
209             #
210             # Description: Return a list-reference of the Categories. If this is
211             # the first such request, then the category values are going
212             # to be scalars, not objects, and must be converted to
213             # objects before being returned.
214             #
215             # Arguments: NAME IN/OUT TYPE DESCRIPTION
216             # $self in ref Object
217             #
218             # Globals: %categories
219             #
220             # Returns: Success: list-reference of data
221             # Failure: throws Error::Simple
222             #
223             ###############################################################################
224             sub get_categories
225             {
226 0     0 1 0 my $self = shift;
227              
228 0         0 my $categories = $categories{ident $self};
229              
230             # If any element is not a reference, we need to transform the list
231 0 0       0 if (grep(! ref($_), @$categories))
232             {
233 0         0 my $class = $self->class_for_type('Categories');
234             # Make sure it's loaded
235 0         0 eval "require $class;";
236 0         0 my $cat_id;
237              
238 0         0 for (0 .. $#$categories)
239             {
240 0 0       0 unless (ref($cat_id = $categories->[$_]))
241             {
242 0 0       0 throw Error::Simple("No category found for ID '$cat_id'")
243             unless ref($categories->[$_] = $class->find({ id =>
244             $cat_id }));
245             }
246             }
247             }
248              
249             # Make a copy, so the real reference doesn't get altered
250 0         0 [ @$categories ];
251             }
252              
253             ###############################################################################
254             #
255             # Sub Name: set_subjects
256             #
257             # Description: Set the list of Subjects objects for this instance. The
258             # list will initially be a list of IDs, taken from the
259             # attributes of the XML. Only upon read-access (via
260             # get_subjects) will the list be turned into real objects.
261             #
262             # Arguments: NAME IN/OUT TYPE DESCRIPTION
263             # $self in ref Object
264             # $list in ref List-reference of category data
265             #
266             # Globals: %subjects
267             #
268             # Returns: Success: $self
269             # Failure: throws Error::Simple
270             #
271             ###############################################################################
272             sub set_subjects
273             {
274 0     0 1 0 my ($self, $list) = @_;
275              
276 0 0       0 throw Error::Simple("Argument to 'set_subjects' must be a list reference")
277             unless (ref($list) eq 'ARRAY');
278              
279             # Make a copy of the list
280 0         0 $subjects{ident $self} = [ @$list ];
281              
282 0         0 $self;
283             }
284              
285             ###############################################################################
286             #
287             # Sub Name: get_subjects
288             #
289             # Description: Return a list-reference of the Subjects. If this is
290             # the first such request, then the subject values are going
291             # to be scalars, not objects, and must be converted to
292             # objects before being returned.
293             #
294             # Arguments: NAME IN/OUT TYPE DESCRIPTION
295             # $self in ref Object
296             #
297             # Globals: %subjects
298             #
299             # Returns: Success: list-reference of data
300             # Failure: throws Error::Simple
301             #
302             ###############################################################################
303             sub get_subjects
304             {
305 0     0 1 0 my $self = shift;
306              
307 0         0 my $subjects = $subjects{ident $self};
308              
309             # If any element is not a reference, we need to transform the list
310 0 0       0 if (grep(! ref($_), @$subjects))
311             {
312 0         0 my $class = $self->class_for_type('Subjects');
313             # Make sure it's loaded
314 0         0 eval "require $class;";
315 0         0 my ($subj_id, $books);
316              
317 0         0 for (0 .. $#$subjects)
318             {
319 0 0       0 unless (ref($subj_id = $subjects->[$_]))
320             {
321 0         0 ($subj_id, $books) = split(/:/, $subj_id);
322 0 0       0 throw Error::Simple("No subject found for ID '$subj_id'")
323             unless ref($subjects->[$_] = $class->find({ id =>
324             $subj_id }));
325             # Use the value in $books to override the value from the DB
326 0         0 $subjects->[$_]->set_book_count($books);
327             }
328             }
329             }
330              
331             # Make a copy, so the real reference doesn't get altered
332 0         0 [ @$subjects ];
333             }
334              
335             ###############################################################################
336             #
337             # Sub Name: find
338             #
339             # Description: Find a single record using the passed-in search criteria.
340             # Most of the work is done by the super-class: this method
341             # turns a single-argument call into a proper hashref, and/or
342             # turns user-supplied arguments into those recognized by the
343             # API.
344             #
345             # Arguments: NAME IN/OUT TYPE DESCRIPTION
346             # $self in ref Object
347             # $args in variable See text
348             #
349             # Returns: Success: result from SUPER::find
350             # Failure: throws Error::Simple
351             #
352             ###############################################################################
353             sub find
354             {
355 1     1 1 3 my ($self, $args) = @_;
356              
357             # First, see if we were passed a single scalar for an argument. If so, it
358             # needs to become the id argument
359 1 50       7 $args = { person_id => $args } unless (ref $args);
360              
361 1         10 $self->SUPER::find($args);
362             }
363              
364             ###############################################################################
365             #
366             # Sub Name: normalize_args
367             #
368             # Description: Normalize the contents of the $args hash reference, turning
369             # the user-visible (and user-friendlier) arguments into the
370             # arguments that the API expects.
371             #
372             # Also adds some "results" values, to tailor the returned
373             # content.
374             #
375             # Arguments: NAME IN/OUT TYPE DESCRIPTION
376             # $class in scalar Object ref or class name
377             # $args in hashref Reference to the arguments hash
378             #
379             # Returns: Success: $args (changed)
380             # Failure: throws Error::Simple
381             #
382             ###############################################################################
383             sub normalize_args
384             {
385 1     1 1 2 my ($class, $args) = @_;
386              
387 1         2 my ($key, $value, @keys, $count, $results, %seen);
388              
389             # Turn the collection of arguments into a set that the isbndb.com API can
390             # use. Each key/value pair has to become a pair of the form "indexX" and
391             # "valueX". Some keys, like author and publisher, have to be handled with
392             # more attention.
393 1         4 @keys = keys %$args;
394 1         3 $count = 0; # Used to gradually increment the "indexX" and "valueX" keys
395 1         2 foreach $key (@keys)
396             {
397             # If we see "api_key", it means that WebService::ISBNDB::API::search
398             # curried it into the arglist due to the type-level search being
399             # called as a static method.
400 1 50       4 next if $key eq 'api_key';
401 1         3 $value = $args->{$key};
402 1         3 delete $args->{$key};
403 1         2 $count++;
404              
405             # A key of "id" needs to be translated as "person_id"
406 1 50       4 if ($key eq 'id')
407             {
408 0         0 $args->{"index$count"} = 'person_id';
409 0         0 $args->{"value$count"} = $value;
410              
411 0         0 next;
412             }
413              
414             # These are the only other allowed search-key(s)
415 1 50       9 if ($key =~ /^(:?name|person_id)$/)
416             {
417 1         4 $args->{"index$count"} = $key;
418 1         3 $args->{"value$count"} = $value;
419              
420 1         3 next;
421             }
422              
423 0         0 throw Error::Simple("'$key' is not a valid search-key for publishers");
424             }
425              
426             # Add the "results" values that we want
427 1         5 $args->{results} = [ qw(details subjects categories) ];
428              
429 1         10 $args;
430             }
431              
432             1;
433              
434             =pod
435              
436             =head1 NAME
437              
438             WebService::ISBNDB::API::Authors - Data class for author information
439              
440             =head1 SYNOPSIS
441              
442             use WebService::ISBNDB::API::Authors;
443              
444             $me = WebService::ISBNDB::API::Authors->find('ray_randy_j');
445              
446             =head1 DESCRIPTION
447              
448             The B class extends the
449             B class to add attributes specific to the data
450             B provides on authors.
451              
452             =head1 METHODS
453              
454             The following methods are specific to this class, or overridden from the
455             super-class.
456              
457             =head2 Constructor
458              
459             The constructor for this class may take a single scalar argument in lieu of a
460             hash reference:
461              
462             =over 4
463              
464             =item new($PERSON_ID|$ARGS)
465              
466             This constructs a new object and returns a referent to it. If the parameter
467             passed is a hash reference, it is handled as normal, per B
468             mechanics. If the value is a scalar, it is assumed to be the author's ID
469             within the system, and is looked up by that.
470              
471             If the argument is the hash-reference form, then a new object is always
472             constructed; to perform searches see the search() and find() methods. Thus,
473             the following two lines are in fact different:
474              
475             $book = WebService::ISBNDB::API::Authors->new({ id => "ray_randy_j" });
476              
477             $book = WebService::ISBNDB::API::Authors->new('ray_randy_j');
478              
479             The first creates a new object that has only the C attribute set. The
480             second returns a new object that represents the given author,
481             with all data present.
482              
483             =back
484              
485             The class also defines:
486              
487             =over 4
488              
489             =item copy($TARGET)
490              
491             Copies the target object into the calling object. All attributes (including
492             the ID) are copied. This method is marked "CUMULATIVE" (see L),
493             and any sub-class of this class should provide their own copy() and also mark
494             it "CUMULATIVE", to ensure that all attributes at all levels are copied.
495              
496             =back
497              
498             See the copy() method in L.
499              
500             =head2 Accessors
501              
502             The following attributes are used to maintain the content of an author
503             object:
504              
505             =over 4
506              
507             =item id
508              
509             The unique ID within the B system for this author.
510              
511             =item name
512              
513             The full name of the author.
514              
515             =item first_name
516              
517             The author's first name only.
518              
519             =item last_name
520              
521             The author's last name only.
522              
523             =item dates
524              
525             If the author is deceased, this will have the years of birth and death,
526             separated by a hyphen, e.g., "1900-1988".
527              
528             =item has_books
529              
530             A boolean value indicating whether the author has books in the database.
531              
532             =item categories
533              
534             A list of category objects for the categories the author is listed in.
535              
536             =item subjects
537              
538             A list of subject objects for the subjects the author's books are listed
539             under.
540              
541             The instances of B objects stored in this attribute differ from
542             those instantiated from the service normally. The C attribute
543             in these objects indicates the number of books I
544             that fall into this subject, not the total number of books in the database
545             that do.
546              
547             =back
548              
549             The following accessors are provided to manage these attributes:
550              
551             =over 4
552              
553             =item get_id
554              
555             Return the category ID.
556              
557             =item set_id($ID)
558              
559             Sets the category ID. This method is restricted to this class, and cannot be
560             called outside of it. In general, you shouldn't need to set the ID after the
561             object is created, since B is a read-only source.
562              
563             =item get_name
564              
565             Return the author's name. This is the full name, as would appear in the
566             C field of a B object.
567              
568             =item set_name($NAME)
569              
570             Set the name to the value in C<$NAME>.
571              
572             =item get_first_name
573              
574             Returns the author's first name only.
575              
576             =item set_first_name($NAME)
577              
578             Set the authors's first name. Note that C and C
579             combined may not always be the same value as C.
580              
581             =item get_last_name
582              
583             Get the author's last name only.
584              
585             =item set_last_name($NAME)
586              
587             Set the authors's last name. Note that C and C
588             combined may not always be the same value as C.
589              
590             =item get_dates
591              
592             If the author is deceased, this returns the years of birth and death.
593              
594             =item set_dates($DATES)
595              
596             Set birth-death date range.
597              
598             =item get_has_books
599              
600             Get the boolean indicating whether this author has books in the service
601             database.
602              
603             =item set_has_books($BOOL)
604              
605             Set the boolean flag that indicates whether this author has any books in the
606             service's database.
607              
608             =item get_categories
609              
610             Return a list-reference of the categories this author is listed in. Each
611             element of the list will be an instance of
612             B.
613              
614             =item set_categories($CATEGORIES)
615              
616             Set the categories to the list-reference given in C<$CATEGORIES>. When the
617             author object is first created from the XML data, this list is populated
618             with the IDs of the categories. They are not converted to objects until
619             requested (via get_categories()) by the user.
620              
621             =item get_subjects
622              
623             Return a list-reference of the subjects this author has books in. Each element
624             of the list will be an instance of B. Note
625             that these subject objects differ slightly from having instantiated the same
626             subjects directly; their C attributes indicate the number of books
627             specific to this author that fall under the subject, not the total number of
628             books from the database as a whole.
629              
630             =item set_subjects($SUBJECTS)
631              
632             Set the list of subjects to the contents of the list-reference passed in
633             C<$SUBJECTS>. When the author object is first created from the XML data, this
634             list is populated with the IDs of the subjects. They are not converted to
635             objects until requested (via get_subjects()) by the user.
636              
637             =back
638              
639             =head2 Utility Methods
640              
641             Besides the constructor and the accessors, the following methods are provided
642             for utility:
643              
644             =over 4
645              
646             =item find($ARG|$ARGS)
647              
648             This is a specialization of find() from the parent class. It allows the
649             argument passed in to be a scalar in place of the usual hash reference. If the
650             value is a scalar, it is searched for as if it were the ID. If the value is a
651             hash reference, it is passed to the super-class method.
652              
653             =item normalize_args($ARGS)
654              
655             This method maps the user-visible arguments as defined for find() and search()
656             into the actual arguments that must be passed to the service itself. In
657             addition, some arguments are added to the request to make the service return
658             extra data used for retrieving categories, location, etc. The
659             method changes C<$ARGS> in place, and also returns C<$ARGS> as the value from
660             the method.
661              
662             =back
663              
664             See the next section for an explanation of the available keys for searches.
665              
666             =head1 SEARCHING
667              
668             Both find() and search() allow the user to look up data in the B
669             database. The allowable search fields are limited to a certain set, however.
670             When either of find() or search() are called, the argument to the method
671             should be a hash reference of key/value pairs to be passed as arguments for
672             the search (the exception being that find() can accept a single string, which
673             has special meaning as detailed earlier).
674              
675             Searches in the text fields are done in a case-insensitive manner.
676              
677             The available search keys are:
678              
679             =over 4
680              
681             =item name
682              
683             The value should be a text string. The search returns authors whose name
684             matches the string.
685              
686             =item id|person_id
687              
688             The value should be a text string. The search returns the author whose ID
689             in the system matches the value.
690              
691             =back
692              
693             Note that the names above may not be the same as the corresponding parameters
694             to the service. The names are chosen to match the related attributes as
695             closely as possible, for ease of understanding.
696              
697             =head1 EXAMPLES
698              
699             Get the record for the author of this module:
700              
701             $me = WebService::ISBNDB::API::Authors->find('ray_randy_j');
702              
703             Find all authors with "Clinton" in their name:
704              
705             $clintons = WebService::ISBNDB::API::Authors->
706             search({ name => 'clinton' });
707              
708             =head1 CAVEATS
709              
710             The data returned by this class is only as accurate as the data retrieved from
711             B.
712              
713             The list of results from calling search() is currently limited to 10 items.
714             This limit will be removed in an upcoming release, when iterators are
715             implemented.
716              
717             =head1 SEE ALSO
718              
719             L, L,
720             L
721              
722             =head1 AUTHOR
723              
724             Randy J. Ray Erjray@blackperl.comE
725              
726             =head1 LICENSE
727              
728             This module and the code within are
729             released under the terms of the Artistic License 2.0
730             (http://www.opensource.org/licenses/artistic-license-2.0.php). This
731             code may be redistributed under either the Artistic License or the GNU
732             Lesser General Public License (LGPL) version 2.1
733             (http://www.opensource.org/licenses/lgpl-license.php).
734              
735             =cut