jp_address 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/MIT-LICENSE +0 -0
  4. data/README.md +81 -96
  5. data/Rakefile +14 -15
  6. data/app/controllers/jp_address/application_controller.rb +1 -0
  7. data/app/controllers/jp_address/zipcodes_controller.rb +9 -4
  8. data/app/models/jp_address/application_record.rb +5 -0
  9. data/app/models/jp_address/zipcode.rb +139 -75
  10. data/config/routes.rb +0 -0
  11. data/db/migrate/20160312045953_create_jp_address_zipcodes.rb +1 -1
  12. data/lib/jp_address/engine.rb +0 -0
  13. data/lib/jp_address/version.rb +1 -1
  14. data/lib/jp_address.rb +3 -1
  15. data/lib/tasks/jp_address_tasks.rake +0 -0
  16. metadata +48 -157
  17. data/app/assets/javascripts/jp_address/application.js +0 -13
  18. data/app/assets/stylesheets/jp_address/application.css +0 -15
  19. data/app/helpers/jp_address/application_helper.rb +0 -4
  20. data/app/views/layouts/jp_address/application.html.erb +0 -14
  21. data/spec/controllers/jp_address/zipcodes_controller_spec.rb +0 -25
  22. data/spec/dummy/README.rdoc +0 -28
  23. data/spec/dummy/Rakefile +0 -6
  24. data/spec/dummy/app/assets/config/manifest.js +0 -2
  25. data/spec/dummy/app/assets/javascripts/application.js +0 -13
  26. data/spec/dummy/app/assets/stylesheets/application.css +0 -15
  27. data/spec/dummy/app/controllers/application_controller.rb +0 -5
  28. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  29. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  30. data/spec/dummy/bin/bundle +0 -3
  31. data/spec/dummy/bin/rails +0 -4
  32. data/spec/dummy/bin/rake +0 -4
  33. data/spec/dummy/bin/setup +0 -29
  34. data/spec/dummy/config/application.rb +0 -31
  35. data/spec/dummy/config/boot.rb +0 -5
  36. data/spec/dummy/config/database.yml +0 -25
  37. data/spec/dummy/config/environment.rb +0 -5
  38. data/spec/dummy/config/environments/development.rb +0 -41
  39. data/spec/dummy/config/environments/production.rb +0 -79
  40. data/spec/dummy/config/environments/test.rb +0 -42
  41. data/spec/dummy/config/initializers/assets.rb +0 -11
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  43. data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
  44. data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  45. data/spec/dummy/config/initializers/inflections.rb +0 -16
  46. data/spec/dummy/config/initializers/mime_types.rb +0 -4
  47. data/spec/dummy/config/initializers/session_store.rb +0 -3
  48. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  49. data/spec/dummy/config/locales/en.yml +0 -23
  50. data/spec/dummy/config/routes.rb +0 -3
  51. data/spec/dummy/config/secrets.yml +0 -22
  52. data/spec/dummy/config.ru +0 -4
  53. data/spec/dummy/db/schema.rb +0 -25
  54. data/spec/dummy/db/test.sqlite3 +0 -0
  55. data/spec/dummy/log/test.log +0 -35
  56. data/spec/dummy/public/404.html +0 -67
  57. data/spec/dummy/public/422.html +0 -67
  58. data/spec/dummy/public/500.html +0 -66
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/factories/jp_address_zipcodes.rb +0 -10
  61. data/spec/models/jp_address/zipcode_spec.rb +0 -64
  62. data/spec/rails_helper.rb +0 -15
  63. data/spec/spec_helper.rb +0 -92
  64. data/spec/support/files/sample_ken.csv +0 -5
  65. data/spec/vcr/download_master_file_from_japanpost.yml +0 -37476
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71e8a270d6771b635d0de517833ad5b21c97558800c2ef459c2493cdc68f8ec9
4
- data.tar.gz: 960f5bc5a54d66f929ab141cee756099b71a9f3a66f64a2ea757ce4e772ae84b
3
+ metadata.gz: 67701127b1fbec6ae9292647b6373acdeca9dcb51b78addeefaeed7544852531
4
+ data.tar.gz: d3fb7f7ee44c1976825cb22c0394414a9dec14aa220536e17bf57daa382a7575
5
5
  SHA512:
6
- metadata.gz: c1ab63af145ca2654ea5383bf459eea3465678013a32589304c46daea4e9f69e99c03ab9bcd4f7db4a43dea9ebcaa00bc5b3c6ffc9fd0591bf5285fee1dae664
7
- data.tar.gz: a87ecc0525afaa28ed2c75caaacdae8ff98191b3118d7d990b5066b1eb95c677e40f1addc0ce3fa47626fdaa6dd26ede79bbb2d3b5d4590f59a5f6da67483682
6
+ metadata.gz: 33814d00bd9491bce91816e44066be23025af90b8c68ff62a3924fe6ed1316d5f08339864642a3848f55a7cff23838712816ec5747edabe633e8ace33c91a8fa
7
+ data.tar.gz: becaad6991cc0cc30d8dc05f479488500b02707c3028c6b8841af2ed3e35a2042e01c5332a15ce20caa9962deb4231b629f9f83503f4e09a0bd0b9b7e1a36c74
data/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # CHANGELOG
2
+
3
+ ## 2.0.0
4
+
5
+ 最新の Ruby / Rails への対応と、郵便番号データの取り込みまわりのバグ修正。
6
+
7
+ ### 対応バージョン
8
+
9
+ * Ruby 3.1 〜 3.4、Rails 7.1 / 8.0 / 8.1 に対応。
10
+ (Ruby 3.1 + Rails 7.1 〜 Ruby 3.4 + Rails 8.1 で実際にテスト済み)
11
+ * Rails 6.x 以前のサポートを終了。
12
+ * Ruby 3.4 で `csv` が bundled gem になったため、gemspec の依存に追加。
13
+ これがないと `require 'csv'` に失敗する。
14
+ * `railties` / `activerecord` を runtime 依存として明示。
15
+ * rubyzip 3.x に対応(`>= 2.3, < 4.0`)。
16
+
17
+ ### バグ修正
18
+
19
+ * **町域名が複数行に分かれている郵便番号で町名が消えていた**。
20
+ ken_all.csv は町域名が長いと括弧の途中で改行して次の行に続くが、その
21
+ 継続行を別の町名として扱っていたため、共通部分が見つからず町名が空に
22
+ なっていた。括弧が閉じるまでの行を読み飛ばすようにした。
23
+ 例:9292225(石川県羽咋郡宝達志水町 米出)が `""` → `"米出"`。
24
+ * **同じ郵便番号の行が全部同じ町名でも町名が空になっていた**。
25
+ `_find_shared_name_from` が「差分が見つからない=共通部分なし」と
26
+ 判定していたのを修正。短い方の地名がまるごと共通部分になる場合
27
+ (例:東中島/東中島本町)もその地名を返すようにした。
28
+ * 日本郵便へのリクエストがリダイレクト・エラーレスポンスを一切見ていな
29
+ かったのを修正。リダイレクトは最大 5 回まで追い、200 以外は
30
+ `JpAddress::Zipcode::DownloadError` を投げる。
31
+ * zip の中から CSV エントリだけを取り出すようにした(従来は全エントリを
32
+ 同じパスに展開しようとしていた)。
33
+ * 存在しない CSV パスを `load_master_data` に渡すと、無駄にダウンロード
34
+ してから `Errno::ENOENT` になっていたのを `ArgumentError` に。
35
+ * `row[8]` が空の行で `NoMethodError` にならないようにした。
36
+ * `FileUtils` を require していなかったのを修正。
37
+
38
+ ### 変更
39
+
40
+ * `load_master_data` を全面的に書き直し。行ごとの生 SQL を `insert_all!`
41
+ でのバッチ投入に変え、統合処理も CSV 読み込み中にメモリ上で行うように
42
+ したので、大幅に速くなった(本番でのロードが数分 → 数十秒程度)。
43
+ これに伴い内部メソッド `_merge_same_zip_addresses` は削除。
44
+ * API のレスポンスを `render plain:` から `render json:` に変更。
45
+ Content-Type が `text/plain` から `application/json` になる。
46
+ * 郵便番号の全角数字(`533-0033`)を受け付けるようにした。
47
+ * `JpAddress::ApplicationRecord` を追加し、`Zipcode` の親クラスにした。
48
+ * Sprockets 前提の app/assets・レイアウト・空の ApplicationHelper を削除。
49
+ JSON しか返さない engine には不要で、propshaft / importmap 環境では
50
+ むしろ壊れるため。
51
+ * テストを VCR カセット(2.5MB のバイナリ)から WebMock のスタブに置き換え。
52
+ コントローラスペックはリクエストスペックに変更。
53
+ * GitHub Actions で Ruby × Rails のマトリクステストを追加。
54
+
55
+ ## 1.0.2 以前
56
+
57
+ 省略。
data/MIT-LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Ruby on Rails 6 で 郵便番号住所検索 な gem
1
+ # Ruby on Rails で 郵便番号住所検索 な gem
2
2
 
