Module 01 — Foundation: Your First Map
By the end of this module you will have a working Leaflet map on a Rails page, rendered entirely from Ruby. You will not write a single line of JavaScript to configure it.
The map will be centred on Sydney, use a CartoDB Positron tile layer, and display a single marker with a popup.
Haven’t read Module 00? Now is a good time. It covers the GIS concepts that will make this module considerably easier to follow — coordinate systems, GeoJSON, tile layers, and the lat/lng gotcha. Twenty minutes now will save you an afternoon.
Create the Application
This series assumes a working knowledge of Phlex and Phlex components in Rails. If you are new to Phlex, or have not used it with Rails before, read the Phlex on Rails primer before continuing. The mapping DSL is built entirely from Phlex components — understanding how they work will make every module easier to follow.
Create a new Rails 8 application:
|
|
Add the required gems to your Gemfile:
|
|
Private repository: The
phlex-leafletrepo is currently available to tutorial subscribers only. You will need to have accepted the GitHub collaboration invite before this step will work.
Install and set up:
|
|
Planning to follow the full series? Module 07 introduces spatial queries and requires PostgreSQL with PostGIS. If you would rather set up your final stack now and avoid switching databases mid-series, see Appendix F — PostGIS Setup before continuing, then pass
--database postgresqltorails new. Everything in Modules 01–06 works identically with either database.
Install Leaflet
Pin Leaflet in your importmap:
|
|
Add the Leaflet stylesheet to your layout <head>:
<%= stylesheet_link_tag "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" %>Fix Your Layout
Rails generates a <main> tag with flex in app/views/layouts/application.html.erb.
The flex class collapses the map container to its minimum content width. Remove it:
<%# app/views/layouts/application.html.erb %>
<main class="container mx-auto mt-28 px-5">
<%= yield %>
</main>The Service
The service returns plain data. It knows nothing about maps, components, or Leaflet — just the data for this page.
|
|
Plumbing
Route
|
|
Controller
|
|
View
The view assembles the map DSL from the data the service returned and renders the component:
<%# app/views/maps/sydney.html.erb %>
<div class="max-w-4xl mx-auto py-8 px-4">
<h1 class="text-2xl font-semibold mb-4"><%= @location[:name] %></h1>
<%= render Components::Map.new(
options: Map::Options.new(
centre: @location[:centre],
zoom: @location[:zoom],
tile_layer: :cartodb_positron,
markers: [
Map::Marker.new(
lat: @location[:lat],
lng: @location[:lng],
popup: Map::Popup.new(
content: "<strong>#{@location[:name]}</strong><br>#{@location[:region]}"
)
)
]
),
height: "500px",
class: "rounded-lg shadow"
) %>
</div>Navigate to /maps/sydney. A map appears, centred on Sydney, with a marker.
Click the marker. A popup opens.
You wrote no JavaScript. You never referenced Leaflet directly.
That is the pattern. Every module that follows uses it.
try changing
:cartodb_positron, to:openstreetmapand refresh the page. Easy wasn’t it!
What’s Next
Module 02 introduces the tile layer registry — named providers, how tiles work,
attribution requirements, and switching between OpenStreetMap, CartoDB, and
Stadia from Ruby. It also unpacks the DSL you used here: what Map::Options,
Map::Marker, and Map::Popup are doing, and why they are designed the way
they are.
If the single-marker map felt too simple, good. The point of Module 01 is the pattern, not the data. Module 05 puts ten thousand school locations on the map. The pattern is the same throughout.