自己紹介
はじめまして、はる(@lemonade_37)と申します。
駆け出しエンジニアとして働き始めて約4ヶ月が経過しました🐣
概要
だいぶ期間が空いてしまいましたが、前回の 【Rails7】Maps JavaScript APIを使って投稿を地図上にピンで表示する の続きとして、位置情報を含んだMapと連動できる新規投稿機能を実装していきます。
完成イメージ図
環境
- Ruby: 3.2.2
- Rails: 7.0.8(esbuild)
- TailwindCSS
- DaisyUI
間違っている箇所や、紹介した方法よりも良い方法があるかもしれませんので、その際は教えて頂けると嬉しいです🙇
新規投稿機能の実装
前回の記事で、APIの導入、geocoder の導入、投稿一覧ページの作成は既にされている状態からスタートします。
コントローラー・ルーティング・ビューの作成
-
postsコントローラーに
new
,create
アクションを追加しますposts_controller.rbdef new @post = Post.new end def create @post = Post.new(post_params) if @post.save redirect_to posts_path else render :new end end private def post_params params.require(:post).permit(:title, :address, :body) end
-
ルーティングにも
new
,create
を追加しますroutes.rbresources :posts, only: %i[index new create]
-
フォーム部分のパーシャルのビューを作成します
views/posts/_form.html.erb<div class="card w-96 bg-base-100 shadow-xl"> <div class="card-body"> <%= form_with model: post, local: true do |f| %> <div> <%= f.label :title %> <%= f.text_field :title, placeholder: "スポット名を入力して下さい", class: "input input-bordered w-full max-w-xs" %> </div> <div> <%= f.label :address %> <%= f.text_field :address, placeholder: "住所を入力してください", class: "input input-bordered w-full max-w-xs" %> </div> <div> <%= f.label :body %> <%= f.text_field :body, placeholder: "おすすめポイントなど、一言", class: "input input-bordered w-full max-w-xs" %> </div> <%= f.submit class: "btn btn-info" %> <% end %> </div> </div>
card 部分は DaisyUI の class を使用してカードが浮いているような見た目にしています。
-
新規投稿ページのビューファイルも作成し、先ほど作成したパーシャルを呼び出します
views/posts/new.html.erb<div class="flex justify-center items-center mt-20"> <%= render 'form', { post: @post } %> </div>
-
この時点で、実在する住所を入力すれば、保存ができるようになっています
入力した住所から緯度と経度を入力してくれる動作については、
gem "geocoder"
の導入と、モデルの下記の記載のおかげでできるようになっています。(前回の記事で設定済)post.rbclass Post < ApplicationRecord geocoded_by :address after_validation :geocode end
Place Autocomplete を使った新規投稿時のオートコンプリート機能の実装
住所を手動で入力するのは大変なので、GoogleMap に登録されているスポットが自動で出てくるオートコンプリート機能をつけていきます。
-
Google Cloud Platform の API ライブラリで、Places API を検索して、有効にします
-
Map の API Key の認証情報で、「API の制限」をしている場合は、有効にした Places API にもチェックを入れます(map JavaScript API と同じ API キーで実装可能です)
-
新規投稿フォームのビューに、
id
とscriptタグ
を追加しますviews/posts/_form.html.erb<div class="card w-96 bg-base-100 shadow-xl"> <div class="card-body"> <%= form_with model: post, local: true do |f| %> <div> <%= f.label :title, for: "Title" %> + <%= f.text_field :title, id: "Title", placeholder: "スポット名を入力して下さい", class: "input input-bordered w-full max-w-xs", autocomplete: "off" %> </div> <div> <%= f.label :address, for: "Address" %> + <%= f.text_field :address, id: "Address", placeholder: "住所を入力してください", class: "input input-bordered w-full max-w-xs", autocomplete: "off" %> </div> <div> <%= f.label :body, for: "Body" %> + <%= f.text_field :body, id: "Body", placeholder: "おすすめポイントなど、一言", class: "input input-bordered w-full max-w-xs", autocomplete: "off" %> </div> <%= f.submit class: "btn btn-info" %> <% end %> </div> </div> + <script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_API_KEY"] %>&libraries=places&callback=initialize"></script>
-
JS ファイルを作成します
app/javascript/application.js// Entry point for the build script in your package.json import { Turbo } from "@hotwired/turbo-rails" Turbo.session.drive = false import "./controllers" + import './place_autocomplete.js'
app/javascript/place_autocomplete.js// Place Autocomplete document.addEventListener('DOMContentLoaded', function () { const inputTitle = document.getElementById('Title'); const inputAddress = document.getElementById('Address'); // オートコンプリートのオプション const options = { types: ['establishment'], componentRestrictions: { country: 'JP' }, }; // オートコンプリート適用 const autocompleteTitle = new google.maps.places.Autocomplete(inputTitle, options); const autocompleteAddress = new google.maps.places.Autocomplete(inputAddress, options); // タイトルのオートコンプリートが選択されたとき autocompleteTitle.addListener('place_changed', function() { const place = autocompleteTitle.getPlace(); inputTitle.value = place.name; inputAddress.value = place.formatted_address; }); // 住所のオートコンプリートが選択されたとき autocompleteAddress.addListener('place_changed', function() { const place = autocompleteAddress.getPlace(); inputTitle.value = place.name; inputAddress.value = place.formatted_address; }); });
componentRestrictions: { country: 'JP' }
この設定を記載することで、オートコンプリートのサジェストを日本国内のスポットのみにできます。
(都道府県ごとに制限することはできないようでした😵)また、使いやすさの面で、投稿タイトル・住所の欄のどちらで記載した際にもオートコンプリートが動くようにしました。
-
存在しない住所で投稿できないバリデーション、投稿失敗した際のエラーメッセージを追加します(エラーメッセージを既に導入済みの方はバリデーションのみ追加してください)
post.rbclass Post < ApplicationRecord geocoded_by :address after_validation :geocode + # ついでに通常のバリデーションも追加 + validates :title, presence: true, length: { maximum: 255 } + validates :address, presence: true + validates :body, length: { maximum: 65_535 } + validate :validate_address + def validate_address + geocoded = Geocoder.search(address) + unless geocoded&.first&.coordinates.present? + errors.add(:address, 'が存在しません') # 「住所が存在しません」と表示される + end + end end
views/shared/_error_messages.html.erb<% if object.errors.any? %> <div role="alert" class="alert alert-warning"> <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg> <span> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </span> </div> <% end %>
※ エラーの表現には DaisyUI の Alert を使用しています。
views/posts/_form.html.erb〜省略〜 <%= form_with model: post, local: true do |f| %> + <%= render 'shared/error_messages', object: f.object %> <div> <%= f.label :title, for: "Title" %> 〜省略〜
posts_controller.rbdef create @post = Post.new(post_params) if @post.save redirect_to posts_path else + render :new, status: :unprocessable_entity end end
Rails7 特有の対応で、
status: :unprocessable_entity
を追加する必要があります。 -
ここまでで、以下の機能が完成しました!🎉
- 新規投稿機能
- 住所オートコンプリート機能
- 住所を入力すると緯度と経度も保存され、地図上に投稿が表示される機能(前回の記事の範囲)
まとめ
$\tiny{やっと続きが書けました🙌}$
この機能を初めて実装した際はJavaScriptにも慣れておらず苦戦したので、自分のためにもまとめることができてよかったです!
記事の方法上手くいかない際、より良い方法があった際はぜひコメントください🙇♀️
ここまで読んでくださりありがとうございました!
参考・引用記事
引用記事
1)【Rails】Rails7.0でバリデーションのエラーメッセージが表示されない時の解決法