ESI meets ERB, with some odd results

Hi --

I've got some ESI variables in a template, and am trying to assign
their values to Ruby variables. Strangely, it seems to work and not
work at the same time.

Here's the relevant part of the template, with some comments and
followed by explanation:

  <esi:vars>
  <% state = "$(GEO{'region_code'})" %>
  <% city = "$(GEO{'city'})" %>

  State: <%= state %><br/> # CT
  City: <%= city %><br/> # NEWHAVEN

  Massaged city: <%= CITIES[state][city] %> # Uses the literal EIS
strings

  </esi:vars>

The CITIES hash provides corrected city names when the ESI value lacks
spaces -- so, for example, CITIES["CT"]["NEWHAVEN"] is "New Haven".
The state and city variables display correctly in those middle lines
-- but when addressing the CITIES hash with those same variables,
they're evaluated as the literal ESI strings ("$(GEO..."). The
evidence for this is that I've seeded the hash:

  CITIES["$(GEO{'region_code'})"]["$(GEO{'city'})"] = "KEYING LITERAL
ESI STRINGS"

and that "KEYING..." string appears next to "Massaged city".

So, strange as it sounds, the variables seem to be differently bound
depending on which line is being evaluated. So far I've not been able
to puzzle through the sequence of events in such a way as to come up
with a way to inject the state and city values into a call to the
CITIES hash. I'd be interested in any ideas or solutions people might
have.

David

hi david!

David A. Black [2011-07-20 15:24]:

  <esi:vars>
  <% state = "$(GEO{'region_code'})" %>
  <% city = "$(GEO{'city'})" %>

  State: <%= state %><br/> # CT
  City: <%= city %><br/> # NEWHAVEN

  Massaged city: <%= CITIES[state][city] %> # Uses the literal EIS
strings

  </esi:vars>

well, as far as ruby/erb is concerned there is no ESI, right? it
simply produces:

  <esi:vars>

  State: $(GEO{'region_code'})
  City: $(GEO{'city'})

  Massaged city:
whatever-the-value-of-CITIES["$(GEO{'region_code'})"]["$(GEO{'city'})"]-is

  </esi:vars>

the hash lookup occurs before ESI gets any chance to replace the
variables. so i don't think there's any way to achieve what you want
(other than going back to the server by some means after ESI
processing has happened). i'm totally unfamiliar with ESI, though,
so i might be off.

cheers
jens

Hi –

David Black [2011-07-20 21:08]:

What's odd is that both behaviors seem to happen: the Ruby
variables being bound to the literal GEO strings, and the Ruby
variables being bound to the ESI interpolations of those strings.

no, it's not. the ruby variables are replaced with the string values
you initialized them with - in *both* places. the ESI processing
kicks in *after* that (it operates on what the application sends as
HTML response; just like SSI in principle). so there's nothing
unusual going on at all. it's just that both processors (ruby/erb
and ESI) operate in the opposite order than you seem to have assumed.

but now that you found a different solution this is all moot, of
course :wink:

Hi –

David Black [2011-07-20 21:08]:

What’s odd is that both behaviors seem to happen: the Ruby

variables being bound to the literal GEO strings, and the Ruby

variables being bound to the ESI interpolations of those strings.

no, it’s not. the ruby variables are replaced with the string values

you initialized them with - in both places. the ESI processing

kicks in after that (it operates on what the application sends as

HTML response; just like SSI in principle). so there’s nothing

unusual going on at all. it’s just that both processors (ruby/erb

and ESI) operate in the opposite order than you seem to have assumed

What I’m not seeing is why this:

CITIES[state][city]

doesn’t undergo the same evaluation/interpolation as this:

<%= state %>

Whatever happens, I would expect “state” to end up representing the same thing every time, instead of representing the string “CT” at one point and the string “$(GEO{‘region_code’})” at another point. Why doesn’t the EIS processing ensure that we get CITIES[“CT”][“NEWHAVEN”]?

but now that you found a different solution this is all moot, of

course :wink:

True :slight_smile: except I hate letting things slip away without fully understanding them.

David

Hi David,

I'll describe two scenarios and I hope I can clarify the problem.

First of all the Mapping:
BAZ = {
  'GEO_foo' => { 'GEO_bar' => 'GEO_baz' },
  "$(GEO{foo})" => { "$(GEO{bar})" => 'KEYING LITERAL ESI STRINGS' }
}

Second the default order of processing...

1. source: -------------------------------------------------------------------
<% foo = "$(GEO{'foo'})" %>
<% bar = "$(GEO{'bar'})" %>

Foo: <%= foo %>
Bar: <%= bar %>

Baz: <%= BAZ[foo][bar] %>

# ... up to this point nothing was evaluated.
2. erb'd: --------------------------------------------------------------------

Foo: $(GEO{'foo'})
Bar: $(GEO{'bar'})

Baz: KEYING LITERAL ESI STRINGS

# ... the time erb evaluates the five expressions the first two assign their
# values, which still contain the ESI instructions. In the following
# expressions these instructions are written to the document.
# As for the last expression Ruby tries to resolve the key `$(GEO{'foo'})` in
# the BAZ hash.
# At this time the BAZ hash has no member with this name resulting in nil
# unless otherwise defined.
# In the first line you see the assignment I made to produce this - kinda
# unwanted - result.

3. ESI: ----------------------------------------------------------------------

Foo: GEO_foo
Bar: GEO_bar

Baz: KEYING LITERAL ESI STRINGS

# Assuming that GEO['foo'] contains the string GEO_foo and GEO['bar'] contains
# GEO_bar the values are correctly replaced.
# But since 'KEYING LITERAL ESI STRINGS' is not in any way an esi instruction
# nothing happens here.

*snip* ***********************************************************************

So, what happens when we switch 2 and 3? First of all I think this could
either suck performance wise or improve it. I don't know 'cause I'm not so
much into socket communication. That aside:

3. ESI: ----------------------------------------------------------------------
<% foo = "GEO_foo" %>
<% bar = "GEO_bar" %>

Foo: <%= foo %>
Bar: <%= bar %>

Baz: <%= BAZ[foo][bar] %>

# ... the ESI instructions were replaced correctly as intended.

2. erb'd: --------------------------------------------------------------------

Foo: GEO_foo
Bar: GEO_foo

Baz: GEO_baz

# ... well, I think this is the result you expected. Mapping is done
# correctly, and Foo: & Bar: show the correct values.

*snip* ***********************************************************************

Switching step 2 and 3 could be a problem depending on your setup, but not
impossible. But still I'd not mix two templating engines as I still think that
it is possible to expose, if not already exposed, the GEO variables to your
Rack::Request object. They should be members of the #env property. If not you
could, if you use VCL and the GeoIP library, modify the plugin in a way it
exposes them.

best
Florian

Hi –