0

I have this piece of code as part of a foreach loop in my controller:

my $gr = My::Model::Group->new(id => $gra->gr_id);
$gra = My::Model::Group::Admin->new(id => $gra->id);
push(@$groups, {$gr => $gra});

In @$groups array I want to store anonymous hashes where the key element is the group object and the value element is the admin of that group object. Then in the template I want to show the list of different groups that the admin can log in, for that I have this code:

[%- FOREACH  gr IN groups -%]
  <li><input type="radio" name="group" value="[% gr.id %]">[% gr.name %]</input></li>
[%- END -%]

I know that the p IN partners is not right but is to show you what I want to achieve. Any suggestions on the template code?

7
  • The FOREACH item IN list is 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 user foo has 1, 3 and bar has 2. Did I understand? Commented May 3, 2017 at 7:54
  • Yes, that's correct. You got it absolutely correct Commented May 3, 2017 at 7:55
  • Is there more than one admin? I don't understand the expected output yet. Commented May 3, 2017 at 8:03
  • The admin is going to be the same, I mean, that it's going to be the same admin for several groups. but I want to capture the groups and the admin in hash form. Later I will do something more with the admin but not for now. So now the desire output it would be the group name and id. Commented May 3, 2017 at 8:05
  • 1
    I am struggling with the TT syntax right now. Commented May 3, 2017 at 8:28

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

with that snippet of code I got it. Thanks a lot! You guys made my day
@Albert the really important part is the DEFAULT. That's what took me a while to figure out.
As you mentioned, this is best done in Perl (not TT), so I added the Perl code to do it.
Thank you @ikegami.
3

You will need to rework your code significantly to make this possible.

Keys in Perl hashes are strings, not scalars. Using anything that isn't a string as a key in a hash (e.g, $gr in the expression { $gr => $gra } will cause it to be stringified, just as if you had interpolated it into a string or printed it. Unless you have explicitly overloaded the "" operator on the My::Model::Group object, the key will end up being stored as a literal string along the lines of:

"My::Model::Group=HASH(0x1234567890)"

This string cannot be converted back to the original object -- in fact, the original object was probably garbage-collected as soon as it went out of scope, so it no longer exists at all.

Consider storing the pair as an array reference instead, e.g.

push @$groups, [$gr, $gra];

1 Comment

ok, but then how I an access to the id in $gr in the template?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.