File Coverage

blib/lib/WebService/ISBNDB/API/Subjects.pm
Criterion Covered Total %
statement 55 95 57.8
branch 6 28 21.4
condition 0 3 0.0
subroutine 14 18 77.7
pod 7 7 100.0
total 82 151 54.3


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