make the stemma a property of the tradition
Tara L Andrews [Sat, 31 Dec 2011 23:22:45 +0000 (00:22 +0100)]
lib/Text/Tradition.pm
lib/Text/Tradition/Analysis.pm
lib/Text/Tradition/Directory.pm
t/text_tradition.t
t/text_tradition_directory.t

index e10198d..bbdb45b 100644 (file)
@@ -3,6 +3,7 @@ package Text::Tradition;
 use Module::Load;
 use Moose;
 use Text::Tradition::Collation;
+use Text::Tradition::Stemma;
 use Text::Tradition::Witness;
 
 use vars qw( $VERSION );
@@ -32,6 +33,12 @@ has 'name' => (
     isa => 'Str',
     default => 'Tradition',
     );
+    
+has 'stemma' => (
+       is => 'ro',
+       isa => 'Text::Tradition::Stemma',
+       writer => '_add_stemma',
+       );
   
 # Create the witness before trying to add it
 around 'add_witness' => sub {
@@ -276,6 +283,40 @@ sub BUILD {
     }
 }
 
+=head2 add_stemma( $dotfile )
+
+Initializes a Text::Tradition::Stemma object from the given dotfile,
+and associates it with the tradition.
+
+=begin testing
+
+use Text::Tradition;
+
+my $t = Text::Tradition->new( 
+    'name'  => 'simple test', 
+    'input' => 'Tabular',
+    'file'  => 't/data/simple.txt',
+    );
+
+my $s;
+ok( $s = $t->add_stemma( 't/data/simple.dot' ), "Added a simple stemma" );
+is( ref( $s ), 'Text::Tradition::Stemma', "Got a stemma object returned" );
+is( $t->stemma, $s, "Stemma is the right one" );
+
+=end testing
+
+=cut
+
+sub add_stemma {
+       my( $self, $dot ) = @_;
+       open my $stemma_fh, '<', $dot or warn "Could not open file $dot";
+       my $stemma = Text::Tradition::Stemma->new( 
+               'collation' => $self->collation,
+               'dot' => $stemma_fh );
+       $self->_add_stemma( $stemma ) if $stemma;
+       return $stemma;
+}
+
 no Moose;
 __PACKAGE__->meta->make_immutable;
 
index 4842b69..56b943c 100644 (file)
@@ -11,10 +11,17 @@ use vars qw/ @EXPORT_OK /;
 @EXPORT_OK = qw/ run_analysis group_variants wit_stringify /;
 
 sub run_analysis {
-       my( $tradition, $stemma ) = @_;
+       my( $tradition ) = @_;
        # What we will return
        my $variants = [];
        my $data = {};
+       
+       # We need a stemma in order to run this...
+       unless( $tradition->has_stemma ) {
+               warn "Tradition '" . $tradition->name . "' has no stemma to analyze";
+               return undef;
+       }
+       my $stemma = $tradition->stemma;
                
        # We have the collation, so get the alignment table with witnesses in rows.
        # Also return the reading objects in the table, rather than just the words.
index 3487d90..42c39f2 100644 (file)
@@ -21,14 +21,11 @@ Text::Tradition::Directory - a KiokuDB interface for storing and retrieving trad
   );
   
   my $tradition = Text::Tradition->new( @args );
+  my $stemma = $tradition->add_stemma( $dotfile ); 
   $d->save_tradition( $tradition );
-  my $stemma = Text::Tradition::Stemma->new( 
-       'dot' => $dotfile, 'collation' => $tradition->collation );
-  $d->save_stemma( $stemma );
   
   foreach my $id ( $d->traditions ) {
        print $d->tradition( $id )->name;
-       print $d->stemma( $id )->as_svg;
   }
     
 =head1 DESCRIPTION
@@ -39,14 +36,7 @@ Text::Tradition::Directory is an interface for storing and retrieving text tradi
 
 =head2 new
 
-Returns a Directory object.  Apart from those documented in L<KiokuX::Model>,
-options include:
-
-=over
-
-=item * preload - Load all traditions and stemmata into memory upon instantiation.  Defaults to true.  (TODO manage on-demand loading)
-
-=back
+Returns a Directory object. 
 
 =head2 tradition_ids
 
