File Coverage

blib/lib/Class/Entity.pm
Criterion Covered Total %
statement 19 45 42.2
branch 4 20 20.0
condition 6 18 33.3
subroutine 7 10 70.0
pod 3 3 100.0
total 39 96 40.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Class::Entity - Object interface for relational databases
4              
5             =head1 DESCRIPTION
6              
7             B allows a developer to create an object interface for
8             a relational database by writing a minimal amount of code in a set of
9             sub-classes which correspond to database tables.
10              
11             Right now this module only implements a read only interface. Writes
12             will probably come later.
13              
14             =head1 SYNOPSIS
15              
16             package Table;
17             use base qw(Class::Entity);
18              
19             package main;
20             use DBI;
21             my $dbh = DBI->connect(...);
22             my $table = Table->fetch(dbh => $dbh, key => 1234);
23             print $table->Column;
24              
25             for (Table->find(dbh => $dbh, where => "Name like 'foo%'")) {
26             printf "% %\n", $table->Name, $table->Date;
27             }
28              
29             =head1 METHODS
30              
31             =over 4
32              
33             =cut
34              
35 7     7   147383 use strict;
  7         20  
  7         326  
36 7     7   38 use warnings;
  7         16  
  7         6388  
37              
38             our $VERSION = "0.5";
39              
40             package Class::Entity;
41             our $AUTOLOAD;
42              
43             =item new(dbh => $dbh, data => $data)
44              
45             The constructor creates a new instance of the current class. If you
46             pass in values via B they will be stored in the object for
47             later use.
48              
49             =cut
50              
51             sub new {
52 8     8 1 46 my ($class, %args) = @_;
53 8   50     152 bless {
      100        
      33        
54             _dbh => $args{dbh} || undef,
55             _data => $args{data} || undef
56             }, $class || ref $class;
57             }
58              
59             =item _primary_key()
60              
61             Return the primary key for the table the current sub-class of
62             B represents. You will normally want to overide this
63             in the sub-class but for convenience it returns B by default.
64              
65             The value of this method is used to create the query that is run
66             when a call to B is made.
67              
68             =cut
69              
70 2     2   36 sub _primary_key { "id" }
71              
72             =item _table()
73              
74             Return the name of the table the current sub-class of B
75             represents. You will normally want to overide this in the sub-class
76             but for convenience it returns the final part of the sub-class name;
77             that is: the sub-class name minus the leading /.*:/.
78              
79             =cut
80              
81             sub _table {
82 9     9   32 my $self = shift;
83 9   66     122 my ($table) = (ref($self)||$self) =~ /:?([^:]+)$/;
84 9         61 $table;
85             }
86              
87             =item _relation()
88              
89             This method provides the sub-class author a means of joing accross
90             multiple tables when a call to B or B is made. All
91             database fields returned via these methods are stored in an instance
92             of the current sub-class and exposed via the autoloader. By default
93             it returns all fields in the current table represented by the
94             sub-class.
95              
96             =cut
97              
98             sub _relation {
99 2     2   21 my $self = shift;
100 2         13 sprintf "* from %s", $self->_table;
101             }
102              
103             =item _object_map()
104              
105             The object map works in conjuction with the autoloader. If you have
106             sub-classes of B, which represent tables linked to
107             in the current sub-class, you can overload this method to return a
108             hash where the keys are the table columns and the values are the
109             names of the associated B sub-classes.
110              
111             The object map will only be used for method calls of the form
112             B.
113              
114             Here's an example:
115              
116             package Users;
117             use base qw(Class::Entity);
118              
119             package Departments;
120             use base qw(Class::Entity);
121             sub _object_map({
122             UserID => "Users"
123             })
124              
125             package main;
126             use DBI;
127             my $dbh DBI->connect(...);
128             my @support = Departments->find(dbh => $dbh, where => "Name = 'Support'");
129             for (@support) {
130             printf "%s %s\n", $_->UserID, $_->get_UserID->name;
131             }
132              
133             =cut
134              
135 0     0   0 sub _object_map { }
136              
137             =item fetch(dbh => $dbh, key => $key)
138              
139             Return an instance of the current sub-class. You must provide a
140             database value via B and a primary key value via B. The
141             database handle is stored in the returned object for later use and
142             all table fields are exposed via the auoloader.
143              
144             =cut
145              
146             sub fetch {
147 0     0 1 0 my ($class, %args) = @_;
148 0   0     0 my $dbh = $args{dbh} || die "no database handle";
149 0   0     0 my $key = $args{key} || die "no key argument";
150 0         0 my $query = sprintf "select %s where %s = $key",
151             $class->_relation, $class->_primary_key;
152 0 0       0 my $sth = $dbh->prepare($query) or return undef;
153 0 0       0 $sth->execute or return undef;
154 0         0 $class->new(dbh => $dbh, data => $sth->fetchrow_hashref);
155             }
156              
157             =item find(dbh => $dbh, where => $where)
158              
159             Return an array in array context, or the first item of the array
160             in scalar context, of instances of the current sub-class based on
161             the query modifier passed in via B. You must pass in a
162             database handle via B which will be stored in the returned
163             instances for later use.
164              
165             =cut
166              
167             sub find {
168 0     0 1 0 my ($class, %args) = @_;
169 0   0     0 my $dbh = $args{dbh} || die "no database handle";
170 0   0     0 my $where = $args{where} || die "no where argument";
171 0         0 my $query = sprintf "select %s where %s",
172             $class->_relation, $where;
173 0 0       0 my $sth = $dbh->prepare($query) or return undef;
174 0 0       0 $sth->execute or return undef;
175             wantarray or return # just the first row in scalar context
176 0 0       0 $class->new(dbh => $dbh, data => $sth->fetchrow_hashref);
177 0         0 my @rows;
178 0         0 while (my $r = $sth->fetchrow_hashref) {
179 0         0 push @rows, $class->new(dbh => $dbh, data => $r);
180             }
181 0         0 @rows;
182             }
183              
184             =item AUTOLOAD([$value])
185              
186             The autoloader provides get and set methods for the table values
187             represented in an instance of the current sub-class. For example,
188             if you have a table with the fields: Name, Date, Subject, you would
189             access them like this:
190              
191             package Table;
192             use base qw(Class::Entity);
193              
194             package main;
195             use DBI;
196             my $dbh = DBI->connect(...);
197             my $table = Table->fetch(dbh => $dbh, key => 10);
198             print $table->Name . "\n";
199             print $table->Date . "\n";
200             print $table->Subject . "\n";
201              
202             If you call an anonymous method of the form B, where
203             B is a column represented by the current object, the autoloader
204             will attempt to dispatch the call to the fetch method of the
205             corresponding sub-class of B, if it's listed in the
206             B<_bject_map>.
207              
208             =cut
209              
210             sub AUTOLOAD {
211 1     1   11 my ($self, $arg) = @_;
212 1 50       5 return if $AUTOLOAD =~ /DESTROY$/;
213 1         8 (my $symbol = $AUTOLOAD) =~ s/.*://;
214 1 50       12 if (my ($method) = $symbol =~ /get_(.*)/) {
    50          
215 0         0 my %h = $self->_object_map;
216 0 0       0 if (my $class = $h{$method}) {
217 0         0 return $class->fetch(dbh => $self->{_dbh}, key => $self->$method);
218             }
219 0         0 warn sprintf qq(annonymous method "%s" cannot be mapped), $symbol;
220 0         0 return undef;
221             } elsif (exists $self->{_data}->{$symbol}) {
222 1 50       11 $arg ? $self->{_data}->{$symbol} = $arg : $self->{_data}->{$symbol};
223             } else {
224 0           warn qq(annonymous method "$symbol" cannot be mapped);
225 0           return undef;
226             }
227             }
228              
229             1;
230              
231             =back
232              
233             =head1 SEE ALSO
234              
235             DBI
236              
237             =head1 AUTHORS
238              
239             Paddy Newman,
240              
241             =head1 ACKNOWLEDGEMENTS
242              
243             This is basically a cut-down, slightly modified version of something
244             an ex-colegue of mine wrote and introduced me to. His name is Dan
245             Barlow and he's a much better programmer than me and he deserves
246             all the credit.
247              
248             =cut
249