Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

View File

@@ -0,0 +1,322 @@
package DBIx::Class::InflateColumn::DateTime;
use strict;
use warnings;
use base qw/DBIx::Class/;
use DBIx::Class::Carp;
use Try::Tiny;
use namespace::clean;
=head1 NAME
DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
=head1 SYNOPSIS
Load this component and then declare one or more
columns to be of the datetime, timestamp or date datatype.
package Event;
use base 'DBIx::Class::Core';
__PACKAGE__->load_components(qw/InflateColumn::DateTime/);
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime' }
create_date => { data_type => 'date' }
);
Then you can treat the specified column as a L<DateTime> object.
print "This event starts the month of ".
$event->starts_when->month_name();
If you want to set a specific timezone and locale for that field, use:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
);
If you want to inflate no matter what data_type your column is,
use inflate_datetime or inflate_date:
__PACKAGE__->add_columns(
starts_when => { data_type => 'varchar', inflate_datetime => 1 }
);
__PACKAGE__->add_columns(
starts_when => { data_type => 'varchar', inflate_date => 1 }
);
It's also possible to explicitly skip inflation:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', inflate_datetime => 0 }
);
NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
The column is set directly for any non-references and C<InflateColumn::DateTime>
is completely bypassed. Instead, use an input parser to create a DateTime
object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
use C<DateTime::Format::ISO8601> thusly:
use DateTime::Format::ISO8601;
my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
=head1 DESCRIPTION
This module figures out the type of DateTime::Format::* class to
inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
that you are using. If you switch from one database to a different
one your code should continue to work without modification (though note
that this feature is new as of 0.07, so it may not be perfect yet - bug
reports to the list very much welcome).
If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
a derivative of these datatypes, e.g. C<timestamp with timezone>), this
module will automatically call the appropriate parse/format method for
deflation/inflation as defined in the storage class. For instance, for
a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
would be called on deflation/inflation. If the storage class does not
provide a specialized inflator/deflator, C<[parse|format]_datetime> will
be used as a fallback. See L<DateTime/Formatters And Stringification>
for more information on date formatting.
For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
=cut
__PACKAGE__->load_components(qw/InflateColumn/);
=head2 register_column
Chains with the L<DBIx::Class::Row/register_column> method, and sets
up datetime columns appropriately. This would not normally be
directly called by end users.
In the case of an invalid date, L<DateTime> will throw an exception. To
bypass these exceptions and just have the inflation return undef, use
the C<datetime_undef_if_invalid> option in the column info:
"broken_date",
{
data_type => "datetime",
default_value => '0000-00-00',
is_nullable => 1,
datetime_undef_if_invalid => 1
}
=cut
sub register_column {
my ($self, $column, $info, @rest) = @_;
$self->next::method($column, $info, @rest);
my $requested_type;
for (qw/datetime timestamp date/) {
my $key = "inflate_${_}";
if (exists $info->{$key}) {
# this bailout is intentional
return unless $info->{$key};
$requested_type = $_;
last;
}
}
return if (!$requested_type and !$info->{data_type});
my $data_type = lc( $info->{data_type} || '' );
# _ic_dt_method will follow whatever the registration requests
# thus = instead of ||=
if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
$info->{_ic_dt_method} = 'timestamp_with_timezone';
}
elsif ($data_type eq 'timestamp without time zone') {
$info->{_ic_dt_method} = 'timestamp_without_timezone';
}
elsif ($data_type eq 'smalldatetime') {
$info->{_ic_dt_method} = 'smalldatetime';
}
elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
$info->{_ic_dt_method} = $data_type;
}
elsif ($requested_type) {
$info->{_ic_dt_method} = $requested_type;
}
else {
return;
}
if ($info->{extra}) {
for my $slot (qw/timezone locale floating_tz_ok/) {
if ( defined $info->{extra}{$slot} ) {
carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
"please put it directly into the '$column' column definition.";
$info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
}
}
}
# shallow copy to avoid unfounded(?) Devel::Cycle complaints
my $infcopy = {%$info};
$self->inflate_column(
$column =>
{
inflate => sub {
my ($value, $obj) = @_;
# propagate for error reporting
$infcopy->{__dbic_colname} = $column;
my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
return (defined $dt)
? $obj->_post_inflate_datetime( $dt, $infcopy )
: undef
;
},
deflate => sub {
my ($value, $obj) = @_;
$value = $obj->_pre_deflate_datetime( $value, $infcopy );
$obj->_deflate_from_datetime( $value, $infcopy );
},
}
);
}
sub _flate_or_fallback
{
my( $self, $value, $info, $method_fmt ) = @_;
my $parser = $self->_datetime_parser;
my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
return try {
$parser->$method($value);
}
catch {
$self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
unless $info->{datetime_undef_if_invalid};
undef; # rv
};
}
sub _inflate_to_datetime {
my( $self, $value, $info ) = @_;
return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
}
sub _deflate_from_datetime {
my( $self, $value, $info ) = @_;
return $self->_flate_or_fallback( $value, $info, 'format_%s' );
}
sub _datetime_parser {
shift->result_source->storage->datetime_parser (@_);
}
sub _post_inflate_datetime {
my( $self, $dt, $info ) = @_;
$dt->set_time_zone($info->{timezone}) if defined $info->{timezone};
$dt->set_locale($info->{locale}) if defined $info->{locale};
return $dt;
}
sub _pre_deflate_datetime {
my( $self, $dt, $info ) = @_;
if (defined $info->{timezone}) {
carp "You're using a floating timezone, please see the documentation of"
. " DBIx::Class::InflateColumn::DateTime for an explanation"
if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
and not $info->{floating_tz_ok}
and not $ENV{DBIC_FLOATING_TZ_OK};
$dt->set_time_zone($info->{timezone});
}
$dt->set_locale($info->{locale}) if defined $info->{locale};
return $dt;
}
1;
__END__
=head1 USAGE NOTES
If you have a datetime column with an associated C<timezone>, and subsequently
create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
timezone, you will get a warning (as there is a very good chance this will not have the
result you expect). For example:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
);
my $event = $schema->resultset('EventTZ')->create({
starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
});
The warning can be avoided in several ways:
=over
=item Fix your broken code
When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
set to the requested value, and B<no time conversion takes place>. It is always a good idea
to be supply explicit times to the database:
my $event = $schema->resultset('EventTZ')->create({
starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
});
=item Suppress the check on per-column basis
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
);
=item Suppress the check globally
Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
=back
Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
Instead put it directly into the columns definition like in the examples above. If you still
use the old way you'll see a warning - please fix your code then!
=head1 SEE ALSO
=over 4
=item More information about the add_columns method, and column metadata,
can be found in the documentation for L<DBIx::Class::ResultSource>.
=item Further discussion of problems inherent to the Floating timezone:
L<Floating DateTimes|DateTime/Floating DateTimes>
and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
=back
=head1 FURTHER QUESTIONS?
Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
=head1 COPYRIGHT AND LICENSE
This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
redistribute it and/or modify it under the same terms as the
L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.