@@ -56,23 +46,15 @@ Returns the ID of all traditions in the database.
 
 Returns the Text::Tradition object of the given ID.
 
-=head2 stemma( $id )
-
-Returns the Text::Tradition::Stemma object associated with the given tradition ID.
-
-=head2 save_tradition( $tradition )
-
-Writes the given tradition to the database, returning its UUID.
+=head2 save( $tradition )
 
-=head2 save_stemma( $stemma )
-
-Writes the given stemma to the database, returning its UUID.
+Writes the given tradition to the database, returning its ID.
 
 =begin testing
 
+use Test::Warn;
 use File::Temp;
 use Text::Tradition;
-use Text::Tradition::Stemma;
 use_ok 'Text::Tradition::Directory';
 
 my $fh = File::Temp->new();
@@ -81,64 +63,41 @@ $fh->close;
 my $dsn = "dbi:SQLite:dbname=$file";
 
 my $d = Text::Tradition::Directory->new( 'dsn' => $dsn,
-    'extra_args' => { 'create' => 1 } );
+       'extra_args' => { 'create' => 1 } );
 is( ref $d, 'Text::Tradition::Directory', "Got directory object" );
 
+my $scope = $d->new_scope;
 my $t = Text::Tradition->new( 
-    'name'  => 'inline', 
-    'input' => 'Tabular',
-    'file'  => 't/data/simple.txt',
-    );
-my $uuid = $d->save_tradition( $t );
+       'name'  => 'inline', 
+       'input' => 'Tabular',
+       'file'  => 't/data/simple.txt',
+       );
+my $uuid = $d->save( $t );
 ok( $uuid, "Saved test tradition" );
 
-my $s = Text::Tradition::Stemma->new( 
-       'collation' => $t->collation,
-       'dotfile' => 't/data/simple.dot' );
-my $sid = $d->save_stemma( $s );
-ok( $sid, "Saved test stemma" );
-
+my $s = $t->add_stemma( 't/data/simple.dot' );
+ok( $d->save( $t ), "Updated tradition with stemma" );
 is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" );
-is( $d->stemma( $uuid ), $s, "Correct stemma returned for id" );
-is( scalar $d->tradition_ids, 1, "Only one tradition in DB" );
+is( $d->tradition( $uuid )->stemma, $s, "...and it has the correct stemma" );
+warning_like { $d->save( $s ) } qr/not a Text::Tradition/, "Correctly failed to save stemma directly";
 
-# Connect to a new instance
 my $e = Text::Tradition::Directory->new( 'dsn' => $dsn );
-is( scalar $e->tradition_ids, 1, "One tradition preloaded from DB" );
+$scope = $e->new_scope;
+is( scalar $e->tradition_ids, 1, "Directory index has our tradition" );
 my $te = $e->tradition( $uuid );
-is( $te->name, $t->name, "New instance returns correct tradition" );
-my $se = $e->stemma( $uuid );
-is( $se->graph, $s->graph, "New instance returns correct stemma" );
-is( $e->tradition( 'NOT-A-UUID' ), undef, "Undef returned for non-tradition" );
-is( $e->stemma( 'NOT-A-UUID' ), undef, "Undef returned for non-stemma" );
-$te->name( "Changed name" );
-my $new_id = $e->save_tradition( $te );
-is( $new_id, $uuid, "Updated tradition ID did not change" );
-
-my $f = Text::Tradition::Directory->new( 'dsn' => $dsn, 'preload' => 0 );
-is( scalar $f->tradition_ids, 0, "No traditions preloaded from DB" );
-### TODO This doesn't work, as I cannot get an object scope in the
-### 'tradition' wrapper.
-# my $tf = $f->tradition( $uuid );
-# is( $tf->name, $t->name, "Next instance returns correct tradition" );
-# is( $tf->name, "Changed name", "Change to tradition carried through" );
+is( $te->name, $t->name, "Retrieved the tradition from a new directory" );
+my $sid = $e->object_to_id( $te->stemma );
+warning_like { $e->tradition( $sid ) } qr/not a Text::Tradition/, "Did not retrieve stemma via tradition call";
+warning_like { $e->delete( $sid ) } qr/Cannot directly delete non-Tradition object/, "Stemma object not deleted from DB";
+$e->delete( $uuid );
+ok( !$e->exists( $uuid ), "Object is deleted from DB" );
+is( scalar $e->tradition_ids, 0, "Object is deleted from index" );
+
 
 =end testing
 
 =cut
 
