Railsで作成したWebアプリケーションに住所を登録すると、表示画面に自動で地図(Googleマップ)が表示されるようにするための覚え書きです。
この機能を実装するにあたり、住所を登録するためのaddressカラムと、地図の位置情報(緯度・経度)を保存するためのlatitude, longitudeカラムを新たに追加する必要があります。
開発環境
- Ruby 3.1.2
- Ruby on Rails 7.0.3
- Bootstrap 5.1.3
- M1 Macbook Air 2020
- mac OS Monterey (ver. 12.4)
- ターミナル bash (Rosetta 2 使用)
出来上がりのイメージ
出来上がりは以下の動作をイメージして作成していきます。
- 投稿の住所入力欄に住所、もしくは特定のスポット名称を入力
- 投稿後の記事に住所とともに地図(Googleマップ)が表示される
- 投稿の住所(もしくはスポット名称)を変更した場合、変更後の場所の地図が表示される
投稿の住所入力欄に住所、もしくは特定のスポット名称(例えば、名古屋城など)を入力すると、投稿記事内に住所とともに地図(Googleマップ)が表示されるようにします。

また、投稿内の住所を変更した場合も、地図の位置も更新されるようにします。
Google Maps の API Key を取得する
今回は地図表示にGoogle Maps JavaScript API(Google Maps Platform) を利用するので、あらかじめGoogle Maps の API Keyを取得しておく必要があります。

