Ruby on Rails 6 で 郵便番号住所検索 な gem

JpAddressとは

日本郵便の「郵便番号データ」を用いて、あなたの Rails 6.1 サイトに「郵便番号からの住所検索機能」を組み込むための gem です。 以下の機能を提供します。

  • [郵便番号データ]をダウンロードして自前DBのテーブル(jp_address_zipcodes)にロードするクラスメソッド。(JpAddress::Zipcode.load_master_data
  • 郵便番号を受け取り都道府県名と住所をJSONで返却するAPI。 (jp_address/zipcodes#search

要するに、「郵便番号住所検索 ruby gem」でググった人向けの gem です。

APIはお使いのRailsアプリケーションにマウントして使います。外部のサービスに依存しません。
あと必要なのは、戻ってくるJSONを加工してHTML要素にセットするJavaScriptの記述だけです。
(本記事下部にサンプルコードを掲載しています。)

インストール

GemFileに追記

gem 'jp_address'

テーブル(jp_address_zipcodes)の作成

$ bundle 
$ bundle exec rails jp_address:install:migrations
$ bundle exec rails db:migrate

テーブルへの郵便番号データのロード

# 開発環境
$ bundle exec rails runner -e development 'JpAddress::Zipcode.load_master_data'

# 本番環境
$ bundle exec rails runner -e production 'JpAddress::Zipcode.load_master_data'

環境にもよりますが、5分ぐらいかかると思います。

APP_ROOT/tmp/ を作業ディレクトリに使用しています。
最初にテーブルをトランケートしますので、毎回「全件insert」になります。

同じ郵便番号を持つレコードは統合されます。

例:9896712

"宮城県","大崎市","鳴子温泉水沼"
"宮城県","大崎市","鳴子温泉南山"
"宮城県","大崎市","鳴子温泉山際"
"宮城県","大崎市","鳴子温泉和田"

これらは先頭から共通する地名を探し、うまく見つかれば

"宮城県","大崎市","鳴子温泉"

として1つのレコードにします。 共通する地名が抜き出せない場合は空の町名にします。

APIのマウント

Railsアプリの config/routes.rb に追記。 at: に渡す値は /jp_address でなくても /service や /api などでも構いません。

mount JpAddress::Engine, at: "/jp_address"

APIの利用

/jp_address にマウントした場合、下記URLへGETリクエストをすることで、JSONを取得できます。
後はこれを好きに加工してテキストボックスなどにセットして使ってください。

get リクエスト先

http://localhost:3000/jp_address/zipcodes/search?zip=5330033

API が返す JSON ```js script "id":84280,"zip":"5330033","prefecture":"大阪府","city":"大阪市東淀川区","town":"東中島"


### APIを利用するサンプル JavaScript
フォームに
1. #zipcode (郵便番号を入力するテキストボックス)
2. #prefecture_id (いわゆる都道府県プルダウン)
3. #address (住所を表示するテキストボックス)

の3要素があるとします。<br>
#zipcodeに入れられた値を keyup イベントで拾ってAPIを叩き、都道府県プルダウンを選択し、住所をセットするサンプルです。<br>
郵便番号の半角ハイフンは自動でカットされます。

都道府県プルダウンは、戻ってくるJSONの "prefecture" すなわち都道府県名で選択します。<br>
ですので、お持ちの都道府県マスターの各レコードがどのようなIDを持っていても構いません。

#### フォーム

#### application.js など共通に読み込まれるファイルに配置するJavaScript
※ JQuery の存在を前提にしています。<br>
※ もともと CoffeeScript で書いてあったソースを decaffeinate したものですので冗長です(汗)。<br>
本質的な処理はAddressSearch 関数が担っているだけで、他の関数は decaffeinate に必要なだけです。
```js script
  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;

      if ("value" in descriptor)
        descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps)
      _defineProperties(Constructor.prototype, protoProps);
    if (staticProps)
      _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  var AddressSearch = function() {
    "use strict";
    function AddressSearch(zip_elem_id, prefecture_elem_id, address_elem_id) {
      _classCallCheck(this, AddressSearch);
      this.zip                = $(zip_elem_id);
      this.prefecture         = $(prefecture_elem_id);
      this.address            = $(address_elem_id);
      this.prefecture_elem_id = prefecture_elem_id;
    }

    _createClass(AddressSearch, [{
      key: "_remove_hyphen",
      value: function _remove_hyphen() {
        return this.zip.val(this.zip.val().replace(/-/, ''));
      }
    }, {
      key: "_clear_current_value",
      value: function _clear_current_value() {
        $(this.prefecture_elem_id + ' >option:eq(0)').prop('selected', true);
        return this.address.val('');
      }
    }, {
      key: "_set_prefecture",
      value: function _set_prefecture(json) {
        return $(this.prefecture_elem_id + ' > option').each(function() {
          if ($(this).text() === json['prefecture']) {
            return $(this).prop('selected', true);
          }
        });
      }
    }, {
      key: "_set_address",
      value: function _set_address(json) {
        return this.address.val(json['city'] + json['town']);
      }
    }, {
      key: "_call_api",
      value: function _call_api() {
        var _this = this;
        return $.getJSON('/jp_address/zipcodes/search', {zip: this.zip.val()}, function(json) {
          if (json['id'] === null) {
            return _this._clear_current_value();
          } else {
            _this._set_prefecture(json);
            return _this._set_address(json);
          }
        });
      }
    }, {
      key: "execute",
      value: function execute() {
        this._remove_hyphen();
        if (this.zip.val().length === 7) {
          return this._call_api();
        }
      }
    }]);

    return AddressSearch;
  }();

フォームのあるページに配置するJavaScript

```js script // #zipcode, #prefecture_id, #address を各自の環境に合わせて書き換えてください。 $(function() { var address_search = new AddressSearch('#zipcode', '#prefecture_id', '#address'); $('#zipcode').keyup(function() { address_search.execute(); }); });


##### 作者
Copyright 2016 (c) Tad Kam, under MIT License.<br>
Tad Kam <[email protected]>