The variable is ours.

作者:   發佈於:  

As I was writing the controller class of Railsish , I was facing a rather interesting problem: what's a good way to pass variables into the template ?

If you know Rails, the one that I'm trying to cloning, or clone-ish-ing, the view code or sort of like an instance method of its corresponding class. All the instance variables that gets initialized in the controller are automatically viewable in the view code. This is super neat. Because then you don't really have to aggregate those into a hash, and them pass it to render.

Since there is no such obvious thing in Perl that's known as ``instance variables'', I have to make something up. At least, something that's sweet enough, that I don't have to pass any variables to the `render` function.

So the goal is something like:

package FooController;

# My action code
sub index {
  my $name = "gugod";

  ## @*#$&{}.... Magic!

  render; # Then "$name" is somehow available in the template code.

Then the solution is somehow easy: PadWalker.

If the render function can automatically grab variables from its caller, including their names and values, then they can be passed into my template system (in my case, it's TT2) without worrying name changes.

But in a way, I don't want render to grab all variables from its caller scope, because then it'll be too much trash like $bar and $i. I want to make it very neat, and yet still controllable.

Easy, just grab our variable instead of my variables.

Heres' the code that I wrote for my render function in the Railsish:

sub build_stash {
    my $caller_vars = Binding->of_caller(2)->our_vars;
    my $stash = {};
    for my $varname (keys %$caller_vars) {
        my $val = $caller_vars->{$varname};
        $varname =~ s/^[\$%@]//;
        $val = $$val if ref($val) eq any('SCALAR', 'REF');
	$stash->{$varname} = $val;
    return $stash;

I used my Binding module instead using PadWalker directly, to fetch our variables above, and re-arrange them by their sigils into proper forms that's ready for TT2 system.

So then in my controller code I wrote:

sub index {
    our $title = "Welcome";

And in the view it's:

[% title %]

It's probably better to invent other variable decelerator instead of just using "our", since it also clashes with package variables a bit, but this is a rather easy solution so far.