in reply to Re^11: How to completely destroy class attributes with Test::Most?
in thread How to completely destroy class attributes with Test::Most?

Building on your previous suggestion, now I'm thinking I could do something like this:

$AUTOLOAD =~ /.*::(get|bundle)(_next)*(_\w+)_files*$/

The bundle mode would also create an object that's just a list of files except you would perform operations on. So instead of:

while ($s->get_next_blah_file) { $s->delete($s->selected_file); }
You would just do:
my $bundle = $s->bundle_blah_files; $bundle->delete;

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
$nysus = $PM . ' ' . $MCF;
Click here if you love Perl Monks

Replies are listed 'Best First'.
Re^13: How to completely destroy class attributes with Test::Most?
by jcb (Parson) on Aug 28, 2019 at 23:25 UTC

    The key motivation for independent iterator objects is that they allow the program to have multiple iterators even on the same category by removing the need for the File::Collector object to track the iterators.

    These "bundles" look a lot like independent iterators, and you could easily introduce an ->all method that returns a special object like so:

    (in File::Collector::Iterator)

    sub all { my $self = shift; bless \ $self, 'File::Collector::Iterator::All'; } { package File::Collector::Iterator::All; sub AUTOLOAD { our $AUTOLOAD; my $self = shift; $$self->$AUTOLOAD(@_) while ($$self->next_file); } }

    This allows subclasses of File::Collector::Iterator to define their own "bundle actions" invokable as $iterator->all->$action. For example, a subclass that adds a delete action could contain simply:

    sub delete { unlink (shift)->selected_file }

      I'll give a concrete example to make clear what I mean:

      package FileParser; use parent qw ( Dondley::WestfieldVote::FileCollector ); sub get_data { my $s = shift; return $s->get_obj_prop('data', 'raw_data', $_[0]); } package FileCollector; sub get_obj_prop { my $s = shift; my $obj = shift; my $prop = shift; my $file = shift || $s->selected_file; # grabs the current file in +iterator if (!$prop || !$obj || !$file) { $s->_croak ("Missing arguments to get_obj_prop method" . ' at ' . (caller(0))[1] . ', line ' . (caller(0))[2] ); } my $o = $obj . '_obj'; my $object = $s->{_files}{$file}{$o}; my $attr = "_$prop"; if (! exists $object->{$attr} ) { $s->croak ("Non-existent $obj object attribute requested: '$prop'" . ' at ' . (caller(0))[1] . ', line ' . (caller(0))[2] ); } my $value = $object->{$attr}; if (ref $value eq 'ARRAY') { return @$value; } else { return $value; } } package Data.pm sub new { my $class = shift; my $data = shift; my $obj = bless { _raw_data => $data, # raw Spreadsheet::Read object _stripped_data => undef, _num_rows => undef, _cols => undef, _num_cols => undef, _non_blank_cols => undef, _first_row => undef, }, $class; return $obj; } # And finally, in a file: my $fp = FileParser->new('some/dir'); while ($fp->next_parseable_file) { my $data = $fp->get_data; }

      This seems super convenient. If I'm not using an iterator, I can just pass a file to the get_data() method.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        With the "bundle" model and a slight modification, that last loop becomes:

        foreach my $data ($fp->bundle_parseable_files->all->get_data) { # do something with each $data }

        The AUTOLOAD in File::Collector::Iterator::All is slightly different:

        sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my @result = (); if (defined wantarray) { push @result, $$self->$AUTOLOAD(@_) while ($$self->next_file) +} else { $$self->$AUTOLOAD(@_) while ($$self->next_file) } return wantarray ? @result : \@result; }

      That code for using the bundle did not work but this did:

      { package File::Collector::Iterator::All; sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my @method = split /::/, $AUTOLOAD; my $method = pop @method; $$self->$method(@_) while ($$self->next); } }

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        I see the problem, too: $AUTOLOAD gets the fully-qualified method name, which can be used, but a call with that name will not find inherited methods.

        If performance is an issue, you might want to benchmark that solution against:

        { package File::Collector::Iterator::All; sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my $method = ($AUTOLOAD =~ m/::([^:]+)$/); $$self->$method(@_) while ($$self->next); } }

        I do not know enough to predict which should be faster.

      "The key motivation for independent iterator objects is that they allow the program to have multiple iterators even on the same category by removing the need for the File::Collector object to track the iterators."

      Yes, but there is a big convenience to having File::Collector track the iterators, isn't there? I can call $s->selected_file from anywhere and refer to the same file from any of the various class methods. I don't have to worry about passing the iterator objects around. Or am I missing something?

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        You seem to be missing that you can put all of the "action" methods that your subclasses add onto File::Collector in the subclasses of File::Collector::Iterator instead.

        This gives a better-separated design, where subclasses of File::Collector add categories and subclasses of File::Collector::Iterator add actions. This way, File::Collector is more of a passive data store, while the iterator subclasses perform actions, using a Model/Controller pattern.