<div>many-to-many interface SOLVED:</div><div><br></div><div>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!</div>
<div><br></div><div>Here's the relationship-definition between the main record (incident) and two of its linking tables (one for agencies, one for contractors):</div><div><br></div><div><div><font class="Apple-style-span" face="'courier new', monospace">package <b>Spill::Schema::DB::Result::Incident</b>;<br>
#...<br>__PACKAGE__->has_many( map_incident_agency => 'Spill::Schema::DB::Result::IncidentAgency' => 'incident' );<br>__PACKAGE__->many_to_many( <b>agencies</b> => 'map_incident_agency', 'agency' );<br>
<meta charset="utf-8"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace">__PACKAGE__->has_many( map_incident_contractor => 'Spill::Schema::DB::Result::IncidentContractor' => 'incident' );<br>
__PACKAGE__->many_to_many( <b>contractors</b> => 'map_incident_contractor', 'contractor' );<br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"><meta charset="utf-8"><span class="Apple-style-span" style="font-family: arial; "><div>meanwhile in the form...</div><div><br></div></span></font></div>
<div><font class="Apple-style-span" face="'courier new', monospace">package <b>Spill::Form::IncidentForm</b>;<br>use HTML::FormHandler::Moose;<br>extends 'HTML::FormHandler::Model::DBIC';</font></div><div>
<font class="Apple-style-span" face="'courier new', monospace"><div><br></div><div>has '+item_class' => ( default => 'Incident' ); # ties it to DBIC</div>#...<br>has_field '<b>agencies</b>' => ( type => 'Multiple' );<br>
has_field '<b>contractors</b>' => ( type => 'Multiple' ); <br></font><br></div><div><meta charset="utf-8"><div>then in the controller...</div></div><div><br></div><div><div><font class="Apple-style-span" face="'courier new', monospace">package Spill::Controller::Spill;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace">use Moose;</font></div></div><div><font class="Apple-style-span" face="'courier new', monospace">use namespace::autoclean;</font></div><div>
<font class="Apple-style-span" face="'courier new', monospace">BEGIN { extends 'Catalyst::Controller'; }</font></div><div><font class="Apple-style-span" face="'courier new', monospace">#...</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace">has '<b>form</b>' => (</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> isa => 'Spill::Form::IncidentForm',</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> is => 'rw',</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> lazy => 1,</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> default => sub { Spill::Form::IncidentForm->new },</font></div><div><font class="Apple-style-span" face="'courier new', monospace">);</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace">#...</font></div><div><font class="Apple-style-span" face="'courier new', monospace">sub my_action : Action {</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> #..</font></div>
<meta charset="utf-8"><div><font class="Apple-style-span" face="'courier new', monospace"> my $form = $c-><b>form</b>;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> my $obj = $c->model('blah')->find_or_new({id=>$id});</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> return unless process( # <b><u>MAGIC!</u></b></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> item => $obj, # take this database object</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> params => $c->req->params, # and update it if everything validates</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> );</font></div>
<meta charset="utf-8"><div><span class="Apple-style-span" style="font-family: 'courier new', monospace; "> #..</span></div><div><font class="Apple-style-span" face="'courier new', monospace">}</font></div>
<div><br></div><div>With those definitions in place, your <font class="Apple-style-span" face="'courier new', monospace">[% form.render %]</font> (or <font class="Apple-style-span" face="'courier new', monospace">[% form.render_field('agencies') %]</font> if you're hand-rolling your form) will do the right thing, no problem!</div>
<div><br></div><div>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:</div>
<div><br></div><div><font class="Apple-style-span" face="'courier new', monospace"># back in </font><span class="Apple-style-span" style="font-family: 'courier new', monospace; ">package <b>Spill::Form::IncidentForm</b></span></div>
<meta charset="utf-8"><div><div><div><font class="Apple-style-span" face="'courier new', monospace">sub options_persons {</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> my $self = shift;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> return unless $self->schema;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div>
<font class="Apple-style-span" face="'courier new', monospace"> my $persons = $self->schema->resultset('Person');</font></div><div><font class="Apple-style-span" face="'courier new', monospace"></font><font class="Apple-style-span" face="'courier new', monospace"> my $org = $self->field('org')->init_value;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> $persons = $persons->search(</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> { $org ? (org => $org) : () },</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> {order_by => ['lname','fname']},</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> ); </font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> my @persons;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> while ( my $person = $persons->next ) { </font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> push @persons, {</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> value => $person->id,</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> label => $person->name,</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> #active => $person->active,</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> }; </font></div><div><font class="Apple-style-span" face="'courier new', monospace"> } </font></div><div><font class="Apple-style-span" face="'courier new', monospace"> return \@persons;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace">} </font></div></div><div><br></div><div>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.</div>
<div><div><div><div class="gmail_quote"><div><br></div><div>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!</div>
</div></div><div><br></div><div>Catalyst, DBIx::Class, HTML::FormHandler, Moose -- woo hoo! Kudos, big fat thumbs up!</div><div><br></div><div><br>-- <br>Failure is not important. How you overcome it, is.<br>-- Nick Vujicic<br>
</div></div></div></div></div>