DBIx-Class-TemporalRelations

 view release on metacpan or  search on metacpan

lib/DBIx/Class/TemporalRelations.pm  view on Meta::CPAN

      'modified'  => [
          [ 'doodads', 'modified_dt' ],
          [ 'doohickies_modified', 'modified_dt' ], ],
      'purchased' => [ 'doohickies_purchased', 'purchased_dt' ],
   );
   
   # Later code:
   use My::Schema;

   ...

   # There can be only one!
   my $person = $schema->resultset('Person')->find({ name => 'D Ruth Holloway'});
   my @doodads_she_modified = $person->doodads_modified;  # Doodad rows
   my $first_doodad = $person->first_doodad_created;  # Doodad row or undef
   my @doohickies = $person->doohickies_modified;  # Doohickey rows

=head1 DESCRIPTION

This module sets up some convenience methods describing temporal relationships between
data elements. A fairly-common construct would be to have a table of users, who are
creating things, and we want to see lists of the things that they created, in a time
order. In SQL, this might be:

   SELECT id, serial_number, created_dt 
   FROM thing 
   WHERE creator = (SELECT id FROM user WHERE username = 'geekruthie')
   ORDER BY created_dt;

And in conventional L<DBIx::Class> parlance:

   $schema->resultset('User')->find({ username => 'geekruthie'} )->things
      ->search({},{ order_by => 'created_dt'});

Easy enough, but with this module, you can do some more things that would require a bit more yak-shaving:

   my $user = $schema->resultset('User')->find({username => 'geekruthie'});
   @things = $user->things_created;
   @things = $user->things_created_before($ts);
   @things = $user->things_created_after($ts);

These methods let you order things in reverse-date order:

   @things = $user->most_recent_things_created;
   @things = $user->most_recent_things_created_before($ts);
   @things = $user->most_recent_things_created_after($ts);

...and let's pick a specific thing, shall we?

   $thing = $user->first_thing_created;
   $thing = $user->last_thing_created;
   $thing = $user->first_thing_created_after($ts);
   $thing = $user->last_thing_created_before($ts);

And if you could also B<modify> things, and stashed the last time the thing was modified, and by whom, in the
C<thing> table:

   @thing = $user->things_modified;
   $thing = $user->last_thing_modified;

(...but see L</BUGS AND LIMITATIONS> for an important limitation on this behavior!)

=head1 CONFIGURATION

In your Result class, once you've loaded this component, you have three ways to 
add temporal relationships:

=head2 Direct injection into C<source_info>

   __PACKAGE__->source_info(
      {
         temporal_relationships =>
            { 'contraptions' => [ { verb => 'purchased', temporal_column => 'purchase_dt' } ] }
      }
   );

The C<temporal_relationships> sub-hash of C<source_info> can be manually populated. It is a
hashref, defined thusly:

   {  '<relationship accessor>' => [
         { verb => '<desired_verb>',                               # mandatory
           temporal_column => '<column_name_to_use_for_ordering>', # mandatory
           singular => '<singular_noun>',                          # optional
           plural => '<plural_noun>'                               # optional 
         },
      ...],
   ...}

If you do not specify the C<singular> or C<plural> terms, they will be inflected from
the C<relationship_accessor>.

=head2 Single method call

   __PACKAGE__->make_temporal_relationship(
      '<desired_verb>',                    # mandatory
      '<relationship_accessor>',           # mandatory
      '<column_name_to_use_for_ordering>', # mandatory
      '<singular_noun>'                    # optional
      '<plural_noun>'                      # optional
   );

=head2 Multiple-relationship method call

   __PACKAGE__->make_temporal_relationships(
     '<desired_verb>'  => [
         [ '<relationship_accessor>',          # mandatory
           '<column_name_to_use_for_ordering>' # mandatory
           '<singular_noun>'                   # optional
           '<plural_noun>'                     # optional
         ], [...]
      ],
      ...
   );

=head1 DEPENDENCIES

=over 4

=item L<DBIx::Class>

=item Optionally, L<DBIx::Class::Candy>

=item L<Lingua::EN::Inflexion>

=item L<Sub::Quote>

=back

=head1 BUGS AND LIMITATIONS

=over 4

=item Overwriteable fields

If you set a temporal relationship on a field that can be overwritten, for example C<modified_by>, realize
that the temporal relationship will disappear. This isn't a full activity log! For that, you probably want
something like L<DBIx::Class::AuditAny> or other similar journaling module.

=back

=head1 ACKNOWLEDGEMENTS

Thanks goes out to my employer, Clearbuilt, for letting me spend some work time on this module.

Blame goes to L<Jason Crome|https://metacpan.org/author/CROMEDOME> for encouraging me in this sort of madness.

=head1 AUTHOR

D Ruth Holloway <ruth@hiruthie.me>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022 by D Ruth Holloway.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut



( run in 2.440 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )