Lesson 5 — Composing the map
Lesson 4 ended with a working regional map: three sources,
four layers, two popup templates, one image registration,
all crammed into a single view_template block. It works,
but view_template has stopped reading at human scale. A
new contributor opening the file has to scroll past
everything to find what’s where, and adding a fifth layer
will make it worse.
This lesson refactors that. No new GIS, no new endpoints, no behaviour change. Same map, cleaner organisation.
The pattern is the same one Phlex teaches for HTML: when a
view’s view_template grows, break it into private methods
named for what they produce. We’ll do exactly that for the
map’s layers.
What’s wrong with the current shape
Look at the end of Lesson 4’s RegionalMap#view_template:
|
|
Maybe 50 lines of declarations. The structure is real but hidden — there’s a jobs feature (source + layer + popup) in there, and an FO feature (source + image + layer + popup), and the SAs feature (source + two layers), but all three have been spread across the block, separated by declarations that belong to other features.
A few things suffer:
- Adding a feature means inserting code in three places.
A new “depots” layer means a new
m.sourcenear the top, maybe a newm.image, and a newm.layerfurther down. Easy to miss one; easy to put them in the wrong spot. - Removing a feature means reverse archaeology. Tracking down all the lines that belong to “jobs” requires reading the whole block.
- The high-level structure of the map is invisible. A reader has to mentally group declarations to understand “this map has three feature types”. The structure is in the file, but only implicitly.
The fix is cohesion: lines that change together live together.
The pattern
Each feature on the map gets its own private method. The
method takes the map builder m as a parameter and emits
that feature’s source, image (if any), layer(s), and popup.
view_template becomes a high-level table of contents that
calls each method in order:
|
|
Read view_template and you can see at a glance: this map
has service areas, jobs, and field officers. The detail of
how each is configured is one method jump away. For a
map with eight layers across five sources, that’s a real
readability win.
Applying it
Replace the whole view_template and the layers it calls
with the refactored version. Constants and popup templates
stay where they were — they’re configuration data, named
once, referenced from the methods that use them.
|
|
That’s it. Refresh the dashboard — the map renders identically. The behaviour is unchanged; only the organisation has shifted.
What the pattern earns
A few things, all of them about cognitive load.
view_template becomes the index. Three lines, three
features. A reader who needs to know “what’s on this map?”
gets the answer at a glance. Compare with the original
50-line view template, where the answer required reading
and grouping declarations mentally.
Each method owns its feature’s full configuration. The jobs source and the jobs layer live in the same place. The FO source, the FO image registration, and the FO layer live in the same place. Adding a new layer to an existing feature means editing one method; removing a feature means deleting one method. Cohesion follows feature.
Layer order is explicit at the top. Vera adds layers
to the map in declaration order; later layers render on
top. With the refactor, that order is set by the order of
calls in view_template — three lines, easy to read,
trivial to reorder. In the original block, layer order
was implicit in the declaration sequence inside a 50-line
method.
Helpers compose further. If add_field_officers grew
to include a hover-grow effect or a clustering layer, it
could split into add_field_officer_pins and
add_field_officer_clusters. The pattern scales naturally
as features grow.
Why pass m rather than store it
The m we yield from Vera::Map.new’s block is the live
Vera::Map instance — calls to m.source, m.layer, and
the rest mutate its accumulators. We could store it as
@m for the duration of view_template and have
add_jobs reach for @m, but that introduces hidden state
the methods depend on.
Passing m explicitly makes the dependency obvious: this
method needs m to do its work; if you call it outside the
map’s block, it errors immediately. The cost is a tiny bit
of typing; the win is methods that only operate on what
they’re given.
Naming convention
add_* reads imperatively, matches what the methods do
(mutate the map). It’s the convention I’d land on for the
tutorial.
A few alternatives that didn’t quite fit:
compose_*— works but is less common in Rails. Ruby reaches foradd_*when something accumulates.*_layer— doesn’t fit because some methods do source + image + layer together. Naming them after one of those pieces understates what they do.feature_*— too generic; “feature” already has a GeoJSON meaning we don’t want to overload.
add_* per feature on the map is what the rest of this
tutorial uses.
When this pattern shines
For a map with three or four feature types like the dispatcher’s regional view, the organisation is nice but not essential. The original 50-line block was readable, just busy.
For maps with eight or twelve feature types — a future Module 6 dispatcher view with choropleth + clustering + drawing zones + customer pins + FO pins + traffic overlay
- heat tiles — this organisation becomes load-bearing. Without it, the view template grows past readable size and contributors start making mistakes.
For maps with one or two feature types, the pattern is overkill — leave them as a flat block.
The right time to refactor is when adding the next layer
makes you think twice about where to put it. That’s the
signal view_template has hit its ceiling.
Where this leaves us
RegionalMap is now organised at human scale. Three feature
methods, each responsible for one map source’s worth of
declarations. Adding a fourth feature is a four-step,
fully-localised change: write a new service object, add the
controller action, write add_<feature>(m), call it in
view_template. No editing in the middle of an existing
block; no risk of a declaration ending up in the wrong
neighbourhood.
The map is solid. The next lesson wraps it in the rest of the dispatcher’s dashboard — the stats panel above and the sidebars to the right.