Ruby on Rails で郵便番号検索
よく住所を入力するフォームで、郵便番号から住所を自動入力する機能がありますよね。そんなのが必要になったのですが、ググって見つけたサンプルにしっくりくるものがなかったので、自分でちまちま作ってみました。コントローラやビューの部分は公開できる状態ではないのですが、モデルやインポート用の Rake タスクは汎用的に作れたので、本日はそれをご紹介します。
主な特徴は以下のとおりです。
- 複数回出現する郵便番号は最初のものだけをインポート。
- 「以下に掲載がない場合」とか「(1丁目〜3丁目)」などという 邪魔なテキストは削除してインポート。
- "1460082", "146-0082" などの表記の揺れを吸収するファインダ。
- インポート時には ActiveRecord を使わずに直接SQLを発行しているので、比較的高速・省メモリ。
- 郵便番号を数値で保存するので、きっと検索が速いと信じたい(^^;
けっこうよく使う処理だと思うので、よろしければご活用ください!
ソースコードとインストール方法
早速ですが、以下が rake タスクのソースコードです。いつもどおりcodeなにがしに投稿してあります。
インストール方法というほどのこともありませんが、 "RAILS_ROOT/lib/task" の中に "zip.rake" などのファイル名で保存すれば OK です。
データのインポート方法
それでは郵便番号データをインポートしてみましょう。なお、 Rails アプリケーションはすでに作成されていて、データベースなどが使える状態になっていることを前提にします。
テーブルとモデルを作成する
まずはデータを格納するテーブルがないとどうにもならないので、 migration を使って作成します。ついでにモデルも追加してしまいましょう。 RAILS_ROOT で以下のコマンドを実行してください。
./script/generate model Zip
これで "RAILS_ROOT/lib/???_create_zips.rb" に migration スクリプトが生成されます(??? はバージョン番号)。そのファイルをテキストエディタで開き、以下のように書き換えてください。
class CreateZips < ActiveRecord::Migration def self.up create_table :zips, :primary_key => 'zip' do |t| t.integer :prefecture t.string :city t.string :street end end def self.down drop_table :zips end end
あとは、 RAILS_ROOT で以下のコマンドを実行すれば、テーブルが作成されます。
rake db:migrate
スキーマは以下のようになっています。
カラム名 | データ型 | 内容 |
---|---|---|
zip | integer | 郵便番号 |
prefecture | integer | JIS X 0401 の県コード |
city | string | 漢字の市区町村名 |
street | string | 漢字の町域名 |
自分が使うデータしか入れてません。すいません。
郵便番号データをダウンロード
元となる郵便番号データは、日本郵政の以下のページからダウンロードできます。
http://www.post.japanpost.jp/zipcode/dl/kogaki.html
県別のデータではなく全国一括のデータをダウンロードしてください。そして、なんとこの時代に LHA 圧縮なので(たしかに LHA は偉大ですけどね!)、なんとかして展開してください。 Windows なら Lhaplus とかでいけるはずです。そういえば、Microsoft 純正のやつもありましたね(笑)。
まあ、とにかく展開すると "KEN_ALL.CSV" という名前のファイルができるはずなので、それを "RAILS_ROOT/db/KEN_ALL.CSV" としてコピーしてください。これで準備完了です。
インポートを実行
あとは Rake タスクを実行するだけです。 RAILS_ROOT で以下のコマンドを実行してください。
rake zip
ちょっと時間がかかるので、気長に待ってください。エラーが出ずに実行が終了すれば、データがインポートされています。 migration の作成時にモデルも追加済みなので、 Rails の console で以下のように検索することができます。
$ ./script/console Loading development environment (Rails 2.0.2) >> Zip.find('1460082') => #<Zip zip: 1460082, prefecture: 13, city: "大田区", street: "池上">
あとは、皆さんのアプリケーションでご自由にご利用くださいませ。
表記の揺れに対応する
このままでも使えますが、郵便番号はハイフンなどを削除して完全に数字だけにしないとヒットしません。これではちょっと使いづらいので、この処理を自動で行うモデルを作ってみました。 Zip.find と Zip.find_by_zip をオーバーライドして、数字以外の文字を削除してから検索するようにしています。
class Zip < ActiveRecord::Base set_primary_key :zip instance_eval do alias :super_method_missing :method_missing alias :super_find :find def find(id, *args) unless Symbol === id && (id == :all || id == :first || id == :last) id = id.to_s.gsub(/[^0-9]/u, '') end super_find(id, *args) end def method_missing(name, *args) if name.to_s == 'find_by_zip' args[0] = args[0].to_s.gsub(/[^0-9]/u, '') result = super_method_missing(name, *args) instance_eval do alias :super_find_by_zip :find_by_zip def find_by_zip(id) super_find_by_zip(id.to_s.gsub(/[^0-9]/u, '')) end end result else super_method_missing(name, *args) end end end end
ファインダのオーバーライドがけっこう面倒で、かなり泥臭いことになっています。もうちょっと簡単な方法とかってないんですかね?
まあ、それはいいとして、このモデルを使えば以下のように表記の揺れに対応できます。
$ ./script/console Loading development environment (Rails 2.0.2) >> Zip.find('146-0082') => #<Zip zip: 1460082, prefecture: 13, city: "大田区", street: "池上"> >> Zip.find('146 0082') => #<Zip zip: 1460082, prefecture: 13, city: "大田区", street: "池上">
注意点
もともと自分で使うためだけに作ったものなので、かなり自分本位な仕様になっています。とくに以下の点にはご注意ください。
- インポートしているのは郵便番号、県コード、市町村名、町域名のみで、その他のデータは無視しています。
- 稀にひとつの郵便番号が複数の地域に割り振られているものがあります。このような場合、最初に出てきた地域のみをインポートして、後続のものは無視しています。
- 郵便番号データのテーブルのプライマリキーは "zip" という名前になっています。モデルを自作する際は注意してください。
- 県データ(prefecture カラム)は JIS X 0401 の県コードで保存されています。 JIS X 0401 については、 Wikipediaなどを参照してください。
- すべてのデータをチェックしたわけではないので、もしデータが崩れているものがありましたら、ぜひ教えてください(^^;
以上、本日は Rails に郵便番号検索のデータをインポートする Rake タスクと、それを扱うモデルをご紹介しました。最初は単に CSV をインポートすればいいと思っていたのですが、元データがかなりいけてなくて、なかなか大変でした。これって日本全国でけっこうな労力の浪費が起きてるんじゃないですかね。ゴミデータを含まない XML 形式の郵便番号データを切に望みます>日本郵政さん
詳しくはこちらの記事をどうぞ!
この記事にコメントする