Catalyst-Model-DBIC-Schema

 view release on metacpan or  search on metacpan

lib/Catalyst/Model/DBIC/Schema.pm  view on Meta::CPAN


Traits are applied at L<Catalyst::Component/COMPONENT> time using
L<CatalystX::Component::Traits>.

C<ref $self> will be an anon class if any traits are applied, C<<
$self->_original_class_name >> will be the original class.

When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
L</ACCEPT_CONTEXT>.

Traits that come with the distribution:

=over 4

=item L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>

=item L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>

=item L<Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy>

=item L<Catalyst::TraitFor::Model::DBIC::Schema::PerRequestSchema>

=back

=head2 compose_namespaces

This model calls L<DBIx::Class::Schema/compose_namespace> by default to
install classes into the model namespaces. You can turn that off by
setting this attribute to false. Default is true.

=head2 install_model_shortcuts

If you don't want shortcut models so you can do e.g. C<< $c->model('DB::Book')
>> set this attribute to false, Default is true.

=head2 storage_type

Allows the use of a different C<storage_type> than what is set in your
C<schema_class> (which in turn defaults to C<::DBI> if not set in current
L<DBIx::Class>).  Completely optional, and probably unnecessary for most
people until other storage backends become available for L<DBIx::Class>.

=head1 ATTRIBUTES

The keys you pass in the model configuration are available as attributes.

Other attributes available:

=head2 connect_info

Your connect_info args normalized to hashref form (with dsn/user/password.) See
L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
L</connect_info>.

=head2 model_name

The model name L<Catalyst> uses to resolve this model, the part after
C<::Model::> or C<::M::> in your class name. E.g. if your class name is
C<MyApp::Model::DB> the L</model_name> will be C<DB>.

=head2 _default_cursor_class

What to reset your L<DBIx::Class::Storage::DBI/cursor_class> to if a custom one
doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.

=head1 ATTRIBUTES FROM L<MooseX::Traits::Pluggable>

=head2 _original_class_name

The class name of your model before any L</traits> are applied. E.g.
C<MyApp::Model::DB>.

=head2 _traits

Unresolved arrayref of traits passed in the config.

=head2 _resolved_traits

Traits you used resolved to full class names.

=head1 CONFIGURING YOUR SCHEMA AND RESULTSETS

See the documentation for
L<Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy> for instructions on how
to pass config values from your L<Catalyst> config to your
L<DBIx::Class::Schema> and/or L<DBIx::Class::ResultSet> classes.

=head1 METHODS

=head2 new

Instantiates the Model based on the above-documented ->config parameters.
The only required parameter is C<schema_class>.  C<connect_info> is
required in the case that C<schema_class> does not already have connection
information defined for it.

=head2 schema

Accessor which returns the connected schema being used by the this model.
There are direct shortcuts on the model class itself for
schema->resultset, schema->source, and schema->class.

=head2 composed_schema

Accessor which returns the composed schema, which has no connection info,
which was used in constructing the L</schema>. Useful for creating
new connections based on the same schema/model.  There are direct shortcuts
from the model object for composed_schema->clone and composed_schema->connect

If L</compose_namespaces> is not true, L</composed_schema> is equivalent to
C<< $model->schema_class->clone >>.

=head2 clone

Shortcut for ->composed_schema->clone

=head2 connect

Shortcut for ->composed_schema->connect

=head2 source

Shortcut for ->schema->source

=head2 class

Shortcut for ->schema->class

=head2 resultset

Shortcut for ->schema->resultset

=head2 txn_do

Shortcut for ->schema->txn_do

=head2 txn_scope_guard

Shortcut for ->schema->txn_scope_guard

=head2 storage

Provides an accessor for the connected schema's storage object.

See L<DBIx::Class::Storage> and L<DBIx::Class::Storage::DBI>.

=cut

has schema_class => (
    is => 'ro',
    isa => SchemaClass,
    required => 1
);

has compose_namespaces => (is => 'ro', isa => Bool, default => 1 );

has install_model_shortcuts => (is => 'ro', isa => Bool, default => 1 );

has storage_type => (is => 'rw', isa => Str);

has connect_info => (is => 'rw', isa => ConnectInfo, coerce => 1);

has model_name => (
    is => 'ro',
    isa => Str,
    required => 1,
    lazy_build => 1,
);

has _default_cursor_class => (
    is => 'ro',
    isa => LoadableClass,
    default => 'DBIx::Class::Storage::DBI::Cursor',
);

has schema => (is => 'rw', isa => Schema);

