many-to-many interface SOLVED:

Here's how we do the many-to-many interface, made brain-dead simple thanks
to Moose, DBIC and FormHandler. All the heavy lifting is handled backstage,
we don't need to lift a finger. We thought we'd have to do lots of
mechanical drudgery, but it's all handled for me!

Here's the relationship-definition between the main record (incident) and
two of its linking tables (one for agencies, one for contractors):

package *Spill::Schema::DB::Result::Incident*;
#...
__PACKAGE__->has_many( map_incident_agency     =>
'Spill::Schema::DB::Result::IncidentAgency'     => 'incident' );
__PACKAGE__->many_to_many( *agencies*     => 'map_incident_agency',
'agency' );

__PACKAGE__->has_many( map_incident_contractor     =>
'Spill::Schema::DB::Result::IncidentContractor'     => 'incident' );
__PACKAGE__->many_to_many( *contractors*  => 'map_incident_contractor',
'contractor' );

meanwhile in the form...

package *Spill::Form::IncidentForm*;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';

has '+item_class' => ( default => 'Incident' ); # ties it to DBIC
#...
has_field '*agencies*'    => ( type => 'Multiple' );
has_field '*contractors*' => ( type => 'Multiple' );

then in the controller...

package Spill::Controller::Spill;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
#...
has '*form*' => (
    isa     => 'Spill::Form::IncidentForm',
    is      => 'rw',
    lazy    => 1,
    default => sub { Spill::Form::IncidentForm->new },
);
#...
sub my_action : Action {
  #..
  my $form = $c->*form*;
  my $obj  = $c->model('blah')->find_or_new({id=>$id});
  return unless process( # *MAGIC!*
    item   => $obj,             # take this database object
    params => $c->req->params,  # and update it if everything validates
  );
  #..
}

With those definitions in place, your [% form.render %] (or [%
form.render_field('agencies') %] if you're hand-rolling your form) will do
the right thing, no problem!

If you need to filter the items that populate the multi-select list, no
problem. There's another link between incident and, this time, person --
only we want to limit the person-list to just those related to the "org" in
the incident itself:

# back in package *Spill::Form::IncidentForm*
sub options_persons {
    my $self = shift;
    return unless $self->schema;

    my $persons = $self->schema->resultset('Person');
    my $org = $self->field('org')->init_value;
    $persons = $persons->search(
        { $org ? (org => $org) : () },
        {order_by => ['lname','fname']},
    );
    my @persons;
    while ( my $person = $persons->next ) {
        push @persons, {
            value => $person->id,
            label => $person->name,
            #active => $person->active,
        };
    }
    return \...@persons;
}

The active label seems to be ignored when we do this, unfortunately. And
really we should pull "org" from $c->user instead of the form.

But whether you roll your own list or let FormHandler/DBIC pull them all --
in your browser you get a multi-select list of all the items in each
many-to-many relationship, and users can click to turn them on or off. Click
"submit", and the linking-table gets updated accordingly. Very, very nice!

Catalyst, DBIx::Class, HTML::FormHandler, Moose -- woo hoo! Kudos, big fat
thumbs up!


-- 
Failure is not important. How you overcome it, is.
-- Nick Vujicic
_______________________________________________
List: [email protected]
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/[email protected]/
Dev site: http://dev.catalyst.perl.org/

Reply via email to