restore Directory deletion, albeit without garbage collection
[scpubgit/stemmatology.git] / lib / Text / Tradition / Directory.pm
1 package Text::Tradition::Directory;
2
3 use strict;
4 use warnings;
5 use Moose;
6 use KiokuDB::GC::Naive;
7 use KiokuDB::TypeMap;
8 use KiokuDB::TypeMap::Entry::Naive;
9 use Text::Tradition::Error;
10
11 extends 'KiokuX::Model';
12
13 =head1 NAME
14
15 Text::Tradition::Directory - a KiokuDB interface for storing and retrieving traditions
16
17 =head1 SYNOPSIS
18
19   use Text::Tradition::Directory;
20   my $d = Text::Tradition::Directory->new( 
21     'dsn' => 'dbi:SQLite:mytraditions.db',
22     'extra_args' => { 'create' => 1 },
23   );
24   
25   my $tradition = Text::Tradition->new( @args );
26   my $stemma = $tradition->add_stemma( dotfile => $dotfile ); 
27   $d->save_tradition( $tradition );
28   
29   foreach my $id ( $d->traditions ) {
30         print $d->tradition( $id )->name;
31   }
32     
33 =head1 DESCRIPTION
34
35 Text::Tradition::Directory is an interface for storing and retrieving text traditions and all their data, including an associated stemma hypothesis.  It is an instantiation of a KiokuDB::Model, storing traditions and associated stemmas by UUID.
36
37 =head1 METHODS
38
39 =head2 new
40
41 Returns a Directory object. 
42
43 =head2 tradition_ids
44
45 Returns the ID of all traditions in the database.
46
47 =head2 tradition( $id )
48
49 Returns the Text::Tradition object of the given ID.
50
51 =head2 save( $tradition )
52
53 Writes the given tradition to the database, returning its ID.
54
55 =head2 delete( $tradition )
56
57 Deletes the given tradition object from the database.
58 WARNING!! Garbage collection does not yet work. Use this sparingly.
59
60 =begin testing
61
62 use TryCatch;
63 use File::Temp;
64 use Text::Tradition;
65 use_ok 'Text::Tradition::Directory';
66
67 my $fh = File::Temp->new();
68 my $file = $fh->filename;
69 $fh->close;
70 my $dsn = "dbi:SQLite:dbname=$file";
71 my $uuid;
72 my $t = Text::Tradition->new( 
73         'name'  => 'inline', 
74         'input' => 'Tabular',
75         'file'  => 't/data/simple.txt',
76         );
77
78 {
79         my $d = Text::Tradition::Directory->new( 'dsn' => $dsn,
80                 'extra_args' => { 'create' => 1 } );
81         is( ref $d, 'Text::Tradition::Directory', "Got directory object" );
82         
83         my $scope = $d->new_scope;
84         $uuid = $d->save( $t );
85         ok( $uuid, "Saved test tradition" );
86         
87         my $s = $t->add_stemma( dotfile => 't/data/simple.dot' );
88         ok( $d->save( $t ), "Updated tradition with stemma" );
89         is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" );
90         is( $d->tradition( $uuid )->stemma(0), $s, "...and it has the correct stemma" );
91         try {
92                 $d->save( $s );
93         } catch( Text::Tradition::Error $e ) {
94                 is( $e->ident, 'database error', "Got exception trying to save stemma directly" );
95                 like( $e->message, qr/Cannot directly save non-Tradition object/, 
96                         "Exception has correct message" );
97         }
98 }
99 my $nt = Text::Tradition->new(
100         'name' => 'CX',
101         'input' => 'CollateX',
102         'file' => 't/data/Collatex-16.xml',
103         );
104 is( ref( $nt ), 'Text::Tradition', "Made new tradition" );
105
106 {
107         my $f = Text::Tradition::Directory->new( 'dsn' => $dsn );
108         my $scope = $f->new_scope;
109         is( scalar $f->tradition_ids, 1, "Directory index has our tradition" );
110         my $nuuid = $f->save( $nt );
111         ok( $nuuid, "Stored second tradition" );
112         is( scalar $f->tradition_ids, 2, "Directory index has both traditions" );
113         my $tf = $f->tradition( $uuid );
114         is( $tf->name, $t->name, "Retrieved the tradition from a new directory" );
115         my $sid = $f->object_to_id( $tf->stemma(0) );
116         try {
117                 $f->tradition( $sid );
118         } catch( Text::Tradition::Error $e ) {
119                 is( $e->ident, 'database error', "Got exception trying to fetch stemma directly" );
120                 like( $e->message, qr/not a Text::Tradition/, "Exception has correct message" );
121         }
122         try {
123                 $f->delete( $sid );
124         } catch( Text::Tradition::Error $e ) {
125                 is( $e->ident, 'database error', "Got exception trying to delete stemma directly" );
126                 like( $e->message, qr/Cannot directly delete non-Tradition object/, 
127                         "Exception has correct message" );
128         }
129         $f->delete( $uuid );
130         ok( !$f->exists( $uuid ), "Object is deleted from DB" );
131         ok( !$f->exists( $sid ), "Object stemma also deleted from DB" );
132         is( scalar $f->tradition_ids, 1, "Object is deleted from index" );
133 }
134
135 {
136         my $g = Text::Tradition::Directory->new( 'dsn' => $dsn );
137         my $scope = $g->new_scope;
138         is( scalar $g->tradition_ids, 1, "Now one object in new directory index" );
139 }
140
141 =end testing
142
143 =cut
144
145 has +typemap => (
146         is => 'rw',
147         isa => 'KiokuDB::TypeMap',
148         default => sub { 
149                 KiokuDB::TypeMap->new(
150                         isa_entries => {
151                                 "Graph" => KiokuDB::TypeMap::Entry::Naive->new,
152                                 "Graph::AdjacencyMap" => KiokuDB::TypeMap::Entry::Naive->new,
153                         }
154                 );
155         },
156 );
157
158 before [ qw/ store update insert delete / ] => sub {
159         my $self = shift;
160         my @nontrad;
161         foreach my $obj ( @_ ) {
162                 if( ref( $obj ) && ref( $obj ) ne 'Text::Tradition' ) {
163                         # Is it an id => Tradition hash?
164                         if( ref( $obj ) eq 'HASH' && keys( %$obj ) == 1 ) {
165                                 my( $k ) = keys %$obj;
166                                 next if ref( $obj->{$k} ) eq 'Text::Tradition';
167                         }
168                         push( @nontrad, $obj );
169                 }
170         }
171         if( @nontrad ) {
172                 throw( "Cannot directly save non-Tradition object of type "
173                         . ref( $nontrad[0] ) );
174         }
175 };
176
177 # TODO Garbage collection doesn't work. Suck it up and live with the 
178 # inflated DB.
179 # after delete => sub {
180 #       my $self = shift;
181 #       my $gc = KiokuDB::GC::Naive->new( backend => $self->directory->backend );
182 #       $self->directory->backend->delete( $gc->garbage->members );
183 # };
184
185 sub save {
186         my $self = shift;
187         return $self->store( @_ );
188 }
189
190 sub tradition {
191         my( $self, $id ) = @_;
192         my $obj = $self->lookup( $id );
193         unless( ref( $obj ) eq 'Text::Tradition' ) {
194                 throw( "Retrieved object is a " . ref( $obj ) . ", not a Text::Tradition" );
195         }
196         return $obj;
197 }
198
199 sub tradition_ids {
200         my $self = shift;
201         my @ids;
202         $self->scan( sub { push( @ids, $self->object_to_id( @_ ) ) } );
203         return @ids;
204 }
205
206 sub throw {
207         Text::Tradition::Error->throw( 
208                 'ident' => 'database error',
209                 'message' => $_[0],
210                 );
211 }
212
213 1;
214         
215 =head1 LICENSE
216
217 This package is free software and is provided "as is" without express
218 or implied warranty.  You can redistribute it and/or modify it under
219 the same terms as Perl itself.
220
221 =head1 AUTHOR
222
223 Tara L Andrews E<lt>aurum@cpan.orgE<gt>