use Cwd qw//;
use Digest::MD5 qw//;
use Lingua::EN::Inflect::Number qw//;
+use Lingua::EN::Inflect::Phrase qw//;
use File::Temp qw//;
use Class::Unload;
use Class::Inspector ();
use File::Slurp 'slurp';
require DBIx::Class;
-our $VERSION = '0.06000';
+our $VERSION = '0.07000';
__PACKAGE__->mk_group_ro_accessors('simple', qw/
schema
generate_pod
pod_comment_mode
pod_comment_spillover_length
+ preserve_case
/);
=head1 NAME
=head2 naming
-Static schemas (ones dumped to disk) will, by default, use the new-style 0.05XXX
+Static schemas (ones dumped to disk) will, by default, use the new-style
relationship names and singularized Results, unless you're overwriting an
-existing dump made by a 0.04XXX version of L<DBIx::Class::Schema::Loader>, in
-which case the backward compatible RelBuilder will be activated, and
-singularization will be turned off.
+existing dump made by an older version of L<DBIx::Class::Schema::Loader>, in
+which case the backward compatible RelBuilder will be activated, and the
+appropriate monikerization used.
Specifying
- naming => 'v5'
+ naming => 'current'
will disable the backward-compatible RelBuilder and use
the new-style relationship names along with singularized Results, even when
The option also takes a hashref:
- naming => { relationships => 'v5', monikers => 'v4' }
+ naming => { relationships => 'v6', monikers => 'v6' }
The keys are:
=item current
-Latest default style, whatever that happens to be.
+Latest style, whatever that happens to be.
+
+=item v4
+
+Unsingularlized monikers, C<has_many> only relationships with no _id stripping.
=item v5
-Version 0.05XXX style.
+Monikers singularized as whole words, C<might_have> relationships for FKs on
+C<UNIQUE> constraints, C<_id> stripping for belongs_to relationships.
-=item v4
+Some of the C<_id> stripping edge cases in C<0.05003> have been reverted for
+the v5 RelBuilder.
+
+=item v6
+
+All monikers and relationships are inflected using
+L<Lingua::EN::Inflect::Phrase>, and there is more aggressive C<_id> stripping
+from relationship names.
+
+In general, there is very little difference between v5 and v6 schemas.
-Version 0.04XXX style.
+=item v7
+
+This mode is identical to C<v6> mode, except that monikerization of CamelCase
+table names is also done correctly.
+
+CamelCase column names in case-sensitive mode will also be handled correctly
+for relationship name inflection.
+
+Currently, only Sybase ASE, MSSQL with CS/BIN collation and Firebird without
+the L<unquoted_ddl|DBIx::Class::Schema::Loader::DBI::InterBase/unquoted_ddl>
+option are in case-sensitive mode.
+
+If you don't have any CamelCase table or column names, you can upgrade without
+breaking any of your code.
=back
will set the C<cascade_delete> option to 0 for all generated relationships,
except for C<has_many>, which will have cascade_delete as 1.
-NOTE: this option is not supported if v4 backward-compatible naming is
-set either globally (naming => 'v4') or just for relationships.
-
=head2 debug
If set to true, each constructive L<DBIx::Class> statement the loader
returns a false value, the code falls back to default behavior
for that table name.
-The default behavior is to singularize the table name, and: C<join '', map
-ucfirst, split /[\W_]+/, lc $table>, which is to say: lowercase everything,
-split up the table name into chunks anywhere a non-alpha-numeric character
-occurs, change the case of first letter of each chunk to upper case, and put
-the chunks back together. Examples:
+The default behavior is to split on case transition and non-alphanumeric
+boundaries, singularize the resulting phrase, then join the titlecased words
+together. Examples:
- Table Name | Moniker Name
- ---------------------------
- luser | Luser
- luser_group | LuserGroup
- luser-opts | LuserOpt
+ Table Name | Moniker Name
+ ---------------------------------
+ luser | Luser
+ luser_group | LuserGroup
+ luser-opts | LuserOpt
+ stations_visited | StationVisited
+ routeChange | RouteChange
=head2 inflect_plural
File in Perl format, which should return a HASH reference, from which to read
loader options.
+=head1 preserve_case
+
+Usually column names are lowercased, to make them easier to work with in
+L<DBIx::Class>. This option lets you turn this behavior off, if the driver
+supports it.
+
+Drivers for case sensitive databases like Sybase ASE or MSSQL with a
+case-sensitive collation will turn this option on unconditionally.
+
+Currently the drivers for SQLite, mysql, MSSQL and Firebird/InterBase support
+setting this option.
+
=head1 METHODS
None of these methods are intended for direct invocation by regular
=cut
-my $CURRENT_V = 'v6';
+my $CURRENT_V = 'v7';
my @CLASS_ARGS = qw(
schema_base_class result_base_class additional_base_classes
Set the 'naming' attribute or the SCHEMA_LOADER_BACKCOMPAT environment variable
to disable this warning.
+See: 'naming' in perldoc DBIx::Class::Schema::Loader::Base .
+
See perldoc DBIx::Class::Schema::Loader::Manual::UpgradingFromV4 if upgrading
from version 0.04006.
EOF
require DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_040;
return $self->{relbuilder} ||=
DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_040->new(
- $self->schema, $self->inflect_plural, $self->inflect_singular
+ $self->schema,
+ $self->inflect_plural,
+ $self->inflect_singular,
+ $self->relationship_attrs,
);
}
+ elsif ($self->naming->{relationships} eq 'v5') {
+ require DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_05;
+ return $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_05->new (
+ $self->schema,
+ $self->inflect_plural,
+ $self->inflect_singular,
+ $self->relationship_attrs,
+ );
+ }
+ elsif ($self->naming->{relationships} eq 'v6') {
+ require DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_06;
+ return $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder::Compat::v0_06->new (
+ $self->schema,
+ $self->inflect_plural,
+ $self->inflect_singular,
+ $self->relationship_attrs,
+ );
+ }
- $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder->new (
- $self->schema,
- $self->inflect_plural,
- $self->inflect_singular,
- $self->relationship_attrs,
+ return $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder->new (
+ $self->schema,
+ $self->inflect_plural,
+ $self->inflect_singular,
+ $self->relationship_attrs,
);
}
$self->_inject($table_class, @{$self->additional_base_classes});
}
+sub _resolve_col_accessor_collisions {
+ my ($self, $col_info) = @_;
+
+ my $base = $self->result_base_class || 'DBIx::Class::Core';
+ my @components = map "DBIx::Class::$_", @{ $self->components || [] };
+
+ my @methods;
+
+ for my $class ($base, @components) {
+ eval "require ${class};";
+ die $@ if $@;
+
+ push @methods, @{ Class::Inspector->methods($class) || [] };
+ }
+
+ my %methods;
+ @methods{@methods} = ();
+
+ while (my ($col, $info) = each %$col_info) {
+ my $accessor = $info->{accessor} || $col;
+
+ next if $accessor eq 'id'; # special case (very common column)
+
+ if (exists $methods{$accessor}) {
+ $info->{accessor} = undef;
+ }
+ }
+}
+
# Set up metadata (cols, pks, etc)
sub _setup_src_meta {
my ($self, $table) = @_;
my $cols = $self->_table_columns($table);
my $col_info = $self->__columns_info_for($table);
- if ($self->_is_case_sensitive) {
+ if ($self->preserve_case) {
for my $col (keys %$col_info) {
$col_info->{$col}{accessor} = lc $col
if $col ne lc($col);
}
- } else {
+ }
+ else {
+ # XXX this needs to go away
$col_info = { map { lc($_), $col_info->{$_} } keys %$col_info };
}
+ $self->_resolve_col_accessor_collisions($col_info);
+
my $fks = $self->_table_fk_info($table);
for my $fkdef (@$fks) {
if ($self->naming->{monikers} eq 'v4') {
return join '', map ucfirst, split /[\W_]+/, lc $table;
}
+ elsif ($self->naming->{monikers} eq 'v5') {
+ return join '', map ucfirst, split /[\W_]+/,
+ Lingua::EN::Inflect::Number::to_S(lc $table);
+ }
+ elsif ($self->naming->{monikers} eq 'v6') {
+ (my $as_phrase = lc $table) =~ s/_+/ /g;
+ my $inflected = Lingua::EN::Inflect::Phrase::to_S($as_phrase);
+
+ return join '', map ucfirst, split /\W+/, $inflected;
+ }
- return join '', map ucfirst, split /[\W_]+/,
- Lingua::EN::Inflect::Number::to_S(lc $table);
+ my @words = map lc, split /(?<=[[:lower:]])[\W_]*(?=[[:upper:]])|[\W_]+/, $table;
+ my $as_phrase = join ' ', @words;
+
+ my $inflected = Lingua::EN::Inflect::Phrase::to_S($as_phrase);
+
+ return join '', map ucfirst, split /\W+/, $inflected;
}
sub _table2moniker {
return $qt . $table . $qt;
}
-sub _is_case_sensitive { 0 }
-
sub _custom_column_info {
my ( $self, $table_name, $column_name, $column_info ) = @_;
return $result;
}
+sub _lc {
+ my ($self, $name) = @_;
+
+ return $self->preserve_case ? $name : lc($name);
+}
+
+sub _uc {
+ my ($self, $name) = @_;
+
+ return $self->preserve_case ? $name : uc($name);
+}
+
# remove the dump dir from @INC on destruction
sub DESTROY {
my $self = shift;