App-Jiffy/lib/App/Jiffy/TimeEntry.pm
package App::Jiffy::TimeEntry;
use strict;
use warnings;
use Scalar::Util qw( blessed );
use MongoDB;
use MongoDB::OID;
use Moo;
my $collection = 'timeEntry';
has id => (
is => 'rw',
isa => sub {
die 'id must be a MongoDB::OID'
unless blessed $_[0] and $_[0]->isa('MongoDB::OID');
},
);
has start_time => (
is => 'rw',
isa => sub {
die 'start_time is not a DateTime object'
unless blessed $_[0] and $_[0]->isa('DateTime');
},
default => sub {
DateTime->now();
},
);
has title => (
is => 'rw',
required => 1,
);
has _duration => ( is => 'rw', );
has cfg => ( is => 'ro', );
=head2 db
C<db> get the db handler from MongoDB
=cut
sub db {
my $self = shift;
my $client = MongoDB::MongoClient->new;
return $client->get_database( ( $self->cfg ) ? $self->cfg->{db} : 'jiffy' );
}
=head2 save
C<save> will commit the current state of the C<TimeEntry> to the database.
=cut
sub save {
my $self = shift;
my $_id = $self->id;
my $document = {
start_time => $self->start_time,
title => $self->title,
};
if ($_id) {
# Update the existing record
$self->db->get_collection($collection)
->update_one( { _id => $self->id }, { '$set' => $document } );
} else {
# Insert new record
my $result = $self->db->get_collection($collection)->insert_one($document);
# Update id
$self->id( $result->inserted_id );
}
}
=head2 duration
C<duration> returns the time between this entry and the next.
=cut
sub duration {
my $self = shift;
my $set = shift;
if ($set) {
$self->_duration($set);
}
if ( $self->_duration ) {
return $self->_duration;
}
my @entry_after = App::Jiffy::TimeEntry::search(
$self->cfg,
query => {
start_time => {
'$gt' => $self->start_time,
},
},
sort => {
start_time => 1,
},
limit => 1,
);
if (@entry_after) {
return $entry_after[0]->start_time->subtract_datetime( $self->start_time );
} else {
return DateTime->now->subtract_datetime( $self->start_time );
}
}
=head2 find
C<find> will return a single document. The query will use the provided C<_id>.
=cut
sub find {
my $cfg = shift;
my $_id = shift;
my $client = MongoDB::MongoClient->new;
my $entry = $client->get_database( $cfg->{db} )->get_collection($collection)
->find_one( { _id => $_id } );
return unless $entry;
return App::Jiffy::TimeEntry->new(
id => $entry->{_id},
title => $entry->{title},
start_time => $entry->{start_time},
cfg => $cfg,
);
}
=head2 search C<$config> C<%options>...
C<search> will return an array of TimeEntry that fit the given C<%options>.
C<%options> can include the following:
=over
=item C<query>
This is the query syntax available via L<MongoDB::Collection>.
=item C<sort>
If present, the results will be sorted via the hashref given by this option.
=item C<limit>
If present, the results will limited to the number provided.
=back
=cut
sub search {
my $cfg = shift;
my %options = @_;
my $query = $options{query};
my $sort = $options{sort};
my $limit = $options{limit};
my $client = MongoDB::MongoClient->new;
my $entries = $client->get_database( $cfg->{db} )->get_collection($collection)
->find($query);
if ($sort) {
$entries = $entries->sort($sort);
}
if ($limit) {
$entries = $entries->limit($limit);
}
# Return undef if nothing was found
return unless $entries;
my $LocalTZ = DateTime::TimeZone->new( name => 'local' ); # For caching
# @TODO create a subclass of the MongoDB cursor that allows chaining of results like MongoDB
map {
$_->{start_time}->set_time_zone($LocalTZ); # Convert to local tz
App::Jiffy::TimeEntry->new(
id => $_->{_id},
title => $_->{title},
start_time => $_->{start_time},
cfg => $cfg,
)
} $entries->all;
}
=head2 last_entry
C<last_entry> will return the last TimeEntry in the db.
=cut
sub last_entry {
my $cfg = shift;
my $client = MongoDB::MongoClient->new;
my $entry = $client->get_database( $cfg->{db} )->get_collection($collection)
->find->sort( { start_time => -1 } )->limit(1)->next;
return unless $entry;
return App::Jiffy::TimeEntry->new(
id => $entry->{_id},
title => $entry->{title},
start_time => $entry->{start_time},
cfg => $cfg,
);
}
=head2 TO_JSON
C<TO_JSON> will return the entry as a JSON convertible hash.
=cut
sub TO_JSON {
my $self = shift;
{
start_time => $self->start_time->iso8601,
title => $self->title,
duration => { $self->duration->deltas },
};
}
1;