-has data_hash => (
-    traits => ['Hash'],
-       default => sub { {} },
-    handles => {
-        tradition     => 'get',
-        stemma           => 'get',
-        add_tradition => 'set',
-        add_stemma       => 'set',
-        tradition_ids => 'keys',
-    },
-);
-       
 has +typemap => (
        is => 'rw',
        isa => 'KiokuDB::TypeMap',
@@ -152,142 +111,72 @@ has +typemap => (
        },
 );
 
-has preload => (
-       is => 'ro',
-       isa => 'Bool',
-       default => 1,
-       );
-
-around 'tradition' => sub {
-       my( $orig, $self, @arg ) = @_;
-       my $data = $self->$orig( @arg );
-       unless( $data ) {
-               # Connect to the DB and fetch the thing.
-               $self->new_scope;
-               my $id = shift @arg;
-               my $trad = $self->lookup( $id );
-               if( ref( $trad ) eq 'Text::Tradition' ) {
-                       $self->add_tradition( $id => $trad );
-                       return $trad;
-               } 
-               # If we got this far...
-               return undef;
-       }
-       return $data->{'object'};
-};
-
-around 'stemma' => sub {
-       my( $orig, $self, @arg ) = @_;
-       my $data = $self->$orig( @arg );
-       unless( $data ) {
-               # Connect to the DB and fetch the thing.
-               $self->new_scope;
-               my $id = shift @arg;
-               my $trad = $self->lookup( $id );
-               if( ref( $trad ) eq 'Text::Tradition' ) {
-                       # Add it
-                       $self->add_tradition( $id => $trad );
-                       # Find the stemma whose collation belongs to $trad
-                       my $ret = $self->grep( sub { $_->collation eq $trad->collation } );
-                       my $stemma;
-                       until ( $ret->is_done ) {
-                               foreach my $st ( $ret->items ) {
-                                       warn "Found two saved stemmas for tradition $id" if $stemma;
-                                       $stemma = $st;
-                               }
-                       }
-                       if( $stemma ) {
-                               $self->add_stemma( $stemma );
-                               return $stemma;
-                       }
-               } 
-               # If we got this far...
-               return undef;
-       }
-       return $data->{'stemma'};
-};
-
-around 'add_tradition' => sub {
-       my( $orig, $self, $id, $obj ) = @_;
-       $self->$orig( $id => { 'object' => $obj } );
-};
-
-around 'add_stemma' => sub {
-       my( $orig, $self, $id, $obj ) = @_;
-       $self->{data_hash}->{$id}->{'stemma'} = $obj;
-};
-
-# Load all the relevant data from the DSN we were passed.
+has tradition_index => (
+    traits => ['Hash'],
+    isa => 'HashRef[Str]',
+    handles => {
+        add_index              => 'set',
+        del_index              => 'delete',
+        name                   => 'get',
+        tradition_ids  => 'keys',
+    },
+    default => sub { {} },
+    );
 
+# Populate the tradition index.
 sub BUILD {
        my $self = shift;
-       my $args = shift;
-       
-       $self->fetch_all if( $self->dsn && $self->preload );
-}
-
-# Connect to self, get the traditions and stemmas, and save them
-# in the directory.
-sub fetch_all {
-       my $self = shift;
-       my $scope = $self->new_scope;
        my $stream = $self->root_set;
-       my %stemmata;
        until( $stream->is_done ) {
                foreach my $obj ( $stream->items ) {
                        my $uuid = $self->object_to_id( $obj );
                        if( ref( $obj ) eq 'Text::Tradition' ) {
-                               $self->add_tradition( $uuid => $obj );
-                       } elsif( ref( $obj ) eq 'Text::Tradition::Stemma' ) {
-                               $stemmata{$obj->collation} = $obj;
+                                $self->add_index( $uuid => $obj->name );
                        } else {
-                               warn "Found root object in DB that is neither tradition nor stemma: $obj";
+                               warn "Found root object in DB that is not a Text::Tradition";
                        }
                }
        }
-       # Now match the stemmata to their traditions.
-       foreach my $id ( $self->tradition_ids ) {
-               my $c = $self->tradition( $id )->collation;
-               if( exists $stemmata{$c} ) {
-                       $self->add_stemma( $id => $stemmata{$c} );
-               }
-       }
+       return $self;
 }
-       
 
