duskwuff already explains in their answer that you can't use objects as hash keys as they get serialized and you'll lose the object-ness. My answer builds on that.
Let's say you have an array of arrays instead, where each inner array holds a pair of objects. I've created Moo classes to illustrate.
package My::Model::Group;
use Moo;
has [qw/id name/] => ( is => 'ro' );
package My::Model::Group::Admin;
use Moo;
has [qw/id name/] => ( is => 'ro' );
package main;
my $groups = [
[
My::Model::Group->new( id => 1, name => 'group1' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
[
My::Model::Group->new( id => 2, name => 'group2' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
[
My::Model::Group->new( id => 3, name => 'group3' ) =>
My::Model::Group::Admin->new( id => 1, name => 'bar' )
],
[
My::Model::Group->new( id => 4, name => 'group4' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
];
There are four pairs. Two admins, four groups. Three of the groups belong to the foo admin, and one to bar. Now let's look at the template.
use Template;
my $tt = Template->new();
$tt->process( \*DATA, { groups => $groups }, \my $output )
or die $tt->error;
print $output;
__DATA__
[%- FOREACH item IN groups -%]
[%- DEFAULT by_admin.${item.1.name} = [] -%]
[%- by_admin.${item.1.name}.push(item.0) -%]
[%- END -%]
[%- FOREACH admin IN by_admin.keys.sort -%]
[%- FOREACH group IN by_admin.$admin -%]
[%- admin %] -> [% group.id %]
[%- END -%]
[%- END -%]
The relevant part obviously is the DATA section. We need to reorganize the array data structure into a hash that has the admins, and then each group sorted into one of the admin slots.
We don't need to create the by_admin variable. It will be created globally implicitly. But we do need to set a default value for $by_admin->{$item[0]->name} (I'm using Perl syntax now, to make it easier to understand). It seems like Template Toolkit does not know autovivification, and the DEFAULT keyword is similar to the //= assignment operator in Perl.
We can then push the first element of item into the array ref we just created (if it didn't exist yet) inside the hash ref element with the key item.1.name inside by_name.
Once we're done preparing, the rest is just a simple loop. We iterate the sorted keys of by_admin, and then iterate the array ref that's behind that key.
Here's the output:
bar -> 3
foo -> 1
foo -> 2
foo -> 4
It would make sense to do the preprocessing not in a template, but in your controller instead. As normal Perl code it should be easier to read.
my %by_admin;
for my $group (@$groups) {
push @{ $by_admin{ $group->[1]{name} } }, $group->[0];
}
Note that I have omitted use strict and use warnings for brevity.
FOREACH item IN listis correct. But you want to regroup them, right? This is like a pivot in Excel. You have something like{ 1 => 'foo', 2 => 'bar', 3 => 'foo' }and you want to show that userfoohas1, 3andbarhas2. Did I understand?