RailsGoogleMap

Rails view helpers that drop an embedded Google Map into a page from an address or a lat/lng pair.

The point of the gem is that it works with or without a Google Maps API key. With a key it uses the official Maps Embed API and you get zoom, map type and language control. Without one it falls back to Google's keyless embed endpoint, which needs no key and no billing account — handy for a staging box, a side project, or a page that just needs to show where you are.

<%= google_map_for("Thamel, Kathmandu") %>

Three ways in, depending on how much of the markup you want to own:

| | | | --- | --- | | google_map_embed_url(query) | just the URL — build your own markup | | google_map_for(query, ...) | an <iframe> tag, optionally wrapped in a div | | render_google_map(query, ...) | a partial you can copy into your app as ERB or Slim and edit |

Installation

Add it to your Gemfile:

gem "rails_google_map"

Then:

bundle install

Or install it directly:

gem install rails_google_map

Requirements

Supported
Ruby 2.5.0 and up (verified on 2.5.1)
Rails (ActionView) 5.0 through 8.x

The suite is verified against the ends of that range: Ruby 2.5.1 with Rails 5.0 and 5.2, and Ruby 3.3 with Rails 8.1.

Installing on Ruby 2.5: use Bundler. The RubyGems that ships with Ruby 2.5 (2.7.x) ignores a dependency's required_ruby_version when resolving, so a bare gem install rails_google_map reaches for the newest ActionView and fails on a Ruby 3.2+ dependency. Bundler resolves it correctly, as does RubyGems 3.0+ (gem update --system '~> 3.0').

Configuration

Configuration is optional — skip it entirely and the keyless fallback is used.

To use the Maps Embed API, create config/initializers/rails_google_map.rb:

RailsGoogleMap.configure do |config|
  config.api_key  = ENV["GOOGLE_MAPS_EMBED_KEY"]
  config.zoom     = 15          # default zoom level
  config.map_type = "roadmap"   # "roadmap" or "satellite"
  config.width    = "100%"      # default iframe width
  config.height   = "450"       # default iframe height
  config.language = nil         # e.g. "en", "ne" — omitted when nil

  # used by render_google_map and the partial
  config.wrapper_class = "google-map"       # class on the surrounding div
  config.map_class     = nil                # class on the iframe
  config.partial       = "rails_google_map/map"
end

If you don't set api_key at all, the gem picks up GOOGLE_MAPS_EMBED_KEY or GOOGLE_MAPS_API_KEY from the environment, so an initializer is often unnecessary.

Keep the key out of source control, and restrict it to the Maps Embed API and to your own HTTP referrers in the Google Cloud console — an embed key travels to the browser inside the iframe URL and is public by nature.

Usage

In a Rails app the helpers are mixed into every view automatically.

google_map_for

<%= google_map_for("Thamel, Kathmandu") %>

<%= google_map_for(@business.address, height: "300", zoom: 17) %>

<%= google_map_for(lat: 27.7172, lng: 85.3240) %>

<%= google_map_for("Thamel", class: "office-map", title: "Our office") %>

The first argument is an address, or a "lat,lng" string. Alternatively pass lat: and lng: (long: also works). When both an address and coordinates are given the address wins — lat/lng columns tend to be unreliable, and often a whole table shares one default pin.

Recognised options:

Option Default Notes
width "100%" iframe width
height "450" iframe height
zoom 15 Embed API only; ignored without a key
map_type "roadmap" Embed API only; "roadmap" or "satellite"
language nil Embed API only
api_key configured key override the key for one call
lat / lng used when no address is given
wrapper nil true for the configured wrapper class, a CSS class, or a Hash of div attributes

Any other option is passed straight through as an iframe attribute (class, title, data, …).

By default it returns a bare <iframe>. Pass wrapper: to get the surrounding div without reaching for the partial:

<%= google_map_for("Thamel", wrapper: true) %>
<%= google_map_for("Thamel", wrapper: "map-box", class: "partners-map") %>
<%= google_map_for("Thamel", wrapper: { class: "map-box", data: { controller: "map" } }) %>

google_map_for returns nil when there is nothing to show — a blank address, or coordinates that are missing or 0,0. So a caller can guard on it:

<% if (map = google_map_for(@business.address, lat: @business.latitude, lng: @business.longitude)) %>
  <div class="google-map"><%= map %></div>
<% end %>

embed_google_map

The same thing with a plain options hash, if that reads better at the call site:

<%= embed_google_map(address: @business.address, height: 300) %>
<%= embed_google_map(lat: 27.7172, long: 85.3240) %>

render_google_map — the full markup, from a partial you can edit

