The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package DBIx::DataModel::Meta::Path;
use strict;
use warnings;
use parent "DBIx::DataModel::Meta";
use DBIx::DataModel;
use DBIx::DataModel::Meta::Utils;

use Scalar::Util         qw/looks_like_number weaken/;
use Params::Validate     qw/validate SCALAR HASHREF ARRAYREF OBJECT/;
use Carp;
use namespace::clean;

{no strict 'refs'; *CARP_NOT = \@DBIx::DataModel::CARP_NOT;}

my $path_spec = {
  name         => {type => SCALAR},
  from         => {isa  => 'DBIx::DataModel::Meta::Source::Table'},
  to           => {isa  => 'DBIx::DataModel::Meta::Source::Table'},
  on           => {type => HASHREF}, # join condition
  multiplicity => {type => ARRAYREF},
  association  => {type => OBJECT,
                   isa  => "DBIx::DataModel::Meta::Association"},
  direction    => {type => SCALAR, regex => qr/^(AB|BA)$/},
};

sub new {
  my $class = shift;

  # parse arguments and create $self
  my $self = validate(@_, $path_spec);

  my $path = $self->{name};
  weaken $self->{$_} for qw/from to association/;

  # add this path into the 'from' metaclass
  not $self->{from}{path}{$path}
    or croak "$self->{from}{class} already has a path '$path'";
  $self->{from}{path}{$path} = $self;
  push @{$self->{from}{components}}, $path
    if $self->{association}{kind} eq 'Composition';

  # install a navigation method into the 'from' table class
  my @navigation_args = ($self->{name},  # method name
                         $self->{name}); # path to follow
  push @navigation_args, {-result_as => "firstrow"}
    if $self->{multiplicity}[1] == 1;
  $self->{from}->define_navigation_method(@navigation_args);

  bless $self, $class;
}

DBIx::DataModel::Meta::Utils->define_readonly_accessors(
  __PACKAGE__, keys %$path_spec
);


sub opposite {
  my $self = shift;
  my $opposite_direction = reverse $self->direction;
  my $opposite_path      = "path_".$opposite_direction;
  return $self->association->$opposite_path;
}


1;


__END__

=head1 NAME

DBIx::DataModel::Meta::Path - meta-information about a path

=head1 SYNOPSIS

  # create the path; best called through $assoc->_install_path(...)
  my $path = new (
    name         => $role_name,
    from         => $source_meta_table,
    to           => $destination_meta_table,
    on           => \%condition,         # in SQL::Abstract::More format
    multiplicity => [$min, $max],
    association  => $association,
    direction    => $direction,          # either 'AB' or 'BA'
  );

=head1 DESCRIPTION

This class is closely related to L<DBIx::DataModel::Meta::Association>.
A I<path> corresponds to one possible database join between
two tables.

=head1 PUBLIC METHODS

=head2 new

Constructor method. Normally this will be called indirectly
through 

  $association->_install_path(%args)

because the L<DBIx::DataModel::Meta::Association/_install_path>
method automatically adds its own invocant (the C<$association>)
into C<%args>.

Named arguments to C<new()> are :

=over

=item name

The name of this path (must be unique within the source table).
That name is used for defining
a Perl method in the class associated to the source table,
and for interpreting multi-steps joins in calls like

  $schema->join(qw/FirstTable role1 role2 .../)

=item from

The L<DBIx::DataModel::Meta::Source::Table> instance
which is the source for this path.

=item to

The L<DBIx::DataModel::Meta::Source::Table> instance
which is the target for this path.

=item on

A hashref that describes the database join condition : the keys
are names of columns for the left-hand side, and values
are names of columns for the right-hand side.
For example 

  on => {foreign_1 => 'primary_1', foreign_2 => 'primary_2'}

will generate SQL clauses of shape

  .. JOIN ON <left>.foreign_1 = <right>.primary_1
         AND <left>.foreign_2 = <right>.primary_2

=item multiplicity

An arrayref C<< [$min, $max] >>; 
see explanations in L<DBIx::DataModel::Meta::Association>.

=item association

The association to which this path belongs.

=item direction

A string that describes the direction of this path within
the association : must be either C<'AB'> or C<'BA'>.

=back

=head2 name

Name of this path

=head2 from

Source of this path 
(an instance of L<DBIx::DataModel::Meta::Source::Table>).

=head2 to

Target of this path 
(an instance of L<DBIx::DataModel::Meta::Source::Table>).

=head2 on

Copy of the hash for the join condition

=head2 multiplicity

Array C<($min, $max)> describing the multiplicity.

=head2 association

Instance of L<DBIx::DataModel::Meta::Association> to which this
path belongs.

=head2 direction

Direction of the path within the association; 
a string containing either C<'AB'> or C<'BA'>.

=head2 opposite

Returns the path object representing the opposite direction.


=cut