住所自動入力機能(jpostalとjp_prefectureの導入)

内容

住所登録/検索機能を実装する

住所登録機能

投稿時に住所(都道府県、市区町村)も一緒に登録できるようにする
postsテーブルにaddressesテーブルを紐付けるため、Formオブジェクトパターンを使用して投稿時に複数のテーブルに保存できるようにする ちなみに入力は、jpostalとjp_prefectureというgemを用いて住所自動入力を実装する

1. jp_prefectureの導入

jquery-railsとjp_prefectureを導入する
今回は既にjQueryは導入済みのため省略
Gemfile

gem 'jp_prefecture'

ターミナル

% bundle install
% rails s

2. モデルを作成し、マイグレーションファイルを編集

Postモデルは作成済みのため、Addressモデルのみ作成し、マイグレーションファイルを編集する
ターミナル

% rails g model address

db/migrate/**************_create_addresses.rb

create_table :addresses do |t|
  t.integer    :postal_code,     null: false
  t.integer    :prefecture_code, null: false
  t.string     :city,            null: false
  t.string     :street,          null: false
  t.references :post,            null: false, foreign_key: true
  t.timestamps
end

ターミナル

% rails db:migrate

3. アソシエーションを記述

app/models/post.rb

has_one :address, dependent: :destroy

app/models/address.rb

belongs_to :post

4. モデルを編集

公式サイトを参考にモデルを編集する
JpPrefecture
address.rb

include JpPrefecture
jp_prefecture :prefecture_code

def prefecture_name
  JpPrefecture::Prefecture.find(code: prefecture_code).try(:name)
end

def prefecture_name=(prefecture_name)
  self.prefecture_code = JpPrefecture::Prefecture.find(name: prefecture_name).code
end

5. 新たにmodelsディレクトリ直下にファイルを作成し、クラスを定義

app/modelsディレクトリ配下にpost_address.rbを作成
post_address.rb

class PostAddress
  include ActiveModel::Model
  attr_accessor :map_link, :distance, :course, :slope, :traffic, :crowd, :view, :comment, :user_id, :postal_code, :prefecture_code, :city, :street

  with_options presence: true do
    validates :map_link,
              format: { with: /\A#{URI::DEFAULT_PARSER.make_regexp(%w[http https])}\z/,
                        message: 'はhttpもしくはhttpsで始まるURLで入力してください' }
    validates :distance, numericality: { greater_than: 0 }
    validates :course
    validates :slope
    validates :traffic
    validates :crowd
    validates :view
    validates :comment, length: { maximum: 140 }
    validates :user_id
    validates :postal_code, format: { with: /\A[0-9]{7}\z/ }
    validates :prefecture_code
    validates :city
    validates :street
  end
end

postsテーブルに保存されるuser_idには、本来belongs_to :userのアソシエーションにより、バリデーションが設定されている
しかし、post_addressクラスにはアソシエーションを定義することはできないため、belongs_toによるバリデーションを行うことができない
そこで、post_addressクラスでuser_idに対してバリデーションを新たに設定

6. 元々記述していたpost.rbのバリデーションを削除

7. データをテーブルに保存する処理を記述

post_address.rb

class PostAddress
# 略

  def save
    post = Post.create(map_link: map_link, distance: distance, course: course, slope: slope, traffic: traffic, crowd: crowd, view: view, comment: comment, user_id: user_id)
    Address.create(postal_code: postal_code, prefecture_code: prefecture_code, city: city, street: street, post_id: post.id)
  end
end

8. コントローラーを編集

postsコントローラーを以下のように編集する
posts_controller.rb

def new
  @post_address = PostAddress.new
end

def create
  @post_address = PostAddress.new(post_params)
  if @post_address.valid?
    @post_address.save
    redirect_to posts_path
  else
    render :new
  end
end

def post_params
  params.require(:post_address).permit(:map_link, :distance, :course, :slope, :traffic, :crowd, :view, :comment, :image, :postal_code, :prefecture_code, :city, :street).merge(user_id: current_user.id)
end

valid?メソッドを使用しているのは、PostAddressクラスがApplicationRecordを継承していないことにより、saveメソッドにはバリデーションを実行する機能がないため

9. ビューを編集(新規投稿ページ)

生成したインスタンスをnew.html.erbにおいて利用
app/views/posts/new.html.erb

# 修正前
<%= form_with model: @post, local: true do |f| %>
# 修正後
<%= form_with model: @post_address, local: true do |f| %>
# 下記追加
<div class="weight-bold-text">
  郵便番号(ハイフンなし)
  <span class="indispensable">必須</span>
</div>
<%= f.text_field :postal_code, autocomplete: 'postal_code', class: "form-control", id: 'postal_code' %>
<div class="weight-bold-text">
  都道府県
  <span class="indispensable">必須</span>
</div>
<%= f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name, { prompt: '選択してください' }, class: 'form-control', id: 'prefecture_code' %>
<div class="weight-bold-text">
  市区町村
  <span class="indispensable">必須</span>
</div>
<%= f.text_field :city, autocomplete: 'city', class: "form-control", id: 'city' %>
<div class="weight-bold-text">
  番地
  <span class="indispensable">必須</span>
</div>
<%= f.text_field :street, autocomplete: 'street', class: "form-control", id: 'street' %>

10. jquery.jpostal.jsを導入

公式GitHubを参考に導入
jquery.jpostal.js
設置方法は2種類あり、自分のサーバに郵便データを設置する方法としない方法がある
jpostal-1006.appspot.comで公開しているので、jquery.jpostal.jsやjson/*.jsonを設置する必要がない、サイト運営者の定期的な郵便データ更新作業も必要ない、と記載されているので今回は自分のサーバにはデータを設置しない方法で導入する(おそらくこちらの方が楽)
まず、jquery本体とjquery.jpostal.jsをインクルードする(今回はjqueryは導入済みのためインクルードの必要はなかった)
application.html.erb

<head>
  <script type="text/javascript" src="//jpostal-1006.appspot.com/jquery.jpostal.js"></script>
</head>

app/javascript配下にaddress_autofill.jsを作成し、コードを記述
app/javascript/address_autofill.js

$(function(){
  $('#postal_code').jpostal({
    postcode: ['#postal_code'],
    address: {
      '#prefecture_code': '%3',
      '#city': '%4',
      '#street': '%5%6%7',
    }
  });
});

公式サイトではjQueryのreadyメソッドを使用しているが、現在は非推奨となっているため修正している
最後にapplication.jsでaddress_autofill.jsを読み込む
app/javascript/packs/address_autofill.js

require("../address_autofill")

以上で導入完了

11. Formオブジェクトのedit/update

ここは難しく説明ができないため割愛

12. Formオブジェクトのテストコード

所感

理解できていないコードを写すのはよくないと痛感。。。 でもできることも増やしていかないといけないので、やりながら理解していくしかない!