View File

@@ -0,0 +1,188 @@
=for comment POD_DERIVED_INDEX_GENERATED
The following documentation is automatically generated. Please do not edit
this file, but rather the original, inline with DBIx::Class::InflateColumn::DateTime
at lib/DBIx/Class/InflateColumn/DateTime.pm
(on the system that originally ran this).
If you do edit this file, and don't want your changes to be removed, make
sure you change the first line.
=cut
=head1 NAME
DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
=head1 SYNOPSIS
Load this component and then declare one or more
columns to be of the datetime, timestamp or date datatype.
package Event;
use base 'DBIx::Class::Core';
__PACKAGE__->load_components(qw/InflateColumn::DateTime/);
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime' }
create_date => { data_type => 'date' }
);
Then you can treat the specified column as a L<DateTime> object.
print "This event starts the month of ".
$event->starts_when->month_name();
If you want to set a specific timezone and locale for that field, use:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
);
If you want to inflate no matter what data_type your column is,
use inflate_datetime or inflate_date:
__PACKAGE__->add_columns(
starts_when => { data_type => 'varchar', inflate_datetime => 1 }
);
__PACKAGE__->add_columns(
starts_when => { data_type => 'varchar', inflate_date => 1 }
);
It's also possible to explicitly skip inflation:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', inflate_datetime => 0 }
);
NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
The column is set directly for any non-references and C<InflateColumn::DateTime>
is completely bypassed. Instead, use an input parser to create a DateTime
object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
use C<DateTime::Format::ISO8601> thusly:
use DateTime::Format::ISO8601;
my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
=head1 DESCRIPTION
This module figures out the type of DateTime::Format::* class to
inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
that you are using. If you switch from one database to a different
one your code should continue to work without modification (though note
that this feature is new as of 0.07, so it may not be perfect yet - bug
reports to the list very much welcome).
If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
a derivative of these datatypes, e.g. C<timestamp with timezone>), this
module will automatically call the appropriate parse/format method for
deflation/inflation as defined in the storage class. For instance, for
a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
would be called on deflation/inflation. If the storage class does not
provide a specialized inflator/deflator, C<[parse|format]_datetime> will
be used as a fallback. See L<DateTime/Formatters And Stringification>
for more information on date formatting.
For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
=head2 register_column
Chains with the L<DBIx::Class::Row/register_column> method, and sets
up datetime columns appropriately. This would not normally be
directly called by end users.
In the case of an invalid date, L<DateTime> will throw an exception. To
bypass these exceptions and just have the inflation return undef, use
the C<datetime_undef_if_invalid> option in the column info:
"broken_date",
{
data_type => "datetime",
default_value => '0000-00-00',
is_nullable => 1,
datetime_undef_if_invalid => 1
}
=head1 USAGE NOTES
If you have a datetime column with an associated C<timezone>, and subsequently
create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
timezone, you will get a warning (as there is a very good chance this will not have the
result you expect). For example:
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
);
my $event = $schema->resultset('EventTZ')->create({
starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
});
The warning can be avoided in several ways:
=over 4
=item Fix your broken code
When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
set to the requested value, and B<no time conversion takes place>. It is always a good idea
to be supply explicit times to the database:
my $event = $schema->resultset('EventTZ')->create({
starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
});
=item Suppress the check on per-column basis
__PACKAGE__->add_columns(
starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
);
=item Suppress the check globally
Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
=back
Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
Instead put it directly into the columns definition like in the examples above. If you still
use the old way you'll see a warning - please fix your code then!
=head1 SEE ALSO
=over 4
=item More information about the add_columns method, and column metadata,
can be found in the documentation for L<DBIx::Class::ResultSource>.
=item Further discussion of problems inherent to the Floating timezone:
L<Floating DateTimes|DateTime/Floating DateTimes>
and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
=back
=head1 INHERITED METHODS
=over 4
=item L<DBIx::Class::InflateColumn>
L<get_inflated_column|DBIx::Class::InflateColumn/get_inflated_column>, L<inflate_column|DBIx::Class::InflateColumn/inflate_column>, L<set_inflated_column|DBIx::Class::InflateColumn/set_inflated_column>, L<store_inflated_column|DBIx::Class::InflateColumn/store_inflated_column>
=item L<DBIx::Class::Row>
L<copy|DBIx::Class::Row/copy>, L<delete|DBIx::Class::Row/delete>, L<discard_changes|DBIx::Class::Row/discard_changes>, L<get_column|DBIx::Class::Row/get_column>, L<get_columns|DBIx::Class::Row/get_columns>, L<get_dirty_columns|DBIx::Class::Row/get_dirty_columns>, L<get_from_storage|DBIx::Class::Row/get_from_storage>, L<get_inflated_columns|DBIx::Class::Row/get_inflated_columns>, L<has_column_loaded|DBIx::Class::Row/has_column_loaded>, L<in_storage|DBIx::Class::Row/in_storage>, L<inflate_result|DBIx::Class::Row/inflate_result>, L<insert|DBIx::Class::Row/insert>, L<insert_or_update|DBIx::Class::Row/insert_or_update>, L<is_changed|DBIx::Class::Row/is_changed>, L<is_column_changed|DBIx::Class::Row/is_column_changed>, L<make_column_dirty|DBIx::Class::Row/make_column_dirty>, L<new|DBIx::Class::Row/new>, L<result_source|DBIx::Class::Row/result_source>, L<set_column|DBIx::Class::Row/set_column>, L<set_columns|DBIx::Class::Row/set_columns>, L<set_inflated_columns|DBIx::Class::Row/set_inflated_columns>, L<store_column|DBIx::Class::Row/store_column>, L<throw_exception|DBIx::Class::Row/throw_exception>, L<update|DBIx::Class::Row/update>, L<update_or_insert|DBIx::Class::Row/update_or_insert>
=back
=head1 FURTHER QUESTIONS?
Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
=head1 COPYRIGHT AND LICENSE
This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
redistribute it and/or modify it under the same terms as the
L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.

