简体   繁体   中英

Access an array item that is an anonymous hash in Template Toolkit in Perl

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?

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 (eg, $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, eg

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

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 sort ed 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM