とりあえず住所検索APIサーバーをRailsで構築してみました。（住所の精度は無視しています。）

## 01. 郵便番号APIサーバーを構築します。

### はじめに

郵便番号から住所を提供してくるツールなど調べていたらこの記事にたどり着きました。
[FizzBuzz 問題どや顔で解くひとなんかよりも "KEN_ALL.csv" をうまく扱える人の方が社会的貢献度高い - Togetter](https://togetter.com/li/352382)
ざっと読んだ上で、面白そうなので郵便番号APIサーバの構築してみます。

#### 郵便番号APIを提供している有名どころや新しいもの
[郵便番号検索API - zipcloud](http://zipcloud.ibsnet.co.jp/doc/api)
[GitHub - madefor/postal-code-api: Postal Code API](https://github.com/madefor/postal-code-api)
[郵便番号から住所を検索するAPIを作りました - Qiita](https://qiita.com/kmdsbng/items/935820212801e68867f0)
[PostcodeJP API](https://postcode-jp.com/)

不備があれば編集リスクエスト下さい。

### 目標
- `郵便局`が提供している郵便番号データを利用して`郵便番号から住所を返却する`郵便番号APIサーバーを構築します。

### 開発環境

こちらはプロジェクト構築後に確認した開発環境になります。

```sh:console
$ rbenv exec bundle exec rake about
Running via Spring preloader in process 92302
About your application's environment
Rails version             5.2.0
Ruby version              2.5.0-p0 (x86_64-darwin16)
RubyGems version          2.7.3
Rack version              2.0.5
JavaScript Runtime        Node.js (V8)
Middleware                Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, ActiveSupport::Cache::Strategy::LocalCache::Middleware, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Sprockets::Rails::QuietAssets, Rails::Rack::Logger, ActionDispatch::ShowExceptions, WebConsole::Middleware, ActionDispatch::DebugExceptions, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ContentSecurityPolicy::Middleware, Rack::Head, Rack::ConditionalGet, Rack::ETag, Rack::TempfileReaper
Application root          /Users/zipcode-sandboc
Environment               development
Database adapter          mysql2
Database schema version   0
```

### Github

https://github.com/ymsr5612/zipcode-sandbox.git

### プロジェクト作成

> コマンドを打つ時に`rbenv exec`から始めるコマンドについては適宜自分の環境に合わせて読み替えて下さい。基本は省略可のはずです。

```sh:console
$ mkdir zipcode-sandbox
$ cd zipcode-sandbox
$ git init
$ gibo node MacOS rails >> .gitignore
$ rbenv local 2.5.0
$ ndenv local v8.9.3
$ rbenv exec gem install bundle
$ rbenv exec bundler init
```

Gemfileを開きRailsのバージョンを指定します。

```rb:Gemfile
# gem 'rails'
gem 'rails', '~> 5.2.0'
```

```sh:console
$ rbenv exec bundle install --path=vendor/bundle/
$ rbenv exec bundle exec rails new . --database=mysql -T -G 
```

Railsのプロジェクトオプションの説明

|オプション|説明|
|---|---|
|--database=mysql|mysqlを指定|
|-T|Test::Unitを組み込まない|
|-G|.gitignoreを組み込まない|

dockerでmysqlコンテナーを立てます。

```sh:console
$ touch docker-compose.yml
```

```yml:docker-compose.yml
version: '2'

services:

    datastore:
      image: busybox
      volumes:
        - mysql-data:/var/lib/mysql

    db:
      image: mysql:5.7.10
      environment:
        - MYSQL_ROOT_PASSWORD=abc123
        - MYSQL_USER=root
        - MYSQL_DATABASE=zipcode-sandboc_development
      ports:
        - "3306:3306"
      volumes_from:
        - datastore

volumes:
  mysql-data:
    driver: local
```

dockerを構築します。

```sh:console
$ docker-compose build
$ docker-compose up -d
```

mysqlが接続できるか確認します。
`mysql>`と出れば接続成功です。

```sh:console
$ mysql -h127.0.0.1 -uroot --password=abc123 --port=3306
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.10 MySQL Community Server (GPL)
<省略>
mysql>
```

プロジェクトを起動してみます。

```sh:console
$ rbenv exec bundle exec rails s --port=3000 -b 0.0.0.0
=> Booting Puma
=> Rails 5.2.0 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
```

`0.0.0.0:3000`にアクセスして接続できればプロジェクト立ち上げ成功です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/30329/a4b44d46-07ed-af66-82f7-e28aa748820c.png)

---

## 02. 郵便局から郵便番号ZIPファイルを取得する。

郵便番号ZIPを取得します。
郵便番号取得バッチを作成するために`runner`を利用します。

> wget,curlあたりで取得するほうが簡単ですがRubyに拘ります。

名前空間にlib配下を追加します。

### 名前空間の設定追加

```rb:config/application.rb
config.autoload_paths += Dir["#{config.root}/lib"]
```

### runnerのサンプルを作成して動きを確認します。

普段はrakeタスクを利用しているので`runner`の動作確認をします。
> runnerの使い方がわかる人は、`郵便局から郵便番号ZIPファイルを取得します。` まで読み飛ばして下さい。

#### バッチクラスを作成
実行確認のために簡単なバッチを用意します。

```rb
# lib/tasks/batch.rb
class Tasks::Batch
  def self.execute
    # 実行したいコードを書く
    p "Hello world"
  end
end
```

#### バッチの実行確認

railsを起動して、runnerタスクを実行します。
タスクが実行できればOKです。

```sh:console
$ rbenv exec bundle exec rails runner Tasks::Batch.hello
Running via Spring preloader in process 95534
"Hello world"
```

### 郵便局から郵便番号ZIPファイルを取得します。

郵便局が提供している郵便番号データをダウンロードします。
ダウンロードには標準ライブラリの`open-uri`を利用します。
また、ダウンロードしたzipファイル解凍のには`gem rubyzip`を利用します。

[読み仮名データの促音・拗音を小書きで表記しないもの - zip形式 日本郵便](http://www.post.japanpost.jp/zipcode/dl/oogaki-zip.html)全国一括版を利用します。

事前に`gem rubyzip`をinstallしておきます。

```rb:Gemfile
gem 'rubyzip', '~> 1.2', '>= 1.2.1'
```

```sh:console
$ rbenv exec bundle install --path=vendor/bundle/
```
### 郵便番号ZIPファイルの取得手順
1. 前回取得したzipファイルと展開したKEN_ALL.CSVファイルを削除
2. URLからzipファイルをダウンロード
3. zipファイルを解凍

詳細の説明はコードに記載します。

```rb:lib/batch.rb
require 'open-uri'
require 'zip'

class Tasks::JapanPost
  class << self
    def download_zipcodes_file
      delete_files(['public/KEN_ALL.CSV', 'public/ken_all.zip'])
      download_and_save("public/", "http://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip")
      unzip_file("public/", "ken_all.zip")
      p 'public/ken_all.zipファイルの展開に成功しました。'
    end
    
    private

      def delete_files(delete_files)
        delete_files.each do |file|
          File.delete file
        end
      end
      
      def download_and_save(dir_path, url_path)
        fileName = File.basename url_path
        open(dir_path + "#{fileName}", 'wb') do |output|
          open(url_path) do |data|
            output.write(data.read)
          end
        end
      end

      def unzip_file(dir_path, file_name)
        Zip::File.open(dir_path + file_name) do |zip|
          zip.each do |entry|
            # { true } は展開先に同名ファイルが存在する場合に上書きする指定
            zip.extract(entry, dir_path + entry.name) { true }
          end
        end
      end
  end
end
```

### シェルで実行

シェルで実行できるように`scripts/zipcode_download.sh`を作成します。

```sh:console
$ mkdir scripts
$ touch scripts/zipcode_download.sh
$ chmod 766 scripts/ # (一応)実行権限を付与
```

シェルはこんな感じです。

```sh:zipcode_download.sh
#!/usr/bin/env bash
# 前回取得したzipファイルを削除
# zipファイルをダウンロードして解凍まで実施。
$ rbenv exec bundle exec rails runner Tasks::JapanPost.download_zipcodes_file
```

実行します。

```sh:console
$ sh ./scripts/zipcode_download.sh
```

`public/KEN_ALL.CSV`が生成されていればOKです。

### 参考

[郵便番号検索 | ゆうびん.jp](https://yu-bin.jp/zipcode/)
[郵便番号データのダウンロード - zipcloud](http://zipcloud.ibsnet.co.jp/)

---

## 03. 郵便番号一覧CSVファイルの読み込み

取得したCSVファイルを読み込みます。
後からDBに登録するのですが、とりあえずCSVファイルを読み込みます。
標準ライブラリ`CSV`を利用します。
CSVファイルのエンコードは文字コードには、MS漢字コード（SHIFT JIS）です。

```rb:tasks/japan_post.rb
require 'csv'

class Tasks::JapanPost
  class << self
    def update_zipcodes
      csv_file_read("public/", "KEN_ALL.CSV").each do |csv|
        p csv # この情報をDBに登録予定
      end
    end
  
    private
  
      def self.csv_file_read(dir_path, file_name)
        CSV.read(dir_path + file_name, encoding: "CP932:UTF-8");
      end
  end
end
```

シェルで実行できるように`scripts/insert_and_update_zipcodes.sh`を作成します。

```sh:console
$ touch scripts/insert_and_update_zipcodes.sh
```

シェルはこんな感じです。

```sh:insert_and_update_zipcodes.sh
#!/usr/bin/env bash

# CSVファイルを読み込んで、DBに保存します。
# レコードが存在しない場合は新規登録し、存在する場合は更新

rbenv exec bundle exec rails runner Tasks::JapanPost.update_zipcodes
```

実行

```sh:console
$ sh ./scripts/insert_and_update_zipcodes.sh
["37201", "760  ", "7600042", "ｶｶﾞﾜｹﾝ", "ﾀｶﾏﾂｼ", "ﾀﾞｲｸﾏﾁ", "香川県", "高松市", "大工町", "0", "0", "0", "0", "0", "0"]
["37201", "761  ", "7618041", "ｶｶﾞﾜｹﾝ", "ﾀｶﾏﾂｼ", "ﾀﾞﾝｼﾁﾖｳ", "香川県", "高松市", "檀紙町", "0", "0", "0", "0", "0", "0"]
["37201", "760  ", "7600007", "ｶｶﾞﾜｹﾝ", "ﾀｶﾏﾂｼ", "ﾁﾕｳｵｳﾁﾖｳ", "香川県", "高松市", "中央町", "0", "0", "0", "0", "0", "0"]
["37201", "761  ", "7618058", "ｶｶﾞﾜｹﾝ", "ﾀｶﾏﾂｼ", "ﾁﾖｸｼﾁﾖｳ", "香川県", "高松市", "勅使町", "0", "0", "0", "0", "0", "0"]
["37201", "760  ", "7600061", "ｶｶﾞﾜｹﾝ", "ﾀｶﾏﾂｼ", "ﾂｷｼﾞﾁﾖｳ", "香川県", "高松市", "築地町", "0", "0", "0", "0", "0", "0"]
```

こんな感じでCSVファイルが読み込めればOKです。

---

## 04.郵便番号を格納するテーブルを作ります。

郵便番号データの説明に詳しい情報が掲載されています。
[郵便番号データの説明 - 日本郵便](http://www.post.japanpost.jp/zipcode/dl/readme.html)

### DB設計

郵便番号一覧を格納するためのテーブルを用意します。
`返却対象`は郵便番号検索結果として利用するデータを指します。
郵便番号APIサーバが返却するデータと今のところは定義しておきます。

### 郵便番号のデータ形式

|No|用途|返却対象|
|---|---|---|
|1|全国地方公共団体コード　半角数字|-
|2|（旧）郵便番号（5桁）半角数字|-
|3|郵便番号（7桁）半角数字|◯
|4|都道府県名　半角カタカナ（コード順に掲載）（注1）|-
|5|市区町村名　半角カタカナ（コード順に掲載）（注1）|-
|6|町域名　半角カタカナ（五十音順に掲載）　（注1）|-
|7|都道府県名　漢字（コード順に掲載）　（注1,2）|◯
|8|市区町村名　漢字（コード順に掲載）　（注1,2）|◯
|9|町域名　漢字（五十音順に掲載）　（注1,2）|◯
|10|一町域が二以上の郵便番号で表される場合の表示<br>（注3）（「1」は該当、「0」は該当せず）|-
|11|小字毎に番地が起番されている町域の表示<br>（注4）（「1」は該当、「0」は該当せず）|-
|12|丁目を有する町域の場合の表示　<br>（「1」は該当、「0」は該当せず）|-
|13|一つの郵便番号で二以上の町域を表す場合の表示　<br>（注5）　（「1」は該当、「0」は該当せず）|-
|14|更新の表示<br>（注6)（「0」は変更なし、「1」は変更あり、「2」廃止（廃止データのみ使用））|-
|15|変更理由　<br>（「0」は変更なし、「1」市政・区政・町政・分区・政令指定都市施行、「2」住居表示の実施、「3」区画整理、「4」郵便区調整等、「5」訂正、「6」廃止（廃止データのみ使用））|-

> 2018/05時点の資料抜粋
> 引用 [郵便番号データの説明 - 日本郵便](http://www.post.japanpost.jp/zipcode/dl/readme.html)

十数万件あるデータは上記の仕様に準拠していますが、上記の条件に合わない特例が存在します。
その特例条件のデータについれは、10〜15のカラムでわかるようになっています。
ですが、いろんなサイトで公開されていない仕様があると言われているので郵便番号を正すにはそれなりの覚悟が入りそうです。

### modelを作成します。

まずは、マイグレーションファイルを作成します。

```rb:db/migrete/_create_yubins.rb 
class CreateYubins < ActiveRecord::Migration[5.2]
  def change
    create_table :yubins do |t|
      t.string    :local_governments_cd
      t.string    :past_zipcode
      t.string    :zipcode,             null: false, index: true
      t.string    :region_kana,         null: false
      t.string    :locality_kana,       null: false
      t.string    :street_address_kana, null: false
      t.string    :region,              null: false
      t.string    :locality,            null: false
      t.string    :street_address,      null: false
      t.integer   :flag_1,              default: 0,   null: false
      t.integer   :flag_2,              default: 0,   null: false
      t.integer   :flag_3,              default: 0,   null: false
      t.integer   :flag_4,              default: 0,   null: false
      t.integer   :view_update,         default: 0,   null: false
      t.integer   :reason,              default: 0,   null: false

      t.timestamps
    end
  end
end
```

次にマイグレーションを実行

```sh:console
$ rbenv exec bundle exec rake db:migrate
== 20180511000000 CreateYubins: migrating =====================================
-- create_table(:yubins)
   -> 0.0125s
== 20180511000000 CreateYubins: migrated (0.0126s) ============================
```

ついでにSEEDを作成してデータを入れます。

```rb:db/seeds.rb
Yubin.create([{
    local_governments_cd: '26109',
    past_zipcode: '612  ',
    zipcode: '6128035',
    region_kana: 'ｷｮｳﾄﾌ',
    locality_kana: 'ｷｮｳﾄｼﾌｼﾐｸ',
    street_address_kana: 'ﾄｷﾜﾁｮｳ',
    region: '京都府',
    locality: '京都市伏見区',
    street_address: '常盤町',
    flag_1: 0,
    flag_2: 0,
    flag_3: 0,
    flag_4: 0,
    view_update: 1,
    reason: 5
  },
  {
    local_governments_cd: '28201',
    past_zipcode: '670  ',
    zipcode: '6700815',
    region_kana: 'ﾋｮｳｺﾞｹﾝ',
    locality_kana: 'ﾋﾒｼﾞｼ',
    street_address_kana: 'ﾉｻﾞﾄﾔﾏﾄﾁｮｳ',
    region: '兵庫県',
    locality: '姫路市',
    street_address: '野里大和町',
    flag_1: 0,
    flag_2: 0,
    flag_3: 0,
    flag_4: 0,
    view_update: 1,
    reason: 5
  }])
```

seeds.rbを流して、データの確認をしてみます。

```sh:console
$ rbenv exec bundle exec rake db:seed
$ rbenv exec bundle exec rails console -s
Running via Spring preloader in process 51625
Loading development environment in sandbox (Rails 5.2.0)
Any modifications you make will be rolled back on exit
irb(main):001:0> Yubin.find 1
  Yubin Load (0.9ms)  SELECT  `yubins`.* FROM `yubins` WHERE `yubins`.`id` = 1 LIMIT 1
=> #<Yubin id: 1, local_governments_cd: "26109", past_zipcode: "612  ", zipcode: "6128035", region_kana: "ｷｮｳﾄﾌ", locality_kana: "ｷｮｳﾄｼﾌｼﾐｸ", street_address_kana: "ﾄｷﾜﾁｮｳ", region: "京都府", locality: "京都市伏見区", street_address: "常盤町", flag_1: 0, flag_2: 0, flag_3: 0, flag_4: 0, view_update: 1, reason: 5, created_at: "2018-05-12 15:17:17", updated_at: "2018-05-12 15:17:17">
irb(main):002:0> Yubin.find 2
  Yubin Load (1.7ms)  SELECT  `yubins`.* FROM `yubins` WHERE `yubins`.`id` = 2 LIMIT 1
=> #<Yubin id: 2, local_governments_cd: "28201", past_zipcode: "670  ", zipcode: "6700815", region_kana: "ﾋｮｳｺﾞｹﾝ", locality_kana: "ﾋﾒｼﾞｼ", street_address_kana: "ﾉｻﾞﾄﾔﾏﾄﾁｮｳ", region: "兵庫県", locality: "姫路市", street_address: "野里大和町", flag_1: 0, flag_2: 0, flag_3: 0, flag_4: 0, view_update: 1, reason: 5, created_at: "2018-05-12 15:17:17", updated_at: "2018-05-12 15:17:17">
irb(main):003:0>
```
登録できたデータを読み込むことができました。

### 実際に郵便番号一覧をDBに登録してみます。

一旦上記で試したDBデータは破棄します。

```sh:console
# DBをDropして作り直し
$ rbenv exec bundle exec rake db:drop db:create db:migrate
Dropped database 'zipcode-sandboc_development'
Dropped database 'zipcode-sandboc_test'
Created database 'zipcode-sandboc_development'
Created database 'zipcode-sandboc_test'
== 20180511144137 CreateYubins: migrating =====================================
-- create_table(:yubins)
   -> 0.0135s
== 20180511144137 CreateYubins: migrated (0.0135s) ============================
```

CSVファイルのままだと、扱いが不便なので一旦、何も加工せずに郵便番号一覧をDBに格納します。
> データを加工せずにDBに取り込むのならテーブルの名前をcsv_yubinにするなど工夫したほうがいいかも。

```rb:
# CSVファイルを読み込みDBにinsertします。
def self.update_zipcodes
  csv_file_read("public/", "KEN_ALL.CSV").each do |csv|
    p csv
    Yubin.create!({
        local_governments_cd: csv[0],
        past_zipcode: csv[1],
        zipcode: csv[2],
        region_kana: csv[3],
        locality_kana: csv[4],
        street_address_kana: csv[5],
        region: csv[6],
        locality: csv[7],
        street_address: csv[8],
        flag_1: csv[9],
        flag_2: csv[10],
        flag_3: csv[11],
        flag_4: csv[12],
        view_update: csv[13],
        reason: csv[14]
      })
  end
  p "#{Yubin.all.length}を登録しました。"
end
```

実行してDBに取り込みます。

```sh:console
$ rbenv exec bundle exec rails runner Tasks::JapanPost.update_zipcodes
・・・
["47381", "90715", "9071544", "ｵｷﾅﾜｹﾝ", "ﾔｴﾔﾏｸﾞﾝﾀｹﾄﾐﾁﾖｳ", "ﾊﾄﾏ", "沖縄県", "八重山郡竹富町", "鳩間", "0", "0", "0", "0", "0", "0"]
["47382", "90718", "9071800", "ｵｷﾅﾜｹﾝ", "ﾔｴﾔﾏｸﾞﾝﾖﾅｸﾞﾆﾁﾖｳ", "ｲｶﾆｹｲｻｲｶﾞﾅｲﾊﾞｱｲ", "沖縄県", "八重山郡与那国町", "以下に掲載がない場合", "0", "0", "0", "0", "0", "0"]
```
ログにCSVの内容が流れればOKです。
実際にDBに登録しているログの確認するには`docker-compose logs`で確認できます。

```sh:console
$ docker-compose logs -f db
   (0.3ms)  BEGIN
  ↳ lib/tasks/japan_post.rb:17
  Yubin Create (0.5ms)  INSERT INTO `yubins` (`local_governments_cd`, `past_zipcode`, `zipcode`, `region_kana`, `locality_kana`, `street_address_kana`, `region`, `locality`, `street_address`, `created_at`, `updated_at`) VALUES ('47348', '90113', '9011301', 'ｵｷﾅﾜｹﾝ', 'ｼﾏｼﾞﾘｸﾞﾝﾖﾅﾊﾞﾙﾁﾖｳ', 'ｲﾀﾗｼｷ', '沖縄県', '島尻郡与那原町', '板良敷', '2018-05-12 15:37:52', '2018-05-12 15:37:52')
  ↳ lib/tasks/japan_post.rb:17
   (1.6ms)  COMMIT
  ↳ lib/tasks/japan_post.rb:17
```

これで、`KEN_ALL.CSV`をDBに取り込むことに成功しました。
`rails console`で確認してみます。

```sh:console
$ rbenv exec bundle exec rails console -s
Running via Spring preloader in process 54010
Loading development environment in sandbox (Rails 5.2.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
irb(main):002:0>
irb(main):003:0>
irb(main):004:0>
irb(main):005:0> Yubin.all.length
  Yubin Load (319.0ms)  SELECT `yubins`.* FROM `yubins`
=> 124184
irb(main):006:0>
```

OKですね。

---

## 05. 郵便番号APIを作成する。

郵便番号一覧をDBに登録することができましたので
郵便番号から住所を取得するAPIを実装します。
> KEN_ALL.CSVが癖が強いデータだということは認識しています。
> ここではAPIを作成に注力します。

### API設計

#### ざっとな感じの設計方針

- サーバ側に状態を保持するような情報を持たない。
- リクエストは完全かつ孤立したものにする。
- 同じURLは同じリソースを返す。

#### APIイメージ

|例|URL
|---|---
|request|//zipcode-sandbox/api/v1/yubin/0660005

```json:想定するjsonフォーマット
{
  "message": null,
  "results": [
    { "address1": "北海道",
      "address2": "千歳市",
      "address3": "協和",
      "zipcode": "0660005"
    }],
  "status": 200
}
```

#### 実装
 
リクエストを受けるためのControllerを作成します。
 
```sh:console
$ rbenv exec bundle exec rails generate controller api::v1::yubins search
      create  app/controllers/api/v1/yubins2_controller.rb
       route  namespace :api do
  namespace :v1 do
    get 'yubins/search'
  end
end
      invoke  erb
      create    app/views/api/v1/yubins
      create    app/views/api/v1/yubins/search.html.erb
      invoke  helper
      create    app/helpers/api/v1/yubins_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/api/v1/yubins.coffee
      invoke    scss
      create      app/assets/stylesheets/api/v1/yubins.scss
```

コントローラには郵便番号を検索するための`search`メソッドを用意します。

```rb:app/controllers/api/v1/yubin_controller.rb
class Api::V1::YubinController < ApplicationController
  def search
    @yubins = Yubin.where(zipcode: params[:id])
    @message = "該当の郵便番号は見つかりませんでした。" if @yubins.blank?
  end
end
```

viewsにはAPIで返却するjsonを生成する処理を記載します。

```rb:app/views/api/v1/yubin/search.json.jbuilder
json.set! :results do
  json.array! @yubins do |yubin|
    json.zipcode yubin.zipcode
    json.region yubin.region
    json.locality yubin.locality
    json.street_address yubin.street_address
  end
end
json.car_count @yubins.length
json.message @message ||= ''
json.status 200
```

現時点では、この情報を返却するようにしました。
郵便番号からヒットする全ての郵便番号情報を返却します。

|Key|Volue|
|---|---
|count|ヒットした件数|
|message|エラーがある時に、`該当の郵便番号は見つかりませんでした。`を表示|
|results|郵便番号一覧|
|status|200 (固定)|

３レコードで１つの郵便番号を表現しているレコードを呼び出してみます。

```sh:console
$ http http://0.0.0.0:3000/api/v1/yubins/0660005
HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json; charset=utf-8
ETag: W/"40bf1b12dfa9886570852ac408753acd"
Referrer-Policy: strict-origin-when-cross-origin
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Request-Id: 6ded3dfa-fdbc-4e92-99ec-1dd5779dd4a1
X-Runtime: 0.042539
X-XSS-Protection: 1; mode=block

{
    "count": 3,
    "message": "",
    "results": [
        {
            "locality": "千歳市",
            "region": "北海道",
            "street_address": "協和（８８－２、２７１－１０、３４３－２、４０４－１、４２７－",
            "zipcode": "0660005"
        },
        {
            "locality": "千歳市",
            "region": "北海道",
            "street_address": "３、４３１－１２、４４３－６、６０８－２、６４１－８、８１４、８４２－",
            "zipcode": "0660005"
        },
        {
            "locality": "千歳市",
            "region": "北海道",
            "street_address": "５、１１３７－３、１３９２、１６５７、１７５２番地）",
            "zipcode": "0660005"
        }
    ],
    "status": 200
}
```

存在しない郵便番号の場合

```sh:console
$ http http://0.0.0.0:3000/api/v1/yubins/9999999                                   
HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json; charset=utf-8
ETag: W/"28937b496d793376719c55fe009f52d9"
Referrer-Policy: strict-origin-when-cross-origin
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Request-Id: 12dcc5fb-66be-489d-afa1-5c76f167af6d
X-Runtime: 0.012420
X-XSS-Protection: 1; mode=block

{
    "count": 0,
    "message": "該当の郵便番号は見つかりませんでした。",
    "results": [],
    "status": 400
}
```

郵便番号が１つでも取得できれば`status`は200を返却し。
郵便番号が１つも取得できなければ`status`は400を返却するとします。

#### おわりに、
ここまでで、Railsアプリプロジェクトを構築し、郵便局が提供している郵便番号一覧を取り込み、郵便番号から住所を返却するAPIを構築することができました。自社で郵便番号を構築するために必要な作業手順は一旦完了です。

また、郵便番号一覧の情報について何も加工しない状態のデータを返却するAPIのバージョンを`v1`とします。
次回は、郵便番号一覧のデータ補正を行いつつ郵便番号APIの品質を高めて`V2`を作成していきます。
## 参考

[KEN_ALL.CSV (郵便番号検索)の落とし穴 - Qiita](https://qiita.com/nanasess/items/0f0aeaa1f72f599b9142)
[郵便番号や市区町村データを取り扱うときにはまったこと - Qiita](https://qiita.com/_takwat/items/3a121656425fac7bb820)
[Rails4の郵便番号/住所変換のマスターデータ作成手順 - Qiita](https://qiita.com/inodev/items/8a9230a3f245967f6e22)
[日本郵便が公開する郵便番号データをそのまま利用するのがなぜ難しいか。そして、住所から郵便番号を求めるのがなぜ難しいか](https://www.publickey1.jp/blog/17/yubin7_pr.html)
[郵便番号データは自分で加工しない - daily dayflower](http://d.hatena.ne.jp/dayflower/20100929/1285744153)