この記事の目的
自分の投稿したワード(住所など)をフォームに入れて、google mapで検索できるようにする。(今回は例としてカフェの名前を検索できるようにする)
環境
ruby 2.5.3
rails 5.2.1
(筆者はbundlerを使ってrailsはローカルでインストールして使用が、今回はグローバルでインストールした時のコマンドの打ち方で書く)
googleAPIを取得
https://console.developers.google.com/?hl=ja
このホームページに登録してAPIを取得する(調べれば出るので今回はカット)
アプリ作成
rails new cafe_map
モデルは以下のようにする。
(id,created_at,update_atカラムは省略)
・topicモデル(contentカラムはなくても問題ない)
カラム名 | 型 |
---|---|
cafe_name | string |
content | string |
・mapモデル
カラム名 | 型 |
---|---|
topic_id | integer |
latitude | float |
longitude | float |
address | string |
latitudeは経度。longitudeは緯度という意味。
モデルの作り方はそれぞれ
rails g model topic cafe_mame:string content:string
rails g scaffold map address:string latitude:float longitude:float
mapモデルはscaffoldを使って作成する。
modelのアソシエーション
has_many :maps
belongs_to :topic
geocoderをインストール
Gemfileに以下を追加。
gem 'geocoder'
geocoderは住所から緯度と経度を割り出して取得してくれる。
bundle installを忘れずに。
geocoderを使えるようにするため以下を追加。
geocoded_by :address
after_validation :geocode, if: :address_changed?
これで住所(address)からlatitudeとlongitudeを作成できる。
ルート、コントローラー、ビュー
流れとして、
topic/newでtopicを作成 => topic/:idでそのtopicを表示 => topic/topic_id/maps/newでtopicに紐づいたmapを作成 => /topics/:topic_id/maps/:idでmapを表示
という風に作る。
・ルート
resources :topics do
resources :maps
end
ネストして設定。これでtopicのidは、mapのtopic_idに紐付く。(簡単に言えば、topic.id == map.topic_id)
・コントローラー
def index
@topics = Topic.all
end
def new
@topic = Topic.new
end
def show
@topic = Topic.find(params[:id])
end
def create
@topic = current_user.topics.new(topic_params)
if @topic.save
redirect_to root_path
else
render 'topic/new'
end
end
private
def topic_params
params.require(:topic).permit(:cafe_name,:content)
end
topicは投稿、一覧の基本的な部分のみ作成。
次はmap。
before_action :set_map, only: [:show, :edit, :update, :destroy]
before_action :set_topic, only: [:index, :new, :edit, :create, :destroy]
# GET /topics/:topic_id/maps
# GET /maps.json
def index
@maps = @topic.maps.all
end
# GET /topics/:topic_id/maps/:id
# GET /maps/1.json
def show
end
# GET /topics/:topic_id/maps/new
def new
@map = Map.new
end
# GET /topics/:topic_id/maps/:id/edit
def edit
end
# POST /topics/:topic_id/maps
# POST /maps.json
def create
@map = @topic.maps.new(map_params)
respond_to do |format|
if @map.save
format.html { redirect_to topic_map_path(topic_id: @map.topic_id,id: @map.id), notice: 'Map was successfully created.' } #:topic_idと:idを2つの引数でそれぞれ指定
format.json { render :show, status: :created, location: topic_map_path(topic_id: @map.topic_id,id: @map.id) }
else
format.html { render :new }
format.json { render json: @map.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /topics/:topic_id/maps/:id
# PATCH/PUT /maps/1.json
def update
respond_to do |format|
if @map.update(map_params)
format.html { redirect_to topic_map_path(topic_id: @map.topic_id,id: @map.id), notice: 'Map was successfully updated.' }
format.json { render :show, status: :ok, location: topic_map_path(topic_id: @map.topic_id,id: @map.id) }
else
format.html { render :edit }
format.json { render json: @map.errors, status: :unprocessable_entity }
end
end
end
# DELETE /topics/:topic_id/maps/:id
# DELETE /maps/1.json
def destroy
@map.destroy
respond_to do |format|
format.html { redirect_to topic_maps_path(topic_id: @topic.id), notice: 'Map was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_map
@map = Map.find(params[:id])
end
def set_topic
@topic = Topic.find_by(id: params[:topic_id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def map_params
params.require(:map).permit(:address,:latitude,:longitude,:topic_id)
end
topicに紐づいたmapを作成、表示、編集、削除などできるように設定。
最後にビュー。
scaffoldでほとんど生成されているが多少変更する。
<script type="text/javascript">
function initMap() {
var test = {lat: <%= @map.latitude %>, lng: <%= @map.longitude %>};
var map = new google.maps.Map(document.getElementById('map'), {
center: test,
zoom: 15
});
var transitLayer = new google.maps.TransitLayer();
transitLayer.setMap(map);
var contentString = '住所:<%= @map.address %>';
var infowindow = new google.maps.InfoWindow({
content: contentString
});
var marker = new google.maps.Marker({
position:test,
map: map,
title: contentString
});
marker.addListener('click', function() {
infowindow.open(map, marker);
});
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=[google map apiのキー]&callback=initMap">
</script>
<p id="notice"><%= notice %></p>
<p>
<strong>Address:</strong>
<%= @map.address %>
</p>
<p>
<strong>Latitude:</strong>
<%= @map.latitude %>
</p>
<p>
<strong>Longitude:</strong>
<%= @map.longitude %>
</p>
<div id="map"></div>
<%= link_to 'Edit', edit_topic_map_path(topic_id: @map.topic_id,id: @map.id) %> |
<%= link_to 'Index', topic_maps_path %>
[google map apiのキー ]のところに取得してapiのキーを書く。google mapが表示されるのは、最後の方の
<div id="map"></div>
で表示されている。cssで
#map{
height: 400px;
width: 400px;
}
などと設定しないと地図が表示されないので注意。(%を使って高さや幅を指定すると、親ブロックが高さや横を指定してないと表示されないので、最初はpxで設定するのがおすすめ。)
他のmapsのビューで、link_toの設定でエラーが出るが、rake routes で確認して直せば良いので省略。
最後にformを変更する。
mapのnewアクションとeditアクションの時のフォームは、renderで表示している。この時form_forは二つの引数(@topicと@map)を持ってきてcreateとupdateで分けるようにした。(form_forは引数が既存かどうかでアクションを振り分ける)
<%= form_for [@topic,@map] do |form| %>
<% if @map.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@map.errors.count, "error") %> prohibited this map from being saved:</h2>
<ul>
<% @map.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<% if @map.address == nil %>
<%= form.label :address %>
<%= form.text_field :address,:value => @topic.cafe_name %>
<% else %>
<%= form.label :address %>
<%= form.text_field :address,:value => @map.address %>
<% end %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
text_fieldにvalueを上記のように設定することで、新規の時はカフェの名前を入力されたままの状態にする。
これで完成。
参考記事
こちらの記事を参考に作りました。
また何か間違いや質問があればぜひお願いします。