my $app_class;

before COMPONENT => sub {
    $app_class = ref $_[1] || $_[1];
};

sub app_class { $app_class }

sub BUILD {
    my ($self, $args) = @_;
    my $class = $self->_original_class_name;
    my $schema_class = $self->schema_class;

    if( !$self->connect_info ) {
        if($schema_class->storage && $schema_class->storage->connect_info) {
            $self->connect_info($schema_class->storage->connect_info);
        }
        else {
            die "Either ->config->{connect_info} must be defined for $class"
                  . " or $schema_class must have connect info defined on it."
		  . " Here's what we got:\n"
		  . Dumper($args);
        }
    }

    if (exists $self->connect_info->{cursor_class}) {
        eval { use_module($self->connect_info->{cursor_class}) }
            or croak "invalid connect_info: Cannot load your cursor_class"
        . " ".$self->connect_info->{cursor_class}.": $@";
    }

    $self->setup($args);

    my $is_installed = defined $self->composed_schema;

    if (not $is_installed) {
        $self->composed_schema($self->compose_namespaces ?
            $schema_class->compose_namespace($class)
            :
            $schema_class->clone
        );
    }

    $self->schema($self->composed_schema->clone)
        unless $self->schema;

    $self->schema->storage_type($self->storage_type)
        if $self->storage_type;

    $self->schema->connection($self->connect_info);

    if ((not $is_installed) && $self->install_model_shortcuts) {
        $self->_install_rs_models;
    }
}

sub clone { shift->composed_schema->clone(@_); }

sub connect { shift->composed_schema->connect(@_); }

# some proxy methods, see also SchemaProxy

sub resultset { shift->schema->resultset(@_); }

sub txn_do { shift->schema->txn_do(@_); }

sub txn_scope_guard { shift->schema->txn_scope_guard(@_); }

sub storage { shift->schema->storage(@_); }

=head2 setup

Called at C<BUILD> time before configuration, but after L</connect_info> is
set. To do something after configuuration use C<< after BUILD => >>.

Receives a hashref of args passed to C<BUILD>.

=cut

sub setup { 1 }

=head2 ACCEPT_CONTEXT

Point of extension for doing things at C<< $c->model >> time with context,
returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
more information.

=cut

sub ACCEPT_CONTEXT { shift }

sub _install_rs_models {
    my $self  = shift;
    my $class = $self->_original_class_name;

    no strict 'refs';

    my @sources = $self->schema->sources;

    unless (@sources) {
        warn <<'EOF' unless $ENV{CMDS_NO_SOURCES};
******************************* WARNING ***************************************
* No sources found (did you forget to define your tables?)                    *
*                                                                             *
* To turn off this warning, set the CMDS_NO_SOURCES environment variable.     *
*******************************************************************************
EOF
    }

    foreach my $moniker (@sources) {
        my $classname = "${class}::$moniker";
        *{"${classname}::ACCEPT_CONTEXT"} = sub {
            shift;
            shift->model($self->model_name)->resultset($moniker);
        }
    }
}

sub _reset_cursor_class {
    my $self = shift;

    if ($self->storage->can('cursor_class')) {
	$self->storage->cursor_class($self->_default_cursor_class)
	    if $self->storage->cursor_class ne $self->_default_cursor_class;
    }
}

{
    my %COMPOSED_CACHE;

    sub composed_schema {
	my $self = shift;
	my $class = $self->_original_class_name;
	my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};

	$$store = shift if @_;

	return $$store
    }
}

sub _build_model_name {
    my $self  = shift;
    my $class = $self->_original_class_name;
    (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;

    return $model_name;
}

__PACKAGE__->meta->make_immutable;

=head1 ENVIRONMENT

=over 4

=item CMDS_NO_SOURCES

Set this variable if you will be using schemas with no sources (Result classes)
to disable the warning. The warning is there because having no Result classes
is usually a mistake.

=back

=head1 Setting up DBIC authentication

You can set this up with 
L<Catalyst::Authentication::Store::DBIx::Class> in MyApp.pm:

  package MyApp;

  use Catalyst qw/... Authentication .../;

  ...

  __PACKAGE__->config('Plugin::Authentication' =>
                {
                    default_realm => 'members',
                    members => {
                        credential => {
                            class => 'Password',
                            password_field => 'password',
                            password_type => 'hashed'
                            password_hash_type => 'SHA-256'
                        },



( run in 0.571 second using v1.01-cache-2.11-cpan-39bf76dae61 )