File Coverage

blib/lib/WebService/ISBNDB/API/Categories.pm
Criterion Covered Total %
statement 55 102 53.9
branch 6 32 18.7
condition 0 6 0.0
subroutine 14 19 73.6
pod 8 8 100.0
total 83 167 49.7


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