View File

@@ -0,0 +1,230 @@
package DBIx::Class::InflateColumn::File;
use strict;
use warnings;
use base 'DBIx::Class';
use File::Path;
use File::Copy;
use Path::Class;
use DBIx::Class::Carp;
use namespace::clean;
carp 'InflateColumn::File has entered a deprecation cycle. This component '
.'has a number of architectural deficiencies that can quickly drive '
.'your filesystem and database out of sync and is not recommended '
.'for further use. It will be retained for backwards '
.'compatibility, but no new functionality patches will be accepted. '
.'Please consider using the much more mature and actively maintained '
.'DBIx::Class::InflateColumn::FS. You can set the environment variable '
.'DBIC_IC_FILE_NOWARN to a true value to disable this warning.'
unless $ENV{DBIC_IC_FILE_NOWARN};
__PACKAGE__->load_components(qw/InflateColumn/);
sub register_column {
my ($self, $column, $info, @rest) = @_;
$self->next::method($column, $info, @rest);
return unless defined($info->{is_file_column});
$self->inflate_column($column => {
inflate => sub {
my ($value, $obj) = @_;
$obj->_inflate_file_column($column, $value);
},
deflate => sub {
my ($value, $obj) = @_;
$obj->_save_file_column($column, $value);
},
});
}
sub _file_column_file {
my ($self, $column, $filename) = @_;
my $column_info = $self->result_source->column_info($column);
return unless $column_info->{is_file_column};
# DO NOT CHANGE
# This call to id() is generally incorrect - will not DTRT on
# multicolumn key. However changing this may introduce
# backwards-comp regressions, thus leaving as is
my $id = $self->id || $self->throw_exception(
'id required for filename generation'
);
$filename ||= $self->$column->{filename};
return Path::Class::file(
$column_info->{file_column_path}, $id, $filename,
);
}
sub delete {
my ( $self, @rest ) = @_;
my $colinfos = $self->result_source->columns_info;
for ( keys %$colinfos ) {
if ( $colinfos->{$_}{is_file_column} ) {
rmtree( [$self->_file_column_file($_)->dir], 0, 0 );
last; # if we've deleted one, we've deleted them all
}
}
return $self->next::method(@rest);
}
sub insert {
my $self = shift;
# cache our file columns so we can write them to the fs
# -after- we have a PK
my $colinfos = $self->result_source->columns_info;
my %file_column;
for ( keys %$colinfos ) {
if ( $colinfos->{$_}{is_file_column} ) {
$file_column{$_} = $self->$_;
$self->store_column($_ => $self->$_->{filename});
}
}
$self->next::method(@_);
# write the files to the fs
while ( my ($col, $file) = each %file_column ) {
$self->_save_file_column($col, $file);
}
return $self;
}
sub _inflate_file_column {
my ( $self, $column, $value ) = @_;
my $fs_file = $self->_file_column_file($column, $value);
return { handle => $fs_file->open('r'), filename => $value };
}
sub _save_file_column {
my ( $self, $column, $value ) = @_;
return unless ref $value;
my $fs_file = $self->_file_column_file($column, $value->{filename});
mkpath [$fs_file->dir];
# File::Copy doesn't like Path::Class (or any for that matter) objects,
# thus ->stringify (http://rt.perl.org/rt3/Public/Bug/Display.html?id=59650)
File::Copy::copy($value->{handle}, $fs_file->stringify);
$self->_file_column_callback($value, $self, $column);
return $value->{filename};
}
=head1 NAME
DBIx::Class::InflateColumn::File - DEPRECATED (superseded by DBIx::Class::InflateColumn::FS)
=head2 Deprecation Notice
This component has a number of architectural deficiencies that can quickly
drive your filesystem and database out of sync and is not recommended for
further use. It will be retained for backwards compatibility, but no new
functionality patches will be accepted. Please consider using the much more
mature and actively supported DBIx::Class::InflateColumn::FS. You can set
the environment variable DBIC_IC_FILE_NOWARN to a true value to disable
this warning.
=head1 SYNOPSIS
In your L<DBIx::Class> table class:
use base 'DBIx::Class::Core';
__PACKAGE__->load_components(qw/InflateColumn::File/);
# define your columns
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
size => 4,
},
"filename",
{
data_type => "varchar",
is_file_column => 1,
file_column_path =>'/tmp/uploaded_files',
# or for a Catalyst application
# file_column_path => MyApp->path_to('root','static','files'),
default_value => undef,
is_nullable => 1,
size => 255,
},
);
In your L<Catalyst::Controller> class:
FileColumn requires a hash that contains L<IO::File> as handle and the file's
name as name.
my $entry = $c->model('MyAppDB::Articles')->create({
subject => 'blah',
filename => {
handle => $c->req->upload('myupload')->fh,
filename => $c->req->upload('myupload')->basename
},
body => '....'
});
$c->stash->{entry}=$entry;
And Place the following in your TT template
Article Subject: [% entry.subject %]
Uploaded File:
<a href="/static/files/[% entry.id %]/[% entry.filename.filename %]">File</a>
Body: [% entry.body %]
The file will be stored on the filesystem for later retrieval. Calling delete
on your resultset will delete the file from the filesystem. Retrevial of the
record automatically inflates the column back to the set hash with the
IO::File handle and filename.
=head1 DESCRIPTION
InflateColumn::File
=head1 METHODS
=head2 _file_column_callback ($file,$ret,$target)
Method made to be overridden for callback purposes.
=cut
sub _file_column_callback {}
=head1 FURTHER QUESTIONS?
Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
=head1 COPYRIGHT AND LICENSE
This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
redistribute it and/or modify it under the same terms as the
L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
=cut
1;