[Catalyst] Catalyst and Moose with "has" many_to_many SOLVED

will trillich will.trillich at serensoft.com
Thu Nov 25 22:36:15 GMT 2010

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     =3D>
'Spill::Schema::DB::Result::IncidentAgency'     =3D> 'incident' );
__PACKAGE__->many_to_many( *agencies*     =3D> 'map_incident_agency',
'agency' );

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

meanwhile in the form...

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

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

then in the controller...

package Spill::Controller::Spill;
use Moose;
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
has '*form*' =3D> (
    isa     =3D> 'Spill::Form::IncidentForm',
    is      =3D> 'rw',
    lazy    =3D> 1,
    default =3D> sub { Spill::Form::IncidentForm->new },
sub my_action : Action {
  my $form =3D $c->*form*;
  my $obj  =3D $c->model('blah')->find_or_new({id=3D>$id});
  return unless process( # *MAGIC!*
    item   =3D> $obj,             # take this database object
    params =3D> $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 =3D shift;
    return unless $self->schema;

    my $persons =3D $self->schema->resultset('Person');
    my $org =3D $self->field('org')->init_value;
    $persons =3D $persons->search(
        { $org ? (org =3D> $org) : () },
        {order_by =3D> ['lname','fname']},
    my @persons;
    while ( my $person =3D $persons->next ) {
        push @persons, {
            value =3D> $person->id,
            label =3D> $person->name,
            #active =3D> $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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20101125/857f6=

More information about the Catalyst mailing list