API Keyを取得したら次に進みます。
データベースに登録した住所から地図(Google Maps)を自動表示する手順
地図表示の大まかな流れは以下の通りです。
- 住所(address)と、緯度・経度(latitude, longitude)を保存するカラムを追加する
- 投稿から住所情報を入力すると、
- gem “geocoder”で住所から緯度・経度を取得してlatitude, longitudeに保存
- 投稿を表示する際にlatitude, longitudeから緯度・経度を取得し、
- 住所の位置(Googleマップ上)にマーカーをつける
それでは、詳しく見ていきましょう。
取得した API Key を環境変数として設定する
まずは、事前準備としてGoogle Maps Platformで取得しておいたAPI Keyを環境変数として設定しておきます。
Rails で環境変数を設定するためにはdotenv-rails
のgemを使用します。
gem "dotenv-rails"
上記をGemfileに追記したら、bundle install
します。
次に、アプリディレクトリ直下に.env
ファイルを新規作成し、以下のようにAPI Keyを記述します。
GOOGLE_MAP_API_KEY = "取得したGoogle Maps APIキー"
最後に、gitのリモートリポジトリに.env
に入力したAPIキーをpushしてしまわないように、.gitignore
ファイルに以下の記述をします。
・・・
/.env
これで環境変数の設定は終わりです。
以降、APIキーを使う際は以下の環境変数を用います。
ENV['GOOGLE_MAP_API_KEY']
住所・緯度・経度カラムを追加する
住所(address)、緯度(latitude)、経度(longitude)カラムをそれぞれテーブル(今回はPostsテーブル)に追加します。
$ rails g migration add_address_info_to_posts address:string latitude:float longitude:float
この際、緯度と経度の型はfloat
型である点に注意しましょう。
class AddAddressInfoToPosts < ActiveRecord::Migration[7.0]
def change
add_column :posts, :address, :string
add_column :posts, :latitude, :float
add_column :posts, :longitude, :float
end
end
問題なければマイグレーションを実行してテーブルにカラムを追加します。
$ rails db:migrate
住所入力フォームを作成する
住所を入力するフォームを作成します。
<%= form_with(model: @post, local: true, class: "mt-4 form-column-width") do |f| %>
・・・
<%= f.label :address, class: "form-label fw-bold"%>
<%= f.text_field :address, autofocus: true,placeholder: "住所を入力してください", class: "form-control" %>
・・・
<%= f.submit (@post.new_record? ? "投稿する" : "更新する"), class: "btn btn-primary mb-4" %>
<% end %>
gem “geocoder” を追加する
入力した住所から緯度と経度を取得するためのgem、geocoder
を追加します。
gem "geocoder"
bundle installで追加したら、モデル(ここではPost)に以下のように記述します。
class Post < ApplicationRecord
geocoded_by :address
after_validation :geocode
・・・
end
これで、Postモデルで住所登録時と変更時にgeocoderが緯度・経度のデータを登録・更新してくれます。
view に Google Maps を表示させるコードを追加する
地図を表示させたいテンプレート(view)に、Google Mapsを表示させるコードを追加します。
<!-- 住所表示エリア -->
<h5>住所</h5>
<p><%= @post.address %></p>
<!-- Googleマップ表示エリア(地図を表示) -->
<div id="map"></div>
<!-- Googleマップ表示用の Javascript -->
<script>
function initMap(){
// 地図の位置情報(緯度・経度)を取得
let mapPosition = {lat: <%= @post.latitude || 0 %> , lng: <%= @post.longitude || 0 %> };
let map = new google.maps.Map(document.getElementById('map'), {
zoom: 15,
center: mapPosition
});
let transitLayer = new google.maps.TransitLayer();
transitLayer.setMap(map);
let contentString = '【住所】<%= @post.address %>';
let infowindow = new google.maps.InfoWindow({
content: contentString
});
let marker = new google.maps.Marker({
position: mapPosition,
map: map,
title: contentString
});
marker.addListener('click', function(){
infowindow.open(map, marker);
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API_KEY'] %>&callback=initMap" async defer></script>
上記コードの最後の<script src="...
の部分で、環境変数として設定したAPI KeyENV['GOOGLE_MAP_API_KEY']
を使用します。
これで、地図の表示はできるようになりましたが、このままだと住所によっては地図を表示できない場合があります。
というのも、デフォルトのgeocoderのままでは詳しい住所の経度、緯度を取得することができないからです。
そこで、詳しい住所でも表示されるように設定していきます。
詳細な位置情報(緯度・経度)を取得できるようにする
ターミナル上で以下のコマンドを実行します。
$ rails g geocoder:config
すると、geocoder用の設定ファイルconfig/initializers/geocoder.rb
が作成されます。
設定ファイルを開いたら、以下のように設定してください。
Geocoder.configure(
# Geocoding options
# timeout: 3, # geocoding service timeout (secs)
lookup: :google, # name of geocoding service (symbol)
# ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol)
# language: :en, # ISO-639 language code
use_https: true, # use HTTPS for lookup requests? (if supported)
# http_proxy: nil, # HTTP proxy server (user:pass@host:port)
# https_proxy: nil, # HTTPS proxy server (user:pass@host:port)
api_key: ENV['GOOGLE_MAP_API_KEY'], # API key for geocoding service
# cache: nil, # cache object (must respond to #[], #[]=, and #del)
# Exceptions that should not be rescued by default
# (if you want to implement custom error handling);
# supports SocketError and Timeout::Error
# always_raise: [],
# Calculation options
# units: :mi, # :km for kilometers or :mi for miles
# distances: :linear # :spherical or :linear
# Cache configuration
# cache_options: {
# expiration: 2.days,
# prefix: 'geocoder:'
# }
)
これで、詳しい住所(緯度・経度)も取得できるようになりました。
(Railsサーバーを起動中の場合は、再起動してから読み込むと設定が適用されます)
【Rails7】Google Maps JavaScript API 複数回読み込みエラーの解消方法
Rails7 ではデフォルトでTurbo Driveが有効化されているため、
住所を登録、更新、削除する際に、以下のようにGoogle Maps JavaScript API の複数回読み込みによるエラーが発生してしまいます。

You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.
これは、住所を登録、更新、削除してページが切り替わる際、Turbo Driveによってページ全体ではなく一部分(<body>タグ内)のみがリフレッシュ(再読み込み)されるために起こります。
ページ全体では再読み込みされていない(最初の1回しか読み込まれていない)状態なのに、ページの<body>タグ内のみ(地図表示で用いるGoogle Maps JavaScript APIを含む)が再度読み込まれるため、
ページ全体から見るとGoogle Maps JavaScript APIが複数回読み込まれたように見えるためです。
このエラーは放置していても地図自体は表示されますが、予期せぬエラーの原因となりかねないのでここで解消しておくのがベターでしょう。
さて、今回のエラーを解消する方法は、ズバリTurbo Driveを無効化することです。
とは言っても、Turbo Driveを無効化するのは住所を登録、更新、削除するリクエストを送信するリンク(ボタン)のみとします。
= f.submit, class: "btn btn-primary mb-4", data: { turbo: false }
= button_to "削除", post_path(@post), class: "btn btn-danger", method: :delete, data: { turbo: false }
上記のように、data: { turbo: false }
とすることで、そのリンクや送信リクエストのみTurbo Driveを無効化することができます。
【追記】Turbo Drive無効化による、バリデーション発動時の不具合について
上記のGoogle Maps JavaScript API複数回読み込みエラーへの対処方法としてTurbo Driveの無効化を挙げましたが、Turbo Driveを無効化することで厄介な問題(不具合)が発生。
新規投稿、編集フォームにてバリデーションエラーが発動すると、newもしくはeditがレンダリングされるようにしていましたが、その際にURLが以下のように変わってしまいます。
ページ自体はレンダリングされるのですが、URLが変更されているため、ユーザーがページの再読み込みを行うとページが変わってしまうという問題が起こります(以下を参照)。

Turbo Driveが有効化されている状態では、バリデーション発動時にnew(もしくはedit)をレンダリングしてもURLは変更されないようTurbo側でうまく調整してくれているみたいですね。
今回の問題を解決する方法の1つとしては、バリデーションエラー時にnew(もしくはedit)をレンダリングするのではなく、redirect_toでnew(もしくはedit)ページを再読み込みする方法があります。
def create
・・・
if @post.save
flash[:notice] = "新規投稿が完了しました"
redirect_to @post
else
#--- redirect_to に修正する ---
redirect_to new_post_path
#----------------------------
end
end
def update
・・・
if @post.update(post_params)
flash[:notice] = "投稿を更新しました"
redirect_to @post
else
#--- redirect_to に修正する ---
redirect_to edit_post_path(@post)
#----------------------------
end
end
他にも、javascriptでバリデーションエラー時にURLを書き換える方法もあります。

参考資料


コメント