Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Template, sorting an array with only one member

by FloydATC (Deacon)
on Oct 20, 2014 at 10:16 UTC ( [id://1104400]=perlquestion: print w/replies, xml ) Need Help??

FloydATC has asked for the wisdom of the Perl Monks concerning the following question:

Given an array of hashes, I want to use Template Toolkit to loop through the array sorted by a hash key 'name':

my @array = ( { name => 'foo', value => 1 }, { name => 'bar', value => 2 }, { name => 'baz', value => 3 } );
[% FOREACH element IN array.sort('name') %] [% element.name %] [% END %]

This works ok if @array has zero or more than 1 member.

However, if @array has exactly 1 member, the virtual method "sort()" will instead return the contents of that one hash in alphabetical order. Needless to say, this causes the output to be completely wrong since "element.name" will now render as a series of empty strings because they will be undefined.

Obviously, there is no need to sort an array with only one member, but in actual use I have no way of knowing in advance how many members there will be.

Does anyone know of a known fix/workaround for this problem? How can I tell Template that "array" is in fact an array?

Update:

I found a workaround that seems to do the trick:

[% PERL %] my $copy; @{$copy} = $stash->get('array'); if (ref($copy) eq 'ARRAY') { $stash->set('copy', sort { $a->{'name'} cmp $b->{'name'} } @{$copy}) +; } [% END %] [% FOREACH element IN copy %] [% element.name %] [% END %]
-- FloydATC

Time flies when you don't know what you're doing

Replies are listed 'Best First'.
Re: Template, sorting an array with only one member
by choroba (Cardinal) on Oct 20, 2014 at 14:05 UTC
    I can't replicate the problem:
    #!/usr/bin/perl use warnings; use strict; use Template; my @arr = ( { name => 'Foo', value => 1 }, # { name => 'Bar', value => 2 }, ); 'Template'->new ->process(\'TEST: [% FOREACH element IN arr.sort("name") %] [% ele +ment.name %][% END %]', { arr => \@arr });

    Output:

    TEST: Foo
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      I've put together a test script demonstrating how my data model works. Please tell me this fails for you, or I might go insane. Bonus points if you can tell me how to fix it.

      #!/usr/bin/perl use strict; use warnings; use Template; my $container = Container->new(); $container->add( Object->new( name => 'foo', value => 10, usefulness => 0 ) ); $container->add( Object->new( name => 'bar', value => 20, meaning => 42 ) ); my $vars = { 'container' => $container }; my $template = <<EOT; container=[% container %] all: [% FOREACH element IN container.all.sort('name') %] [% element %] name is [% element.name %] [% END %] one: [% FOREACH element IN container.one(10).sort('name') %] [% element %] name is [% element.name %] [% END %] EOT my $tt = Template->new(); $tt->process(\$template, $vars); package Object; sub new { my $class = shift; my $self = {@_}; return bless($self, $class); } package Container; sub new { my $class = shift; my $self = {@_}; return bless($self, $class); } sub add { my $self = shift; my $new = shift; push @{$self->{'objects'}}, $new; } sub all { my $self = shift; return @{$self->{'objects'}}; } sub one { my $self = shift; my $filter = shift; return grep { $_->{'value'} == $filter } @{$self->{'objects'}}; }
      -- FloydATC

      Time flies when you don't know what you're doing

        Container's one method is returning a single Object object instead of an array reference with a single Object object inside.

        The fix is quite simple:

        sub one { my $self = shift; my $filter = shift; return [ grep { $_->{'value'} == $filter } @{$self->{'objects'}} ]; }

        Output:

        container=Container=HASH(0x1cc2e10)
        all:
        
        Object=HASH(0x1cdf6b0) name is foo
        
        Object=HASH(0x1cdf788) name is bar
        
        one:
        
        Object=HASH(0x1cdf6b0) name is foo
        
        

        Have one/all return arrayrefs, not lists?
        sub all { my $self = shift; return $self->{'objects'}; } sub one { my $self = shift; my $filter = shift; return [ grep { $_->{'value'} == $filter } @{$self->{'objects'}} ]; }
Re: Template, sorting an array with only one member
by LanX (Saint) on Oct 20, 2014 at 10:26 UTC
    Great DWIM bug. :)

    More a TT question, but what happens if you always append an empty hash to the array?

    Of course doing the sort logic in Perl should always fix this.

    Cheers Rolf

    (addicted to the Perl Programming Language and ☆☆☆☆ :)

      More a TT question, but what happens if you always append an empty hash to the array?

      This would solve the Template problem but it would also require that I check for empty hashes in every single place that array is used. I want to keep the workaround within the template if at all possible.

      I'll see if I can find a way to do that.

      -- FloydATC

      Time flies when you don't know what you're doing

Re: Template, sorting an array with only one member (code not words)
by Anonymous Monk on Oct 20, 2014 at 10:56 UTC
    Code talks louder than words :)
    $ cat sort-list.tt2 [% MACRO blocker BLOCK %] [% FOREACH element IN array.sort('name') %] [% element.name %] [%- END -%] [%- END -%] [%- SET array = [ { name => 'foo', value => 1 }, { name => 'bar', value => 2 }, { name => 'baz', value => 3 } ]; blocker %] [%- SET array = [ { name => 'bar', value => 2 }, ]; blocker %] $ tpage sort-list.tt2 bar baz foo bar

    My code says it works ... so what does your code say?

      I get the same result if I define the array within the template. If the array is served as a reference through Template::process() then it doesn't work as expected.

      -- FloydATC

      Time flies when you don't know what you're doing

        I get the same result if I define the array within the template. If the array is served as a reference through Template::process() then it doesn't work as expected.

        Thats just more words :) but I'm glad to see choroba got you to play ;) and you figured it out

      You must be giving it a hash instead of an array ... don't know how you managed to write that :)

      [% MACRO blocker BLOCK %] [% FOREACH element IN array.sort('name') %] [% element.name %] [%- END -%] [%- END -%] [%- SET array = [ { name => 'foo', value => 1 }, { name => 'bar', value => 2 }, { name => 'baz', value => 3 } ]; blocker; %] [%- SET array = [ { name => 'bar', value => 2 }, ]; blocker; %] [%- ## A HASH IS NOT AN ARRAY OF HASHES SET array = { name => 'bar', value => 2 }; blocker; %]

      yadayadayada

      * bar * baz * foo * bar * *

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1104400]
Approved by hippo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (6)
As of 2024-04-19 06:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found