Revision 4180aaea
Von Moritz Bunkus vor fast 12 Jahren hinzugefügt
SL/DB/Helper/AttrDuration.pm | ||
---|---|---|
package SL::DB::Helper::AttrDuration;
|
||
|
||
use strict;
|
||
|
||
use parent qw(Exporter);
|
||
our @EXPORT = qw(attr_duration);
|
||
|
||
use Carp;
|
||
|
||
sub attr_duration {
|
||
my ($package, @attributes) = @_;
|
||
|
||
_make($package, $_) for @attributes;
|
||
}
|
||
|
||
sub _make {
|
||
my ($package, $attribute) = @_;
|
||
|
||
no strict 'refs';
|
||
|
||
*{ $package . '::' . $attribute . '_as_hours' } = sub {
|
||
my ($self, $value) = @_;
|
||
|
||
$self->$attribute(int($value) + ($self->$attribute - int($self->$attribute))) if @_ > 1;
|
||
return int($self->$attribute // 0);
|
||
};
|
||
|
||
*{ $package . '::' . $attribute . '_as_minutes' } = sub {
|
||
my ($self, $value) = @_;
|
||
|
||
$self->$attribute(int($self->$attribute) * 1.0 + ($value // 0) / 60.0) if @_ > 1;
|
||
return int(($self->$attribute // 0) * 60.0 + 0.5) % 60;
|
||
};
|
||
|
||
*{ $package . '::' . $attribute . '_as_duration_string' } = sub {
|
||
my ($self, $value) = @_;
|
||
|
||
$self->$attribute(defined($value) ? $::form->parse_amount(\%::myconfig, $value) * 1 : undef) if @_ > 1;
|
||
return defined($self->$attribute) ? $::form->format_amount(\%::myconfig, $self->$attribute // 0, 2) : undef;
|
||
};
|
||
|
||
*{ $package . '::' . $attribute . '_as_man_days' } = sub {
|
||
my ($self, $value) = @_;
|
||
|
||
if (@_ > 1) {
|
||
return undef if !defined $value;
|
||
$self->$attribute($value);
|
||
}
|
||
$value = $self->$attribute // 0;
|
||
return $value >= 8.0 ? $value / 8.0 : $value;
|
||
};
|
||
|
||
*{ $package . '::' . $attribute . '_as_man_days_unit' } = sub {
|
||
my ($self, $unit) = @_;
|
||
|
||
if (@_ > 1) {
|
||
return undef if !defined $unit;
|
||
croak "Unknown unit '${unit}'" if $unit !~ m/^(?:h|hour|man_day)$/;
|
||
$self->$attribute(($self->$attribute // 0) * 8.0) if $unit eq 'man_day';
|
||
}
|
||
|
||
return ($self->$attribute // 0) >= 8.0 ? 'man_day' : 'h'
|
||
};
|
||
|
||
*{ $package . '::' . $attribute . '_as_man_days_string' } = sub {
|
||
my ($self, $value) = @_;
|
||
my $method = "${attribute}_as_man_days";
|
||
|
||
if (@_ > 1) {
|
||
return undef if !defined $value;
|
||
$self->$method($::form->parse_amount(\%::myconfig, $value));
|
||
}
|
||
|
||
return $::form->format_amount(\%::myconfig, $self->$method // 0, 2);
|
||
};
|
||
}
|
||
|
||
1;
|
||
__END__
|
||
|
||
=pod
|
||
|
||
=encoding utf8
|
||
|
||
=head1 NAME
|
||
|
||
SL::DB::Helper::AttrDuration - Attribute helper for duration stored in
|
||
numeric columns
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
# In a Rose model:
|
||
use SL::DB::Helper::AttrDuration;
|
||
__PACKAGE__->attr_duration('time_estimation');
|
||
|
||
# Read access:
|
||
print "Minutes: " . $obj->time_estimation_as_minutes . " hours: " . $obj->time_estimation_as_hours . "\n";
|
||
|
||
# Use formatted strings in input fields in templates:
|
||
<form method="post">
|
||
...
|
||
[% L.input_tag('time_estimation_as_duration_string', SELF.obj.time_estimation_as_duration_string) %]
|
||
</form>
|
||
|
||
=head1 OVERVIEW
|
||
|
||
This is a helper for columns that store a duration as a numeric or
|
||
floating point number representing a number of hours. So the value
|
||
1.75 would stand for "1 hour, 45 minutes".
|
||
|
||
The helper methods created are:
|
||
|
||
=over 4
|
||
|
||
=item C<attribute_as_minutes [$new_value]>
|
||
|
||
Access only the minutes. Return values are in the range [0 - 59].
|
||
|
||
=item C<attribute_as_hours [$new_value]>
|
||
|
||
Access only the hours. Returns an integer value.
|
||
|
||
=item C<attribute_as_duration_string [$new_value]>
|
||
|
||
Access the full value as a formatted string according to the user's
|
||
locale settings.
|
||
|
||
=item C<attribute_as_man_days [$new_value]>
|
||
|
||
Access the attribute as a number of man days which are assumed to be 8
|
||
hours long. If the underlying attribute is less than 8 then the value
|
||
itself will be returned. Otherwise the value divided by 8 is returned.
|
||
|
||
If used as a setter then the underlying attribute is simply set to
|
||
C<$new_value>. Intentional use is to set the man days first and the
|
||
unit later, e.g.
|
||
|
||
$obj->attribute_as_man_days($::form->{attribute_as_man_days});
|
||
$obj->attribute_as_man_days_unit($::form->{attribute_as_man_days_unit});
|
||
|
||
Note that L<SL::DB::Object/assign_attributes> is aware of this and
|
||
handles this case correctly.
|
||
|
||
=item C<attribute_as_man_days_unit [$new_unit]>
|
||
|
||
Returns the unit that the number returned by L</attribute_as_man_days>
|
||
represents. This can be either C<h> if the underlying attribute is
|
||
less than 8 and C<man_day> otherwise.
|
||
|
||
If used as a setter then the underlying attribute is multiplied by 8
|
||
if C<$new_unit> equals C<man_day>. Otherwise the underlying attribute
|
||
is not modified. Intentional use is to set the man days first and the
|
||
unit later, e.g.
|
||
|
||
$obj->attribute_as_man_days($::form->{attribute_as_man_days});
|
||
$obj->attribute_as_man_days_unit($::form->{attribute_as_man_days_unit});
|
||
|
||
Note that L<SL::DB::Object/assign_attributes> is aware of this and
|
||
handles this case correctly.
|
||
|
||
=back
|
||
|
||
=head1 FUNCTIONS
|
||
|
||
=over 4
|
||
|
||
=item C<attr_duration @attributes>
|
||
|
||
Package method. Call with the names of attributes for which the helper
|
||
methods should be created.
|
||
|
||
=back
|
||
|
||
=head1 BUGS
|
||
|
||
Nothing here yet.
|
||
|
||
=head1 AUTHOR
|
||
|
||
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
|
||
|
||
=cut
|
SL/DB/Object.pm | ||
---|---|---|
|
||
my %types = map { $_->name => $_->type } ref($self)->meta->columns;
|
||
|
||
# Special case for *_as_man_days/*_as_man_days_unit: the _unit
|
||
# variation must always be called after the non-unit method.
|
||
my @man_days_attributes = grep { m/_as_man_days$/ } keys %attributes;
|
||
foreach my $attribute (@man_days_attributes) {
|
||
my $value = delete $attributes{$attribute};
|
||
$self->$attribute(defined($value) && ($value eq '') ? undef : $value);
|
||
}
|
||
|
||
while (my ($attribute, $value) = each %attributes) {
|
||
my $type = lc($types{$attribute} || 'text');
|
||
$value = $type eq 'boolean' ? ($value ? 't' : 'f')
|
t/db_helper/attr_duration.t | ||
---|---|---|
package AttrDurationTestDummy;
|
||
|
||
use base qw(SL::DB::Object);
|
||
|
||
__PACKAGE__->meta->setup(
|
||
table => 'dummy',
|
||
columns => [ dummy => { type => 'numeric', precision => 2, scale => 12 }, ]
|
||
);
|
||
|
||
use SL::DB::Helper::AttrDuration;
|
||
|
||
__PACKAGE__->attr_duration('dummy');
|
||
|
||
package main;
|
||
|
||
use Test::More tests => 83;
|
||
use Test::Exception;
|
||
|
||
use strict;
|
||
|
||
use lib 't';
|
||
use utf8;
|
||
|
||
use Data::Dumper;
|
||
use Support::TestSetup;
|
||
|
||
sub new_item {
|
||
return AttrDurationTestDummy->new(@_);
|
||
}
|
||
|
||
Support::TestSetup::login();
|
||
my $item;
|
||
|
||
# Wenn das Attribut undef ist:
|
||
is(new_item->dummy, undef, 'uninitialized: raw');
|
||
is(new_item->dummy_as_hours, 0, 'uninitialized: as_hours');
|
||
is(new_item->dummy_as_minutes, 0, 'uninitialized: as_minutes');
|
||
is(new_item->dummy_as_duration_string, undef, 'uninitialized: as_duration_string');
|
||
is(new_item->dummy_as_man_days, 0, 'uninitialized: as_man_days');
|
||
is(new_item->dummy_as_man_days_unit, 'h', 'uninitialized: as_man_days_unit');
|
||
is(new_item->dummy_as_man_days_string, '0,00', 'uninitialized: as_man_days_string');
|
||
|
||
# Auslesen kleiner 8 Stunden:
|
||
is(new_item(dummy => 2.75)->dummy, 2.75, 'initialized < 8: raw');
|
||
is(new_item(dummy => 2.75)->dummy_as_hours, 2, 'initialized < 8: as_hours');
|
||
is(new_item(dummy => 2.75)->dummy_as_minutes, 45, 'initialized < 8: as_minutes');
|
||
is(new_item(dummy => 2.75)->dummy_as_duration_string, '2,75', 'initialized < 8: as_duration_string');
|
||
is(new_item(dummy => 2.75)->dummy_as_man_days, 2.75, 'initialized < 8: as_man_days');
|
||
is(new_item(dummy => 2.75)->dummy_as_man_days_unit, 'h', 'initialized < 8: as_man_days_unit');
|
||
is(new_item(dummy => 2.75)->dummy_as_man_days_string, '2,75', 'initialized < 8: as_man_days_string');
|
||
|
||
# Auslesen größer 8 Stunden:
|
||
is(new_item(dummy => 12.5)->dummy, 12.5, 'initialized > 8: raw');
|
||
is(new_item(dummy => 12.5)->dummy_as_hours, 12, 'initialized > 8: as_hours');
|
||
is(new_item(dummy => 12.5)->dummy_as_minutes, 30, 'initialized > 8: as_minutes');
|
||
is(new_item(dummy => 12.5)->dummy_as_duration_string, '12,50', 'initialized > 8: as_duration_string');
|
||
is(new_item(dummy => 12.5)->dummy_as_man_days, 1.5625, 'initialized > 8: as_man_days');
|
||
is(new_item(dummy => 12.5)->dummy_as_man_days_unit, 'man_day', 'initialized > 8: as_man_days_unit');
|
||
is(new_item(dummy => 12.5)->dummy_as_man_days_string, '1,56', 'initialized > 8: as_man_days_string');
|
||
|
||
$item = new_item(dummy => 2.25); $item->dummy_as_duration_string(undef);
|
||
is($item->dummy, undef, 'write as_duration_string undef read raw');
|
||
is($item->dummy_as_minutes, 0, 'write as_duration_string undef read as_minutes');
|
||
is($item->dummy_as_hours, 0, 'write as_duration_string undef read as_hours');
|
||
is($item->dummy_as_duration_string, undef, 'write as_duration_string undef read as_duration_string');
|
||
|
||
$item = new_item(dummy => 2.25); $item->dummy_as_duration_string("4,80");
|
||
is($item->dummy, 4.8, 'write as_duration_string 4,80 read raw');
|
||
is($item->dummy_as_minutes, 48, 'write as_duration_string 4,80 read as_minutes');
|
||
is($item->dummy_as_hours, 4, 'write as_duration_string 4,80 read as_hours');
|
||
is($item->dummy_as_duration_string, "4,80", 'write as_duration_string 4,80 read as_duration_string');
|
||
|
||
$item = new_item(dummy => 2.25); $item->dummy_as_minutes(12);
|
||
is($item->dummy, 2.2, 'write as_minutes 12 read raw');
|
||
is($item->dummy_as_minutes, 12, 'write as_minutes 12 read as_minutes');
|
||
is($item->dummy_as_hours, 2, 'write as_minutes 12 read as_hours');
|
||
is($item->dummy_as_duration_string, "2,20", 'write as_minutes 12 read as_duration_string');
|
||
|
||
$item = new_item(dummy => 2.25); $item->dummy_as_hours(5);
|
||
is($item->dummy, 5.25, 'write as_hours 5 read raw');
|
||
is($item->dummy_as_minutes, 15, 'write as_hours 5 read as_minutes');
|
||
is($item->dummy_as_hours, 5, 'write as_hours 5 read as_hours');
|
||
is($item->dummy_as_duration_string, "5,25", 'write as_hours 5 read as_duration_string');
|
||
|
||
$item = new_item(dummy => undef);
|
||
is($item->dummy, undef, 'write raw undef read raw');
|
||
is($item->dummy_as_man_days, 0, 'write raw undef read as_man_days');
|
||
is($item->dummy_as_man_days_unit, 'h', 'write raw undef read as_man_days_unit');
|
||
is($item->dummy_as_man_days_string, '0,00', 'write raw undef read as_man_days_string');
|
||
|
||
$item = new_item(dummy => 4);
|
||
is($item->dummy, 4, 'write raw 4 read raw');
|
||
is($item->dummy_as_man_days, 4, 'write raw 4 read as_man_days');
|
||
is($item->dummy_as_man_days_unit, 'h', 'write raw 4 read as_man_days_unit');
|
||
is($item->dummy_as_man_days_string, '4,00', 'write raw 4 read as_man_days_string');
|
||
|
||
$item = new_item(dummy => 18);
|
||
is($item->dummy, 18, 'write raw 18 read raw');
|
||
is($item->dummy_as_man_days, 2.25, 'write raw 18 read as_man_days');
|
||
is($item->dummy_as_man_days_unit, 'man_day', 'write raw 18 read as_man_days_unit');
|
||
is($item->dummy_as_man_days_string, '2,25', 'write raw 18 read as_man_days_string');
|
||
|
||
$item = new_item(dummy => 4);
|
||
is($item->dummy, 4, 'should not change anything when writing undef: write raw 4 read raw');
|
||
is($item->dummy_as_man_days(undef), undef, 'should not change anything when writing undef: write as_man_days undef return undef');
|
||
is($item->dummy, 4, 'should not change anything when writing undef: read raw 2');
|
||
is($item->dummy_as_man_days_unit(undef), undef, 'should not change anything when writing undef: write as_man_days_unit undef return undef');
|
||
is($item->dummy, 4, 'should not change anything when writing undef: read raw 3');
|
||
is($item->dummy_as_man_days_string(undef), undef, 'should not change anything when writing undef: write as_man_days_string undef return undef');
|
||
is($item->dummy, 4, 'should not change anything when writing undef: read raw 4');
|
||
|
||
|
||
$item = new_item;
|
||
is($item->dummy(2), 2, 'parse less than a man day: write raw 2 read raw');
|
||
is($item->dummy_as_man_days(0.75), 0.75, 'parse less than a man day: write as_man_days 0.75 read as_man_days');
|
||
is($item->dummy_as_man_days_string('0,5'), '0,50', 'parse less than a man day: write as_man_days_string 0,5 read read as_man_days_string');
|
||
|
||
$item = new_item;
|
||
is($item->dummy(12), 12, 'parse more than a man day: write raw 12 read raw');
|
||
is($item->dummy_as_man_days(13.25), 1.65625, 'parse more than a man day: write as_man_days 13.25 read as_man_days');
|
||
is($item->dummy_as_man_days_string('13,5'), '1,69', 'parse more than a man day: write as_man_days_string 13,5 read read as_man_days_string');
|
||
|
||
$item = new_item;
|
||
is($item->dummy(3.25), 3.25, 'parse less than a man day with unit h: write raw 3.25 read raw');
|
||
is($item->dummy_as_man_days_unit('h'), 'h', 'parse less than a man day with unit h: write as_man_days_unit h read as_man_days_unit');
|
||
is($item->dummy, 3.25, 'parse less than a man day with unit h: read raw');
|
||
|
||
$item = new_item;
|
||
is($item->dummy(3.25), 3.25, 'parse less than a man day with unit hour: write raw 3.25 read raw');
|
||
is($item->dummy_as_man_days_unit('hour'), 'h', 'parse less than a man day with unit hour: write as_man_days_unit hour read as_man_days_unit');
|
||
is($item->dummy, 3.25, 'parse less than a man day with unit hour: read raw');
|
||
|
||
$item = new_item;
|
||
is($item->dummy(3.25), 3.25, 'parse more than a man day with unit man_day: write raw 3.25 read raw');
|
||
is($item->dummy_as_man_days_unit('man_day'), 'man_day', 'parse more than a man day with unit man_day: write as_man_days_unit man_day read as_man_days_unit');
|
||
is($item->dummy, 26, 'parse more than a man day with unit man_day: read raw');
|
||
|
||
is(new_item->assign_attributes(dummy_as_man_days => 3, dummy_as_man_days_unit => 'h')->dummy, 3, 'assign_attributes hash 3h');
|
||
is(new_item->assign_attributes(dummy_as_man_days_unit => 'h', dummy_as_man_days => 3 )->dummy, 3, 'assign_attributes hash h3');
|
||
|
||
is(new_item->assign_attributes(dummy_as_man_days => 3, dummy_as_man_days_unit => 'man_day')->dummy, 24, 'assign_attributes hash 3man_day');
|
||
is(new_item->assign_attributes(dummy_as_man_days_unit => 'man_day', dummy_as_man_days => 3 )->dummy, 24, 'assign_attributes hash man_day3');
|
||
|
||
is(new_item->assign_attributes('dummy_as_man_days', 3, 'dummy_as_man_days_unit', 'h')->dummy, 3, 'assign_attributes array 3h');
|
||
is(new_item->assign_attributes('dummy_as_man_days_unit', 'h', 'dummy_as_man_days', 3 )->dummy, 3, 'assign_attributes array h3');
|
||
|
||
is(new_item->assign_attributes('dummy_as_man_days', 3, 'dummy_as_man_days_unit', 'man_day')->dummy, 24, 'assign_attributes array 3man_day');
|
||
is(new_item->assign_attributes('dummy_as_man_days_unit', 'man_day', 'dummy_as_man_days', 3 )->dummy, 24, 'assign_attributes array man_day3');
|
||
|
||
# Parametervalidierung
|
||
throws_ok { new_item()->dummy_as_man_days_unit('invalid') } qr/unknown.*unit/i, 'unknown unit';
|
||
lives_ok { new_item()->dummy_as_man_days_unit('h') } 'known unit h';
|
||
lives_ok { new_item()->dummy_as_man_days_unit('hour') } 'known unit hour';
|
||
lives_ok { new_item()->dummy_as_man_days_unit('man_day') } 'known unit man_day';
|
||
|
||
done_testing();
|
Auch abrufbar als: Unified diff
AttrDuration-Helfer