郵便番号から住所を調べる

今回はRubyで郵便番号から住所の都道府県、市、町名などを調べるスクリプトとRailsアプリを作成してみました。

## 概要
1. [データを手に入れる](#get-data)
2. [データを読み込む](#load-data)
3. [住所を調べる](#lookup-address)
4. [Railsアプリに追加してみる](#rails-app)
5. [まとめ](#wrap-up)


<a name="get-data"></a>
## データを手に入れる
[日本郵便](http://www.post.japanpost.jp/)のサイトで[郵便番号のデータは無料でダウンロードできます](http://www.post.japanpost.jp/zipcode/download.html)。

「読み仮名データの促音・拗音を小書きで表記するもの　全国一括」というファイルが必要なので、以下のリンクでダウンロードして展開してください。

[http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip](http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip)


<a name="load-data"></a>
## データを読み込む
展開した「KEN_ALL.csv]は以下の形式です。3つ目の欄は郵便番号、7つ目は都道府県、８つ目は市など、９つ目は町名などです。それ以外は使いませんが、興味があれば[日本郵便のサイトに説明があります](http://www.post.japanpost.jp/zipcode/dl/readme.html)。

```csv:KEN_ALL.CSV
01101,"060  ","0600000","ﾎｯｶｲﾄﾞｳ", "ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ", "ｲｶﾆｹｲｻｲｶﾞﾅｲﾊﾞｱｲ", "北海道", "札幌市中央区", "以下に掲載がない場合",0,0,0,0,0,0
01101,"064  ","0640941","ﾎｯｶｲﾄﾞｳ", "ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ", "ｱｻﾋｶﾞｵｶ", "北海道", "札幌市中央区", "旭ケ丘",0,0,1,0,0,0
01101,"060  ","0600041","ﾎｯｶｲﾄﾞｳ", "ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ", "ｵｵﾄﾞｵﾘﾋｶﾞｼ", "北海道", "札幌市中央区", "大通東 ",0,0,1,0,0,0
01101,"060  ","0600042","ﾎｯｶｲﾄﾞｳ", "ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ", "ｵｵﾄﾞｵﾘﾆｼ(1-19ﾁｮｳﾒ)", "北海道", "札幌市中央区", "大通西（１〜１９丁目）",1,0,1,0,0,0
...
```

ファイルのエンコーディングはShift_JISなので、エディタはサポートしていない場合、文字化けになるかもしれません。


まず、新しいRubyのスクリプトを作成して、データを読み込みます。

```ruby:post_code_index.rb
# encoding: utf-8
require 'csv'

class PostCodeIndex
  def initialize(file_name)
    @data = {}
 
    CSV.foreach(file, encoding: 'Shift_JIS:UTF-8') do |row|
      post_code, city, prefecture, street = row[2], row[6], row[7], row[8]
      @data[post_code] = {
        post_code: post_code,
        prefecture: prefecture,
        city: city,
        street: clean_street(street)
      }
    end
  end
  
  private
  
  def clean_street(input)
    return '' if input == '以下に掲載がない場合'
    input.sub(/（.*$/, '')
  end
end
```
Rubyの[CSVライブラリー](https://docs.ruby-lang.org/ja/2.3.0/library/csv.html)はファイルの内容を読み込んで各行を配列にします。そして、`encoding: 'Shift_JIS:UTF-8'」`を与えると、データのエンコードイングはShift_JISからUTF-8になります。

CSVのブロックで使う欄をハッシュに与えます。郵便番号をキーにして作ったハッシュを`@data`のハッシュに与えます。

郵便番号によっては町名がなくて「以下に掲載がない場合」が書いてある場合や、町名があっても「(1-19ﾁｮｳﾒ)」のような括弧が付いている場合もあります。このような場合はユーザーに見せたくないテキストを消して町名を正規化するために、`clean_street`のメソッドを作りました。

<a name="lookup-address"></a>
## 住所を調べる

読み込んだデータを使うために２つのメソッドを追加します。`String#tr`についてわからない方は[こちら](
https://docs.ruby-lang.org/ja/2.3.0/class/String.html#I_TR)。

```ruby:post_code_index.rb
# encoding: utf-8
require 'csv'

class PostCodeIndex
  # ...

  def lookup(post_code)
    @data[clean_post_code(post_code)]
  end
  
  private

  # ...

  def clean_post_code(input)
    input.to_s.tr('０-９', '0-9').gsub(/[^0-9]/, '')
  end
end
```

これで以下のように郵便番号から住所の都道府県、市、町名が調べる事ができます。

```ruby
index = PostCodeIndex.new('KEN_ALL.csv')

# 記号があってもなくても一緒
index.lookup('140-0002') # => {:post_code=>"1400002", :prefecture=>"品川区", :city=>"東京都", :street=>"東品川"}

# 文字列値でも整数値でも構わない
index.lookup(4180112) # => {:post_code=>"4180112", :prefecture=>"富士宮市", :city=>"静岡県", :street=>"北山"}

# 半角と全角はどっちでも大丈夫
index.lookup('１０８ー００７５') # => {:post_code=>"1080075", :prefecture=>"港区", :city=>"東京都", :street=>"港南"}

# もし与えられた郵便番号がデータにない場合、nilを戻す
index.lookup('000-0000') # => nil
```

もしユーザー入力中に郵便番号や住所の予測変換表示したい場合、もう一つのメソッドが必要です。

```ruby:post_code_index.rb
  def suggest(input, top = 5)
    partial_code = clean_post_code(input)

    @data.select { |post_code| post_code.start_with?(partial_code) }.values.take(top)
  end
```

これで、郵便番号の一部を与えると、予測変換された住所が戻ります。

```ruby
index = PostCodeIndex.new('KEN_ALL.csv')

index.suggest('140-0')
# => [
#   {:post_code=>"1400000", :prefecture=>"品川区", :city=>"東京都", :street=>""},
#   {:post_code=>"1400014", :prefecture=>"品川区", :city=>"東京都", :street=>"大井"},
#   {:post_code=>"1400012", :prefecture=>"品川区", :city=>"東京都", :street=>"勝島"},
#   {:post_code=>"1400001", :prefecture=>"品川区", :city=>"東京都", :street=>"北品川"},
#   {:post_code=>"1400015", :prefecture=>"品川区", :city=>"東京都", :street=>"西大井"}
# ]

# 提案はいくつほしいか指定できる
index.suggest('１０８００', 3)
# => [
#   {:post_code=>"1080022", :prefecture=>"港区", :city=>"東京都", :street=>"海岸"},
#   {:post_code=>"1080075", :prefecture=>"港区", :city=>"東京都", :street=>"港南"},
#   {:post_code=>"1080014", :prefecture=>"港区", :city=>"東京都", :street=>"芝"}
# ]

index.suggest('000-0000') # => []
```

<a name="rails-app"></a>
## Railsアプリに追加してみる

以上のコードはどんなアプリやフレームワークでも使えるけれども、Railsアプリに追加してみましょう。

```ruby:app/services/post_code_index.rb
# encoding: utf-8

require 'csv'
require 'singleton'

class PostCodeIndex
  include Singleton

  def initialize
    file = File.join File.dirname(__FILE__), '..', '..', 'config', 'KEN_ALL.csv'
    # ...
  end
  # ...
end
```
私は風通にデータベース以外、JSON APIなどからデータを読み込むクラスを`services`に入れます。そして、このクラスを構築するのは結構時間かかるので、一回だけ構築するように[Singleton](https://docs.ruby-lang.org/ja/2.3.0/library/singleton.html)にしました。

```ruby:config/initializers/post_code_index.rb
Rails.logger.info 'Initializing post code index...'
PostCodeIndex.instance
```

`PostCodeIndex`が最初に使われるとき、時間かからる構築しないように、initializerを追加しました。こうすると、アプリ起動するとき、構築されます。

```ruby:app/controllers/example_controller.rb
class ExampleController < ActionController::Base
  layout 'application'

  def index
  end

  def lookup_address
    address = PostCodeIndex.instance.lookup(params[:post_code])
    render json: { data: address }
  end
end
```

```ruby:config/routes.rb
Rails.application.routes.draw do
  get '/example', to: "example#index"
  get '/example/lookup_address', to: 'example#lookup_address'
end
```
`lookup_address`はJSONを戻すので、AJAXで住所のデータが読み込めます。

```erb:app/views/example/index.html.erb
<script>
 $(document).ready(function () {
   $("#example_zip_code").change(function () {
     $.ajax({
       url: "/example/lookup_address",
       json: true,
       data: { post_code: $(this).val() }
     }).done(function (response) {
       $("#example_prefecture").val(response.data.prefecture);
       $("#example_city").val(response.data.city);
       $("#example_street").val(response.data.street);
     });
   });
 });
</script>

<%= form_for :example do |f| %>
  <%= f.text_field :zip_code %>
  <%= f.text_field :prefecture %>
  <%= f.text_field :city %>
  <%= f.text_field :street %>
<% end %>
```
追加した`/example/lookup_address`のJSON APIはどんなJavaScriptのライブラリーやフレームワークでも使えるけれども、jQueryがRailsと付いているのでこの例はjQueryを使います。

![lookup.gif](https://qiita-image-store.s3.amazonaws.com/0/153003/21802cd2-20a6-6237-6d8a-a0facf26bab1.gif "lookup.gif")


簡単ですが、完成です。

<a name="wrap-up"></a>
## まとめ

他のウェブサイトでよくこの機能を見た事がありますが、思った以上に簡単に作れました。もちろん、郵便番号から住所を調べるライブリやJSON APIなどがあります。例えば、[ajaxzip3](https://github.com/ajaxzip3/ajaxzip3.github.io)。でも、自分で作くるのは一番柔軟で、自分のプロジェクトの条件と完全に合わせる事ができます。

- [post_code_index.rbのgist](https://gist.github.com/jaredklewis/c5278dfb308772837b1ce068494d7108):
- [Railsアプリのgist](https://gist.github.com/jaredklewis/fbd89863081bd50fc9866df46068ecac)