-sub save_tradition {
-       my( $self, $tradition ) = @_;
-       # Write the thing to the db and return its ID.
-       unless( ref( $tradition ) eq 'Text::Tradition' ) {
-               warn "Object $tradition is not a Text::Tradition";
-               return undef;
+# If a tradition is deleted, remove it from the index.
+around delete => sub {
+       my $orig = shift;
+       my $self = shift;
+       warn "Only the first object will be deleted" if @_ > 1;
+       my $arg = shift;
+       my $obj = ref( $arg ) ? $arg : $self->lookup( $arg );
+       my $id = ref( $arg ) ? $self->object_to_id( $arg ) : $arg;
+       unless( ref $obj eq 'Text::Tradition' ) {
+               warn "Cannot directly delete non-Tradition object $obj";
+               return;
+       }
+       $self->$orig( $arg );
+       $self->del_index( $id );
+};
+
+sub save {
+       my( $self, $obj ) = @_;
+       unless( ref( $obj ) eq 'Text::Tradition' ) {
+               warn "Object $obj is not a Text::Tradition";
+               return;
        }
-       my $scope = $self->new_scope;
-       my $uuid = $self->store( $tradition );
-       $self->add_tradition( $uuid => $tradition );
+       my $uuid = $self->store( $obj );
+       $self->add_index( $uuid => $obj->name ) if $uuid;
        return $uuid;
 }
 
-sub save_stemma {
-       my( $self, $stemma ) = @_;
-       unless( ref( $stemma ) eq 'Text::Tradition::Stemma' ) {
-               warn "Object $stemma is not a Text::Tradition::Stemma";
-               return undef;
-       }
-       my $scope = $self->new_scope;
-       # Get the tradition to which this stemma belongs.
-       my $tradition = $stemma->collation->tradition;
-       # Make sure the tradition is in the DB.
-       my $tid = $self->save_tradition( $tradition );
-       unless( $tid ) {
-               warn "Could not access this stemma's tradition; aborting";
-               return undef;
+
+sub tradition {
+       my( $self, $id ) = @_;
+       my $obj = $self->lookup( $id );
+       unless( ref( $obj ) eq 'Text::Tradition' ) {
+               warn "Retrieved object is a " . ref( $obj ) . ", not a Text::Tradition";
+               return;
        }
-       my $sid = $self->store( $stemma );
-       $self->add_stemma( $tid => $stemma );
-       return $tid;
+       return $obj;
 }
-       
 
 1;
        
index 2c36c23..eae4e3a 100644 (file)
@@ -47,5 +47,23 @@ is( scalar $s->witnesses, 3, "object has three witnesses again" );
 
 
 
+# =begin testing
+{
+use Text::Tradition;
+
+my $t = Text::Tradition->new( 
+    'name'  => 'simple test', 
+    'input' => 'Tabular',
+    'file'  => 't/data/simple.txt',
+    );
+
+my $s;
+ok( $s = $t->add_stemma( 't/data/simple.dot' ), "Added a simple stemma" );
+is( ref( $s ), 'Text::Tradition::Stemma', "Got a stemma object returned" );
+is( $t->stemma, $s, "Stemma is the right one" );
+}
+
+
+
 
 1;
index e2c2667..efc4f23 100644 (file)
@@ -8,9 +8,9 @@ $| = 1;
 
 # =begin testing
 {
+use Test::Warn;
 use File::Temp;
 use Text::Tradition;
-use Text::Tradition::Stemma;
 use_ok 'Text::Tradition::Directory';
 
 my $fh = File::Temp->new();
@@ -19,47 +19,35 @@ $fh->close;
 my $dsn = "dbi:SQLite:dbname=$file";
 
 my $d = Text::Tradition::Directory->new( 'dsn' => $dsn,
-    'extra_args' => { 'create' => 1 } );
+       'extra_args' => { 'create' => 1 } );
 is( ref $d, 'Text::Tradition::Directory', "Got directory object" );
 
+my $scope = $d->new_scope;
 my $t = Text::Tradition->new( 
-    'name'  => 'inline', 
-    'input' => 'Tabular',
-    'file'  => 't/data/simple.txt',
-    );
-my $uuid = $d->save_tradition( $t );
+       'name'  => 'inline', 
+       'input' => 'Tabular',
+       'file'  => 't/data/simple.txt',
+       );
+my $uuid = $d->save( $t );
 ok( $uuid, "Saved test tradition" );
 
-my $s = Text::Tradition::Stemma->new( 
-       'collation' => $t->collation,
-       'dotfile' => 't/data/simple.dot' );
-my $sid = $d->save_stemma( $s );
-ok( $sid, "Saved test stemma" );
-
+my $s = $t->add_stemma( 't/data/simple.dot' );
+ok( $d->save( $t ), "Updated tradition with stemma" );
 is( $d->tradition( $uuid ), $t, "Correct tradition returned for id" );
-is( $d->stemma( $uuid ), $s, "Correct stemma returned for id" );
-is( scalar $d->tradition_ids, 1, "Only one tradition in DB" );
+is( $d->tradition( $uuid )->stemma, $s, "...and it has the correct stemma" );
+warning_like { $d->save( $s ) } qr/not a Text::Tradition/, "Correctly failed to save stemma directly";
 
-# Connect to a new instance
 my $e = Text::Tradition::Directory->new( 'dsn' => $dsn );
-is( scalar $e->tradition_ids, 1, "One tradition preloaded from DB" );
+$scope = $e->new_scope;
+is( scalar $e->tradition_ids, 1, "Directory index has our tradition" );
 my $te = $e->tradition( $uuid );
-is( $te->name, $t->name, "New instance returns correct tradition" );
-my $se = $e->stemma( $uuid );
-is( $se->graph, $s->graph, "New instance returns correct stemma" );
-is( $e->tradition( 'NOT-A-UUID' ), undef, "Undef returned for non-tradition" );
-is( $e->stemma( 'NOT-A-UUID' ), undef, "Undef returned for non-stemma" );
-$te->name( "Changed name" );
-my $new_id = $e->save_tradition( $te );
-is( $new_id, $uuid, "Updated tradition ID did not change" );
-
-my $f = Text::Tradition::Directory->new( 'dsn' => $dsn, 'preload' => 0 );
-is( scalar $f->tradition_ids, 0, "No traditions preloaded from DB" );
-### TODO This doesn't work, as I cannot get an object scope in the
-### 'tradition' wrapper.
-# my $tf = $f->tradition( $uuid );
-# is( $tf->name, $t->name, "Next instance returns correct tradition" );
-# is( $tf->name, "Changed name", "Change to tradition carried through" );
+is( $te->name, $t->name, "Retrieved the tradition from a new directory" );
+my $sid = $e->object_to_id( $te->stemma );
+warning_like { $e->tradition( $sid ) } qr/not a Text::Tradition/, "Did not retrieve stemma via tradition call";
+warning_like { $e->delete( $sid ) } qr/Cannot directly delete non-Tradition object/, "Stemma object not deleted from DB";
+$e->delete( $uuid );
+ok( !$e->exists( $uuid ), "Object is deleted from DB" );
+is( scalar $e->tradition_ids, 0, "Object is deleted from index" );
 }