google_map_for builds its tag in Ruby, so the markup is fixed. When you want to own the markup, render_google_map renders a partial instead — wrapper div and all:

= render_google_map(@business.map_query, title: t("partners.map_of", name: @business.name), map_class: "partners-map")
<%= render_google_map(@business.map_query, title: t("partners.map_of", name: @business.name), map_class: "partners-map") %>

Both produce:

<div class="google-map">
  <iframe src="https://www.google.com/maps/embed?origin=mfe&amp;pb=!1m2!2m1!1sThamel%2C+Kathmandu"
          class="partners-map" title="Map of Thamel" width="100%" height="450"
          style="border:0;" loading="lazy"
          referrerpolicy="no-referrer-when-downgrade"
          allowfullscreen frameborder="0"></iframe>
</div>

Like google_map_for, it returns nil when there is nothing to map, so the surrounding if is optional.

Options: title, wrapper_class, map_class, width, height, plus the URL options (zoom, map_type, language, api_key) and lat/lng. Anything else is handed to the partial as an extra local, which is how a partial of your own receives its data:

<%= render_google_map(@business.address, partial: "maps/card", label: "Find us") %>

Editing the markup

The gem ships the partial as ERB, which renders fine in a Slim app — Rails picks the handler per template. To change the markup, copy it into your app:

rails generate rails_google_map:views                 # app/views/rails_google_map/_map.html.erb
rails generate rails_google_map:views --format slim   # app/views/rails_google_map/_map.html.slim

App view paths win over an engine's, so the copy takes over with no further configuration. The Slim copy is the markup you probably came here for:

- if url.present?
  div class=wrapper_class
    iframe src=url class=map_class title=title width=width height=height style="border:0;" loading="lazy" referrerpolicy="no-referrer-when-downgrade" allowfullscreen=true frameborder="0"

Or point at a partial of your own and leave the gem's alone:

RailsGoogleMap.configure { |config| config.partial = "shared/google_map" }

Your partial receives url, title, wrapper_class, map_class, width and height as locals.

google_map_embed_url

Returns just the URL, for when you want to write the markup yourself:

google_map_embed_url("Thamel, Kathmandu")
# => "https://www.google.com/maps/embed?origin=mfe&pb=!1m2!2m1!1sThamel%2C+Kathmandu"

google_map_embed_url("Thamel, Kathmandu", zoom: 18)   # with an API key configured
# => "https://www.google.com/maps/embed/v1/place?key=...&q=Thamel%2C+Kathmandu&zoom=18&maptype=roadmap"

It returns nil for a blank query.

Geocoding

If you want to build a richer map with the Maps JavaScript API instead of an iframe, the gem can turn an address into coordinates. This one does require an API key, with the Geocoding API enabled:

RailsGoogleMap::Geocoder.coordinates_for("Thamel, Kathmandu")
# => { lat: 27.7154, lng: 85.3123 }

Raises RailsGoogleMap::ConfigurationError when no key is configured, and RailsGoogleMap::Geocoder::GeocodeError when the address can't be resolved or the API call fails.

Outside Rails

google_map_embed_url and google_map_for need no booted Rails app — include the helper anywhere. (render_google_map does need one, since it renders a template.)

require "rails_google_map"

class MapPresenter
  include RailsGoogleMap::MapHelper
end

MapPresenter.new.google_map_for("Thamel, Kathmandu")

Keyless mode: what you give up

The keyless endpoint takes the place in pb, not in q/maps/embed?q= answers with a blank page, and /maps?q=...&output=embed merely redirects to the pb form. It is an undocumented endpoint, so treat it as best-effort: it shows the place, but zoom, map_type and language are ignored, and Google could change it. Configure an API key for anything you depend on.

Development

After checking out the repo, run bin/setup to install dependencies. Then run rake spec to run the tests. You can also run bin/console for an interactive prompt.

To check the gem against a particular Rails release, set RAILS_VERSION:

RAILS_VERSION=5.0.0 bundle update railties && bundle exec rake spec
RAILS_VERSION=5.2   bundle update railties && bundle exec rake spec
bundle update railties && bundle exec rake spec   # newest Rails

Gemfile.lock is not checked in, precisely so the same Gemfile can resolve under Ruby 2.5 and Ruby 3.3.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, then run bundle exec rake release, which creates a git tag, pushes commits and the tag, and pushes the .gem file to rubygems.org.

The gemspec builds the file list from git ls-files, so a new file must be git added or it will not be packaged, even though it works fine locally.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sthaB-kash/rails_google_map. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the RailsGoogleMap project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.