use Class::Unload;
use Class::Inspector ();
use Scalar::Util 'looks_like_number';
-use DBIx::Class::Schema::Loader::Utils qw/split_name dumper_squashed eval_package_without_redefine_warnings class_path slurp_file sigwarn_silencer/;
+use DBIx::Class::Schema::Loader::Utils qw/split_name dumper_squashed eval_package_without_redefine_warnings class_path slurp_file sigwarn_silencer firstidx uniq/;
use DBIx::Class::Schema::Loader::Optional::Dependencies ();
use Try::Tiny;
use DBIx::Class ();
use Encode qw/encode decode/;
-use List::MoreUtils qw/all any firstidx uniq/;
+use List::Util qw/all any none/;
use File::Temp 'tempfile';
use namespace::clean;
-our $VERSION = '0.07037';
+our $VERSION = '0.07042';
__PACKAGE__->mk_group_ro_accessors('simple', qw/
schema
use_moose
only_autoclean
overwrite_modifications
+ dry_run
+ generated_classes
+ omit_version
+ omit_timestamp
relationship_attrs
moniker_to_table
uniq_to_primary
quiet
+ allow_extra_m2m_cols
/);
=item force_ascii
For L</v8> mode and later, uses L<String::ToIdentifier::EN> instead of
-L<String::ToIdentifier::EM::Unicode> to force monikers and other identifiers to
+L<String::ToIdentifier::EN::Unicode> to force monikers and other identifiers to
ASCII.
=back
completed.> messages. Does not affect warnings (except for warnings related to
L</really_erase_my_files>.)
+=head2 dry_run
+
+If true, don't actually write out the generated files. This can only be
+used with static schema generation.
+
=head2 generate_pod
By default POD will be generated for columns and relationships, using database
Again, you should be using version control on your schema classes. Be
careful with this option.
+=head2 omit_version
+
+Omit the package version from the signature comment.
+
+=head2 omit_timestamp
+
+Omit the creation timestamp from the signature comment.
+
=head2 custom_column_info
Hook for adding extra attributes to the
on tables to primary keys, assuming there is only one largest unique
constraint.
+=head2 allow_extra_m2m_cols
+
+Generate C<many_to_many> relationship bridges even if the link table has
+extra columns other than the foreign keys. The primary key must still
+equal the union of the foreign keys.
+
+
=head2 filter_generated_code
An optional hook that lets you filter the generated text for various classes
filter_generated_code => sub {
my ($type, $class, $text) = @_;
- ...
- return $new_code;
+ ...
+ return $new_code;
+ }
+
+You can also use this option to set L<perltidy markers|perltidy/Skipping
+Selected Sections of Code> in your generated classes. This will leave
+the generated code in the default format, but will allow you to tidy
+your classes at any point in future, without worrying about changing the
+portions of the file which are checksummed, since C<perltidy> will just
+ignore all text between the markers.
+
+ filter_generated_code => sub {
+ return "#<<<\n$_[2]\n#>>>";
}
=head1 METHODS
$self->{class_to_table} = {};
$self->{classes} = {};
$self->{_upgrading_classes} = {};
+ $self->{generated_classes} = [];
$self->{schema_class} ||= ( ref $self->{schema} || $self->{schema} );
$self->{schema} ||= $self->{schema_class};
if $self->{dump_overwrite};
$self->{dynamic} = ! $self->{dump_directory};
+
+ croak "dry_run can only be used with static schema generation"
+ if $self->dynamic and $self->dry_run;
+
$self->{temp_directory} ||= File::Temp::tempdir( 'dbicXXXX',
TMPDIR => 1,
CLEANUP => 1,
if (ref $self->moniker_parts ne 'ARRAY') {
croak 'moniker_parts must be an arrayref';
}
- if ((firstidx { $_ eq 'name' } @{ $self->moniker_parts }) == -1) {
+ if (none { $_ eq 'name' } @{ $self->moniker_parts }) {
croak "moniker_parts option *must* contain 'name'";
}
}
# The relationship loader needs a working schema
local $self->{quiet} = 1;
local $self->{dump_directory} = $self->{temp_directory};
+ local $self->{generated_classes} = [];
+ local $self->{dry_run} = 0;
$self->_reload_classes(\@tables);
$self->_load_relationships(\@tables);
unshift @INC, $self->dump_directory;
+ return if $self->dry_run;
+
my @to_register;
my %have_source = map { $_ => $self->schema->source($_) }
$self->schema->sources;
sub _ensure_dump_subdirs {
my ($self, $class) = (@_);
+ return if $self->dry_run;
+
my @name_parts = split(/::/, $class);
pop @name_parts; # we don't care about the very last element,
# which is a filename
sub _sig_comment {
my ($self, $version, $ts) = @_;
return qq|\n\n# Created by DBIx::Class::Schema::Loader|
- . qq| v| . $version
- . q| @ | . $ts
+ . (defined($version) ? q| v| . $version : '')
+ . (defined($ts) ? q| @ | . $ts : '')
. qq|\n# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:|;
}
my $filename = $self->_get_dump_filename($class);
$self->_ensure_dump_subdirs($class);
- if (-f $filename && $self->really_erase_my_files) {
+ if (-f $filename && $self->really_erase_my_files && !$self->dry_run) {
warn "Deleting existing file '$filename' due to "
. "'really_erase_my_files' setting\n" unless $self->quiet;
unlink($filename);
if (-f $old_filename) {
$custom_content = ($self->_parse_generated_file ($old_filename))[4];
- unlink $old_filename;
+ unlink $old_filename unless $self->dry_run;
}
}
croak "filter '$filter' exited non-zero: $exit_code";
}
}
- if (not $text or not $text =~ /\bpackage\b/) {
- warn("$class skipped due to filter") if $self->debug;
- return;
- }
+ if (not $text or not $text =~ /\bpackage\b/) {
+ warn("$class skipped due to filter") if $self->debug;
+ return;
+ }
}
# Check and see if the dump is in fact different
}
}
+ push @{$self->generated_classes}, $class;
+
+ return if $self->dry_run;
+
$text .= $self->_sig_comment(
- $self->version_to_dump,
- POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
+ $self->omit_version ? undef : $self->version_to_dump,
+ $self->omit_timestamp ? undef : POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
);
open(my $fh, '>:encoding(UTF-8)', $filename)
qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\r?\n};
my ($md5, $ts, $ver, $gen);
+ local $_;
while(<$fh>) {
if(/$mark_re/) {
my $pre_md5 = $1;
$md5 = $2;
# Pull out the version and timestamp from the line above
- ($ver, $ts) = $gen =~ m/^# Created by DBIx::Class::Schema::Loader v(.*?) @ (.*?)\r?\Z/m;
+ ($ver, $ts) = $gen =~ m/^# Created by DBIx::Class::Schema::Loader( v[\d.]+)?( @ [\d-]+ [\d:]+)?\r?\Z/m;
+ $ver =~ s/^ v// if $ver;
+ $ts =~ s/^ @ // if $ts;
$gen .= $pre_md5;
croak "Checksum mismatch in '$fn', the auto-generated part of the file has been modified outside of this loader. Aborting.\nIf you want to overwrite these modifications, set the 'overwrite_modifications' loader option.\n"
return '' unless $self->generate_pod;
- return <<"EOF"
-=head1 BASE CLASS: L<$base_class>
-
-=cut
-
-EOF
+ return "\n=head1 BASE CLASS: L<$base_class>\n\n=cut\n\n";
}
sub _filter_comment {
contain multiple entries per table for the original and normalized table
names, as above in L</monikers>.
+=head2 generated_classes
+
+Returns an arrayref of classes that were actually generated (i.e. not
+skipped because there were no changes).
+
=head1 NON-ENGLISH DATABASES
If you use the loader on a database with table and column names in a language