3
3
  ## JpAddressとは
4
- 日本郵便の「[郵便番号データ](https://www.post.japanpost.jp/zipcode/dl/oogaki-zip.html)」を用いて、あなたの Rails 6.1 サイトに「郵便番号からの住所検索機能」を組み込むための gem です。
4
+ 日本郵便の「[郵便番号データ](https://www.post.japanpost.jp/zipcode/download.html)」を用いて、あなたの Rails サイトに「郵便番号からの住所検索機能」を組み込むための gem です。
5
5
  以下の機能を提供します。
6
6
 
7
- * [[郵便番号データ](https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip "ken_all.zip")]をダウンロードして自前DBのテーブル(jp_address_zipcodes)にロードするクラスメソッド。(```JpAddress::Zipcode.load_master_data```)
7
+ * [[郵便番号データ](https://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip "ken_all.zip")]をダウンロードして自前DBのテーブル(jp_address_zipcodes)にロードするクラスメソッド。(```JpAddress::Zipcode.load_master_data```)
8
8
  * 郵便番号を受け取り都道府県名と住所をJSONで返却するAPI。
9
9
  (```jp_address/zipcodes#search```)
10
10
 
@@ -14,8 +14,17 @@ APIはお使いのRailsアプリケーションにマウントして使います
14
14
  あと必要なのは、戻ってくるJSONを加工してHTML要素にセットするJavaScriptの記述だけです。<br>
15
15
  (本記事下部にサンプルコードを掲載しています。)
16
16
 
17
+ ### 対応バージョン
18
+
19
+ | | 対応 |
20
+ |---|---|
21
+ | Ruby | 3.1 以降 |
22
+ | Rails | 7.1 / 8.0 / 8.1 |
23
+
24
+ Rails 6.x 以前をお使いの場合は 1.0.2 をご利用ください。
25
+
17
26
  ### インストール
18
- GemFileに追記
27
+ Gemfileに追記
19
28
  ```ruby
20
29
  gem 'jp_address'
21
30
  ```
@@ -36,11 +45,15 @@ $ bundle exec rails runner -e development 'JpAddress::Zipcode.load_master_data'
36
45
  $ bundle exec rails runner -e production 'JpAddress::Zipcode.load_master_data'
37
46
  ```
38
47
 
39
- 環境にもよりますが、5分ぐらいかかると思います。
40
-
41
48
  APP_ROOT/tmp/ を作業ディレクトリに使用しています。<br>
42
49
  最初にテーブルをトランケートしますので、毎回「全件insert」になります。<br>
43
50
 
51
+ すでに手元に ken_all.csv がある場合は、パスを渡せばダウンロードを省略できます。<br>
52
+ (文字コードは日本郵便の配布どおり CP932 のままで構いません。)
53
+ ```ruby
54
+ JpAddress::Zipcode.load_master_data('tmp/ken_all.csv')
55
+ ```
56
+
44
57
  同じ郵便番号を持つレコードは統合されます。<br>
45
58
  <br>
46
59
  例:9896712<br>
@@ -78,6 +91,14 @@ http://localhost:3000/jp_address/zipcodes/search?zip=5330033
78
91
  {"id":84280,"zip":"5330033","prefecture":"大阪府","city":"大阪市東淀川区","town":"東中島"}
79
92
  ```
80
93
 
94
+ 該当する郵便番号がない場合は、各項目が null の JSON が返ります。
95
+ ```js script
96
+ {"id":null,"zip":null,"prefecture":null,"city":null,"town":null}
97
+ ```
98
+
99
+ 半角ハイフン・空白・全角数字は自動的に取り除かれるので、`533-0033` や `533-0033` を
100
+ そのまま投げても構いません。
101
+
81
102
  ### APIを利用するサンプル JavaScript
82
103
  フォームに
83
104
  1. #zipcode (郵便番号を入力するテキストボックス)
@@ -85,8 +106,7 @@ http://localhost:3000/jp_address/zipcodes/search?zip=5330033
85
106
  3. #address (住所を表示するテキストボックス)
86
107
 
87
108
  の3要素があるとします。<br>
88
- #zipcodeに入れられた値を keyup イベントで拾ってAPIを叩き、都道府県プルダウンを選択し、住所をセットするサンプルです。<br>
89
- 郵便番号の半角ハイフンは自動でカットされます。
109
+ #zipcodeに入れられた値を input イベントで拾ってAPIを叩き、都道府県プルダウンを選択し、住所をセットするサンプルです。<br>
90
110
 
91
111
  都道府県プルダウンは、戻ってくるJSONの "prefecture" すなわち都道府県名で選択します。<br>
92
112
  ですので、お持ちの都道府県マスターの各レコードがどのようなIDを持っていても構いません。
@@ -148,108 +168,73 @@ http://localhost:3000/jp_address/zipcodes/search?zip=5330033
148
168
  </form>
149
169
  ```
150
170
 
151
- #### application.js など共通に読み込まれるファイルに配置するJavaScript
152
- JQuery の存在を前提にしています。<br>
153
- ※ もともと CoffeeScript で書いてあったソースを decaffeinate したものですので冗長です(汗)。<br>
154
- 本質的な処理はAddressSearch 関数が担っているだけで、他の関数は decaffeinate に必要なだけです。
171
+ #### JavaScript
172
+ jQuery などのライブラリは不要です。app/javascript 配下など、フォームのあるページで
173
+ 読み込まれる場所に置いてください。
174
+
155
175
  ```js script
156
- function _classCallCheck(instance, Constructor) {
157
- if (!(instance instanceof Constructor)) {
158
- throw new TypeError("Cannot call a class as a function");
159
- }
176
+ class AddressSearch {
177
+ constructor(zipSelector, prefectureSelector, addressSelector, endpoint = '/jp_address/zipcodes/search') {
178
+ this.zip = document.querySelector(zipSelector);
179
+ this.prefecture = document.querySelector(prefectureSelector);
180
+ this.address = document.querySelector(addressSelector);
181
+ this.endpoint = endpoint;
182
+ }
183
+
184
+ start() {
185
+ this.zip.addEventListener('input', () => this.execute());
160
186
  }
161
187
 
162
- function _defineProperties(target, props) {
163
- for (var i = 0; i < props.length; i++) {
164
- var descriptor = props[i];
165
- descriptor.enumerable = descriptor.enumerable || false;
166
- descriptor.configurable = true;
188
+ async execute() {
189
+ const zip = this.zip.value.replace(/[^0-90-9]/g, '');
190
+ if (zip.length !== 7) return;
167
191
 
168
- if ("value" in descriptor)
169
- descriptor.writable = true;
170
- Object.defineProperty(target, descriptor.key, descriptor);
192
+ const url = `${this.endpoint}?zip=${encodeURIComponent(zip)}`;
193
+ const res = await fetch(url, { headers: { Accept: 'application/json' } });
194
+ if (!res.ok) return;
195
+
196
+ const json = await res.json();
197
+ if (json.id === null) {
198
+ this.clear();
199
+ } else {
200
+ this.setPrefecture(json.prefecture);
201
+ this.address.value = `${json.city}${json.town}`;
171
202
  }
172
203
  }
173
204
 
174
- function _createClass(Constructor, protoProps, staticProps) {
175
- if (protoProps)
176
- _defineProperties(Constructor.prototype, protoProps);
177
- if (staticProps)
178
- _defineProperties(Constructor, staticProps);
179
- return Constructor;
205
+ clear() {
206
+ this.prefecture.selectedIndex = 0;
207
+ this.address.value = '';
180
208
  }
181
209
 
182
- var AddressSearch = function() {
183
- "use strict";
184
- function AddressSearch(zip_elem_id, prefecture_elem_id, address_elem_id) {
185
- _classCallCheck(this, AddressSearch);
186
- this.zip = $(zip_elem_id);
187
- this.prefecture = $(prefecture_elem_id);
188
- this.address = $(address_elem_id);
189
- this.prefecture_elem_id = prefecture_elem_id;
210
+ setPrefecture(name) {
211
+ for (const option of this.prefecture.options) {
212
+ if (option.text === name) {
213
+ option.selected = true;
214
+ return;
215
+ }
190
216
  }
217
+ }
218
+ }
191
219
 
192
- _createClass(AddressSearch, [{
193
- key: "_remove_hyphen",
194
- value: function _remove_hyphen() {
195
- return this.zip.val(this.zip.val().replace(/-/, ''));
196
- }
197
- }, {
198
- key: "_clear_current_value",
199
- value: function _clear_current_value() {
200
- $(this.prefecture_elem_id + ' >option:eq(0)').prop('selected', true);
201
- return this.address.val('');
202
- }
203
- }, {
204
- key: "_set_prefecture",
205
- value: function _set_prefecture(json) {
206
- return $(this.prefecture_elem_id + ' > option').each(function() {
207
- if ($(this).text() === json['prefecture']) {
208
- return $(this).prop('selected', true);
209
- }
210
- });
211
- }
212
- }, {
213
- key: "_set_address",
214
- value: function _set_address(json) {
215
- return this.address.val(json['city'] + json['town']);
216
- }
217
- }, {
218
- key: "_call_api",
219
- value: function _call_api() {
220
- var _this = this;
221
- return $.getJSON('/jp_address/zipcodes/search', {zip: this.zip.val()}, function(json) {
222
- if (json['id'] === null) {
223
- return _this._clear_current_value();
224
- } else {
225
- _this._set_prefecture(json);
226
- return _this._set_address(json);
227
- }
228
- });
229
- }
230
- }, {
231
- key: "execute",
232
- value: function execute() {
233
- this._remove_hyphen();
234
- if (this.zip.val().length === 7) {
235
- return this._call_api();
236
- }
237
- }
238
- }]);
220
+ // #zipcode, #prefecture_id, #address を各自の環境に合わせて書き換えてください。
221
+ document.addEventListener('DOMContentLoaded', () => {
222
+ new AddressSearch('#zipcode', '#prefecture_id', '#address').start();
223
+ });
224
+ ```
239
225
 
240
- return AddressSearch;
241
- }();
226
+ ### 開発
227
+
228
+ ```
229
+ $ bundle install
230
+ $ bundle exec rspec
242
231
  ```
243
232
 
244
- #### フォームのあるページに配置するJavaScript
245
- ```js script
246
- // #zipcode, #prefecture_id, #address を各自の環境に合わせて書き換えてください。
247
- $(function() {
248
- var address_search = new AddressSearch('#zipcode', '#prefecture_id', '#address');
249
- $('#zipcode').keyup(function() {
250
- address_search.execute();
251
- });
252
- });
233
+ 複数バージョンの Rails で試す場合は gemfiles/ 配下の Gemfile を使ってください。
234
+
235
+ ```
236
+ $ BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle install
237
+ $ BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rspec
253
238
  ```
254
239
 
255
240
  ##### 作者
data/Rakefile CHANGED
@@ -4,23 +4,22 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'JpAddress'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('lib/**/*.rb')
7
+ begin
8
+ require 'rdoc/task'
9
+
10
+ RDoc::Task.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'JpAddress'
13
+ rdoc.options << '--line-numbers'
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+ rescue LoadError
17
+ # rdoc is optional; skip the task when it is not installed.
14
18
  end
15
19
 
16
- APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
20
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
17
21
  load 'rails/tasks/engine.rake'
18
22
 
19
-
20
- load 'rails/tasks/statistics.rake'
21
-
22
-
23
-
24
23
  Bundler::GemHelper.install_tasks
25
24
 
26
25
  require 'rspec/core'
@@ -29,5 +28,5 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
29
28
  spec.pattern = FileList['spec/**/*_spec.rb']
30
29
  end
31
30
 
32
- task :default => :spec
33
- task :test => :spec
31
+ task default: :spec
32
+ task test: :spec
@@ -1,4 +1,5 @@
1
1
  module JpAddress
2
2
  class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :null_session
3
4
  end
4
5
  end
@@ -1,10 +1,15 @@
1
- require_dependency "jp_address/application_controller"
2
-
3
1
  module JpAddress
4
2
  class ZipcodesController < ApplicationController
5
3
  def search
6
- @zipcode = Zipcode.find_by(:zip => params[:zip].to_s.gsub(/[^0-9]/, '')) || Zipcode.new
7
- render plain: @zipcode.to_json
4
+ @zipcode = Zipcode.find_by(zip: _normalized_zip) || Zipcode.new
5
+ render json: @zipcode
6
+ end
7
+
8
+ private
9
+
10
+ # 全角数字・ハイフン・空白などを取り除いて半角数字だけにします。
11
+ def _normalized_zip
12
+ params[:zip].to_s.tr('0-9', '0-9').gsub(/[^0-9]/, '')
8
13
  end
9
14
  end
10
15
  end
@@ -0,0 +1,5 @@
1
+ module JpAddress
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end