jp_address_complement 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -15
- data/README.md +21 -7
- data/examples/rails/jp_address_complement_demo/Gemfile.lock +1 -3
- data/lib/jp_address_complement/importers/csv_importer.rb +24 -25
- data/lib/jp_address_complement/ken_all.rb +24 -0
- data/lib/jp_address_complement/ken_all_downloader.rb +24 -10
- data/lib/jp_address_complement/normalizer.rb +2 -2
- data/lib/jp_address_complement/railtie.rb +2 -0
- data/lib/jp_address_complement/repositories/active_record_postal_code_repository.rb +3 -3
- data/lib/jp_address_complement/repositories/csv_postal_code_repository.rb +2 -12
- data/lib/jp_address_complement/searcher.rb +1 -0
- data/lib/jp_address_complement/validators/address_validator.rb +2 -1
- data/lib/jp_address_complement/version.rb +1 -1
- data/lib/jp_address_complement.rb +22 -4
- data/sig/generated/jp_address_complement/importers/csv_importer.rbs +12 -21
- data/sig/generated/jp_address_complement/ken_all.rbs +33 -0
- data/sig/generated/jp_address_complement/ken_all_downloader.rbs +5 -0
- data/sig/generated/jp_address_complement/repositories/csv_postal_code_repository.rbs +1 -22
- data/sig/generated/jp_address_complement.rbs +2 -1
- metadata +25 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ac0fd79d7ab6a6cfead9398aa3907ccfcbfe6766e59a3653b768035f057e5ce
|
|
4
|
+
data.tar.gz: be6413cd0b12304bf00259f3a8bbacf68051bdaa6f7d74eaf54718550c4f0a9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ab89ad98dc9510ba4a9524dc4683211ecb39562b8a91b669164c4e2c7da82d515ebaa9a9a780fd5c385ca8c228a1ac0b0673e21678deeea9b9e3f6271ce836a
|
|
7
|
+
data.tar.gz: bc901f201de85ffd2aedca0613c07dd988f478927ff91f2220e9195f5587fba695a78ea8c16ca25971e64c3186dca69ba5ca15f70776fb0de3ba4333ca9a84f9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## [0.3.0] - 2026-03-02
|
|
4
|
+
|
|
5
|
+
### Security
|
|
4
6
|
|
|
5
|
-
- **
|
|
6
|
-
- `JpAddressComplement::Importers::CsvImporter#import` の戻り値を **Integer から `ImportResult`(Data.define :upserted, :deleted)に変更**(破壊的変更)
|
|
7
|
-
- フルインポート時に、今回のCSVに存在しない住所レコードをストアから削除するようにした
|
|
8
|
-
- 空CSV(有効行0件)の場合はインポートを拒否し、`JpAddressComplement::ImportError` を発生させる
|
|
9
|
-
- Rake タスク `jp_address_complement:import` の完了メッセージを「インポート完了: upsert N 件, 削除 M 件」形式に変更
|
|
7
|
+
- **Zip Slip 脆弱性の修正**: 解凍先パスの検証を強化し、Zip Slip 攻撃を防止。あわせて `Dir.chdir` によるスレッド安全性の問題を解消
|
|
10
8
|
|
|
11
9
|
### Added
|
|
12
10
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
- 町域なしの住所も `valid_combination?` で一致とみなすように変更(テスト追加)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **ActiveSupport 依存の廃止**: `blank?` をネイティブ Ruby に置換
|
|
16
|
+
- **upsert_batch の改善**: N+1 DELETE を Arel バッチ削除に変更し、SQLite の式木深度上限エラーに対応(`DELETE_CHUNK_SIZE` 導入)
|
|
17
|
+
- **COL_* 定数の整理**: KenAll モジュールに集約し重複を解消
|
|
18
|
+
- README: `search_postal_codes_by_address` の戻り値を正しく記載
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- RuboCop 違反の修正(Metrics/AbcSize 含む)および RBS シグネチャの更新
|
|
23
|
+
|
|
24
|
+
## [0.2.0] - 2026-03-02
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
17
27
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
28
|
+
- **Active Record をオプショナルに変更**
|
|
29
|
+
- gemspec: `activerecord` / `railties` を runtime 依存から削除し、development 依存に移動
|
|
30
|
+
- デフォルトリポジトリ・`base_record_class`: Gemfile のみで未 require の場合に備え、require を試してから利用するよう変更(`defined?` 判定を廃止)
|
|
31
|
+
- Railtie: 同様に `require 'rails'` を試してから読み込むよう変更
|
|
32
|
+
- `default_repository`: LoadError 時に利用者向けメッセージで `JpAddressComplement::Error` を発生
|
|
33
|
+
- README: 必要環境・インストール手順に利用者側の対応を追記
|
|
23
34
|
|
|
24
35
|
## [0.1.0] - 2026-02-22
|
|
25
36
|
|
data/README.md
CHANGED
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
## 必要環境
|
|
27
27
|
|
|
28
28
|
- Ruby 3.2 以上
|
|
29
|
-
- Rails 7.
|
|
30
|
-
- ActiveRecord 利用を前提としています
|
|
29
|
+
- **デフォルトの DB 利用(ActiveRecord)を使う場合のみ**: Rails 7.0 以上および `activerecord` gem(Rails アプリでは通常含まれる)。使わない場合は不要です。
|
|
31
30
|
|
|
32
31
|
## インストール
|
|
33
32
|
|
|
@@ -39,13 +38,24 @@
|
|
|
39
38
|
gem 'jp_address_complement'
|
|
40
39
|
```
|
|
41
40
|
|
|
41
|
+
**デフォルトの ActiveRecord リポジトリを使う場合(DB に郵便番号テーブルを置く場合)**
|
|
42
|
+
本 gem は `activerecord` を必須依存にしていません。Rails アプリでは `gem 'rails'` に含まれるため追加不要です。Rails を使わずに ActiveRecord だけ使う場合は、利用者側で `Gemfile` に `activerecord` を追加してください。
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem 'jp_address_complement'
|
|
46
|
+
gem 'activerecord', '>= 7.0' # デフォルトリポジトリを使う場合のみ
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**CSV リポジトリや自前リポジトリだけ使う場合**
|
|
50
|
+
`JpAddressComplement.configure { |c| c.repository = ... }` でリポジトリを設定すれば、`activerecord` は不要です。gem の追加だけで利用できます。
|
|
51
|
+
|
|
42
52
|
Bundler を使わない場合:
|
|
43
53
|
|
|
44
54
|
```bash
|
|
45
55
|
gem install jp_address_complement
|
|
46
56
|
```
|
|
47
57
|
|
|
48
|
-
### 2.
|
|
58
|
+
### 2. テーブルの作成(デフォルトの ActiveRecord リポジトリを使う場合)
|
|
49
59
|
|
|
50
60
|
郵便番号データを格納するマイグレーションを生成し、マイグレートします。
|
|
51
61
|
|
|
@@ -54,7 +64,7 @@ rails g jp_address_complement:install
|
|
|
54
64
|
rails db:migrate
|
|
55
65
|
```
|
|
56
66
|
|
|
57
|
-
### 3.
|
|
67
|
+
### 3. 住所データのインポート(デフォルトの ActiveRecord リポジトリを使う場合)
|
|
58
68
|
|
|
59
69
|
日本郵便の KEN_ALL 形式 CSV(UTF-8 版 `utf_ken_all.csv`)をインポートする必要があります。
|
|
60
70
|
|
|
@@ -125,7 +135,7 @@ records = JpAddressComplement.search_by_postal_code_prefix('1000')
|
|
|
125
135
|
|
|
126
136
|
### 郵便番号と住所の整合性チェック
|
|
127
137
|
|
|
128
|
-
|
|
138
|
+
「この郵便番号とこの住所の組み合わせは正しいか?」を判定します。フォームのバリデーションに利用できます。町域まで含む場合は町域まで、市区町村までしか含まれていない場合は市区町村までのチェックとなります。
|
|
129
139
|
|
|
130
140
|
```ruby
|
|
131
141
|
JpAddressComplement.valid_combination?('1000001', '東京都千代田区千代田')
|
|
@@ -177,12 +187,16 @@ JpAddressComplement.prefecture_code_from_name('東京都')
|
|
|
177
187
|
都道府県・市区町村・町域を指定して、対応する郵便番号の一覧を取得します。
|
|
178
188
|
|
|
179
189
|
```ruby
|
|
180
|
-
|
|
190
|
+
results = JpAddressComplement.search_postal_codes_by_address(
|
|
181
191
|
pref: '東京都',
|
|
182
192
|
city: '千代田区',
|
|
183
193
|
town: '千代田'
|
|
184
194
|
)
|
|
185
|
-
# => ["1000001"]
|
|
195
|
+
# => [["1000001", #<JpAddressComplement::AddressRecord postal_code="1000001", pref="東京都", city="千代田区", town="千代田", ...>]]
|
|
196
|
+
|
|
197
|
+
# 郵便番号のみが必要な場合
|
|
198
|
+
results.map(&:first)
|
|
199
|
+
# => ["1000001"]
|
|
186
200
|
```
|
|
187
201
|
|
|
188
202
|
`town` は省略可能です。その場合は都道府県+市区町村で検索されます。
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require 'csv'
|
|
5
5
|
require_relative '../address_record'
|
|
6
6
|
require_relative '../models/postal_code'
|
|
7
|
+
require_relative '../ken_all'
|
|
7
8
|
|
|
8
9
|
module JpAddressComplement
|
|
9
10
|
module Importers
|
|
@@ -15,20 +16,16 @@ module JpAddressComplement
|
|
|
15
16
|
# UTF-8 版 KEN_ALL(utf_ken_all.csv)を読み込み、jp_address_complement_postal_codes テーブルに upsert する
|
|
16
17
|
# UTF-8 形式の CSV を前提として処理する
|
|
17
18
|
class CsvImporter
|
|
18
|
-
|
|
19
|
+
include KenAll
|
|
20
|
+
|
|
21
|
+
# バッチ削除・ユニークキーとして使うカラム群
|
|
22
|
+
KEY_COLUMNS = %i[postal_code pref_code city town kana_pref kana_city kana_town].freeze #: Array[Symbol]
|
|
19
23
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
COL_KANA_TOWN = 5 #: Integer
|
|
26
|
-
COL_PREF = 6 #: Integer
|
|
27
|
-
COL_CITY = 7 #: Integer
|
|
28
|
-
COL_TOWN = 8 #: Integer
|
|
29
|
-
COL_IS_PARTIAL = 9 #: Integer
|
|
30
|
-
COL_HAS_ALIAS = 12 #: Integer
|
|
31
|
-
COL_IS_LARGE_OFFICE = 13 #: Integer
|
|
24
|
+
# SQLite のデフォルト式木深度制限(1000)対策。
|
|
25
|
+
# reduce(:or) で N 件 OR 結合すると深さ ≈ N + 6 になるため、500 件に収める(深さ ≈ 506)。
|
|
26
|
+
DELETE_CHUNK_SIZE = 500 #: Integer
|
|
27
|
+
|
|
28
|
+
BATCH_SIZE = 1000 #: Integer
|
|
32
29
|
|
|
33
30
|
# @rbs (String csv_path) -> void
|
|
34
31
|
def initialize(csv_path)
|
|
@@ -108,22 +105,24 @@ module JpAddressComplement
|
|
|
108
105
|
# @rbs (Array[Hash[Symbol, untyped]] batch) -> void
|
|
109
106
|
def upsert_batch(batch)
|
|
110
107
|
PostalCode.transaction do
|
|
111
|
-
batch
|
|
112
|
-
PostalCode.where(
|
|
113
|
-
postal_code: record[:postal_code],
|
|
114
|
-
pref_code: record[:pref_code],
|
|
115
|
-
city: record[:city],
|
|
116
|
-
town: record[:town],
|
|
117
|
-
kana_pref: record[:kana_pref],
|
|
118
|
-
kana_city: record[:kana_city],
|
|
119
|
-
kana_town: record[:kana_town]
|
|
120
|
-
).delete_all
|
|
121
|
-
end
|
|
122
|
-
|
|
108
|
+
batch_delete(batch)
|
|
123
109
|
PostalCode.upsert_all(batch)
|
|
124
110
|
end
|
|
125
111
|
end
|
|
126
112
|
|
|
113
|
+
# バッチ内の全レコードを1クエリで一括削除する
|
|
114
|
+
# Arel を使うことで NULL カラム(town 等)を IS NULL として正しく扱う
|
|
115
|
+
# @rbs (Array[Hash[Symbol, untyped]] batch) -> void
|
|
116
|
+
def batch_delete(batch)
|
|
117
|
+
table = PostalCode.arel_table
|
|
118
|
+
batch.each_slice(DELETE_CHUNK_SIZE) do |chunk|
|
|
119
|
+
conditions = chunk.map do |record|
|
|
120
|
+
KEY_COLUMNS.map { |col| table[col].eq(record[col]) }.reduce(:and)
|
|
121
|
+
end.reduce(:or)
|
|
122
|
+
PostalCode.where(conditions).delete_all
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
127
126
|
# 郵便番号・都道府県・市区町村・町域(漢字)が同じでも読み(カナ)が異なれば別レコードとして扱う
|
|
128
127
|
# @rbs (Hash[Symbol, untyped] record) -> Array[String]
|
|
129
128
|
def row_key(record)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module JpAddressComplement
|
|
5
|
+
# UTF-8 版 KEN_ALL(utf_ken_all.csv)のフォーマット定数
|
|
6
|
+
#
|
|
7
|
+
# CSV の各列インデックスを定義する。include することでクラス内から直接参照できる。
|
|
8
|
+
#
|
|
9
|
+
# 参考: https://www.post.japanpost.jp/zipcode/dl/readme.html
|
|
10
|
+
module KenAll
|
|
11
|
+
# 列インデックス(KEN_ALL.CSV 形式)
|
|
12
|
+
COL_PREF_CODE = 0 # : Integer
|
|
13
|
+
COL_POSTAL_CODE = 2 # : Integer
|
|
14
|
+
COL_KANA_PREF = 3 # : Integer
|
|
15
|
+
COL_KANA_CITY = 4 # : Integer
|
|
16
|
+
COL_KANA_TOWN = 5 # : Integer
|
|
17
|
+
COL_PREF = 6 # : Integer
|
|
18
|
+
COL_CITY = 7 # : Integer
|
|
19
|
+
COL_TOWN = 8 # : Integer
|
|
20
|
+
COL_IS_PARTIAL = 9 # : Integer
|
|
21
|
+
COL_HAS_ALIAS = 12 # : Integer
|
|
22
|
+
COL_IS_LARGE_OFFICE = 13 # : Integer
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -94,21 +94,35 @@ module JpAddressComplement
|
|
|
94
94
|
def extract_zip_entries(zip_path, tmpdir)
|
|
95
95
|
csv_path = nil
|
|
96
96
|
Zip::File.open(zip_path) do |zip_file|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
zip_file.each do |entry|
|
|
98
|
+
dest = safe_extract_dest(tmpdir, entry.name)
|
|
99
|
+
next if dest.nil? || dest == tmpdir # パストラバーサルの可能性があるエントリはスキップ
|
|
100
|
+
|
|
101
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
102
|
+
# RubyZip 3.x: 第1引数は destination_directory 配下への相対パス、
|
|
103
|
+
# destination_directory: で展開先ディレクトリを指定する。
|
|
104
|
+
# safe_extract_dest で検証済みの相対パスを渡し、パストラバーサルを多層防御する。
|
|
105
|
+
relative_path = dest[(tmpdir.length + 1)..]
|
|
106
|
+
entry.extract(relative_path, destination_directory: tmpdir) { true } # steep:ignore
|
|
107
|
+
csv_path = dest if entry_csv?(entry.name, dest)
|
|
107
108
|
end
|
|
108
109
|
end
|
|
109
110
|
csv_path
|
|
110
111
|
end
|
|
111
112
|
|
|
113
|
+
# ZIP エントリの展開先絶対パスを安全に解決する。
|
|
114
|
+
# entry_name に含まれる ../ などにより tmpdir の外を指す場合は nil を返す(Zip Slip 対策)。
|
|
115
|
+
# @rbs (String tmpdir, String entry_name) -> String?
|
|
116
|
+
def safe_extract_dest(tmpdir, entry_name)
|
|
117
|
+
# 先頭の / を除去して相対パスとして扱い、File.expand_path で ../ を解決する
|
|
118
|
+
relative = entry_name.delete_prefix('/')
|
|
119
|
+
dest = File.expand_path(File.join(tmpdir, relative))
|
|
120
|
+
# tmpdir 配下に収まるエントリのみ許可する
|
|
121
|
+
return nil unless dest.start_with?("#{tmpdir}#{File::SEPARATOR}") || dest == tmpdir
|
|
122
|
+
|
|
123
|
+
dest
|
|
124
|
+
end
|
|
125
|
+
|
|
112
126
|
# @rbs (String entry_name, String dest) -> bool
|
|
113
127
|
def entry_csv?(entry_name, dest)
|
|
114
128
|
File.basename(entry_name) == CSV_FILENAME && File.file?(dest)
|
|
@@ -21,7 +21,7 @@ module JpAddressComplement
|
|
|
21
21
|
# @param code [String, nil] 郵便番号文字列(ハイフン・全角・〒記号を自動除去)
|
|
22
22
|
# @return [String, nil] 正規化後の7桁郵便番号。不正な場合は nil
|
|
23
23
|
def normalize_postal_code(code)
|
|
24
|
-
return nil if code.
|
|
24
|
+
return nil if code.nil? || code.strip.empty?
|
|
25
25
|
|
|
26
26
|
normalized = normalize_string(code)
|
|
27
27
|
return nil unless normalized.match?(DIGIT_ONLY)
|
|
@@ -34,7 +34,7 @@ module JpAddressComplement
|
|
|
34
34
|
# @param prefix [String, nil] 郵便番号の先頭部分(4桁以上)
|
|
35
35
|
# @return [String, nil] 正規化後の数字文字列。4桁未満または不正な場合は nil
|
|
36
36
|
def normalize_prefix(prefix)
|
|
37
|
-
return nil if prefix.
|
|
37
|
+
return nil if prefix.nil? || prefix.strip.empty?
|
|
38
38
|
|
|
39
39
|
normalized = normalize_string(prefix)
|
|
40
40
|
return nil if normalized.empty?
|
|
@@ -14,6 +14,8 @@ module JpAddressComplement
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
initializer 'jp_address_complement.setup_repository' do # steep:ignore
|
|
17
|
+
next unless defined?(ActiveRecord)
|
|
18
|
+
|
|
17
19
|
require_relative 'repositories/active_record_postal_code_repository'
|
|
18
20
|
require_relative 'models/postal_code'
|
|
19
21
|
JpAddressComplement.configuration.repository ||=
|
|
@@ -35,11 +35,11 @@ module JpAddressComplement
|
|
|
35
35
|
|
|
36
36
|
# @rbs (pref: String?, city: String?, ?town: String?) -> untyped
|
|
37
37
|
def address_relation(pref:, city:, town: nil)
|
|
38
|
-
return nil if pref.
|
|
39
|
-
return nil if city.
|
|
38
|
+
return nil if pref.to_s.strip.empty?
|
|
39
|
+
return nil if city.to_s.strip.empty?
|
|
40
40
|
|
|
41
41
|
relation = postal_code_model.where(pref: pref, city: city)
|
|
42
|
-
return relation if town.
|
|
42
|
+
return relation if town.to_s.strip.empty?
|
|
43
43
|
|
|
44
44
|
pattern = "#{escape_like(town.to_s.strip)}%"
|
|
45
45
|
relation.where('town LIKE ?', pattern)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
require 'csv'
|
|
6
6
|
require_relative 'postal_code_repository'
|
|
7
7
|
require_relative '../address_record'
|
|
8
|
+
require_relative '../ken_all'
|
|
8
9
|
|
|
9
10
|
module JpAddressComplement
|
|
10
11
|
module Repositories
|
|
@@ -23,18 +24,7 @@ module JpAddressComplement
|
|
|
23
24
|
# end
|
|
24
25
|
#
|
|
25
26
|
class CsvPostalCodeRepository < PostalCodeRepository
|
|
26
|
-
|
|
27
|
-
COL_PREF_CODE = 0 # : Integer
|
|
28
|
-
COL_POSTAL_CODE = 2 # : Integer
|
|
29
|
-
COL_KANA_PREF = 3 # : Integer
|
|
30
|
-
COL_KANA_CITY = 4 # : Integer
|
|
31
|
-
COL_KANA_TOWN = 5 # : Integer
|
|
32
|
-
COL_PREF = 6 # : Integer
|
|
33
|
-
COL_CITY = 7 # : Integer
|
|
34
|
-
COL_TOWN = 8 # : Integer
|
|
35
|
-
COL_IS_PARTIAL = 9 # : Integer
|
|
36
|
-
COL_HAS_ALIAS = 12 # : Integer
|
|
37
|
-
COL_IS_LARGE_OFFICE = 13 # : Integer
|
|
27
|
+
include KenAll
|
|
38
28
|
|
|
39
29
|
# @rbs (String csv_path) -> void
|
|
40
30
|
# @param csv_path [String] 読み込む KEN_ALL 形式 UTF-8 CSV のパス
|
|
@@ -20,7 +20,8 @@ module JpAddressComplement
|
|
|
20
20
|
postal_code = record.public_send(postal_code_field)
|
|
21
21
|
address = record.public_send(address_field)
|
|
22
22
|
|
|
23
|
-
return if postal_code.
|
|
23
|
+
return if (postal_code.nil? || postal_code.to_s.strip.empty?) ||
|
|
24
|
+
(address.nil? || address.to_s.strip.empty?)
|
|
24
25
|
|
|
25
26
|
return if JpAddressComplement.valid_combination?(postal_code, address)
|
|
26
27
|
|
|
@@ -9,8 +9,13 @@ require_relative 'jp_address_complement/repositories/postal_code_repository'
|
|
|
9
9
|
require_relative 'jp_address_complement/searcher'
|
|
10
10
|
require_relative 'jp_address_complement/prefecture'
|
|
11
11
|
|
|
12
|
-
# Rails
|
|
13
|
-
|
|
12
|
+
# Rails が利用可能な場合のみ Railtie をロード(Gemfile に指定されていても require 前だと defined?(Rails) が false になり得るため、require を試す)
|
|
13
|
+
begin
|
|
14
|
+
require 'rails'
|
|
15
|
+
require_relative 'jp_address_complement/railtie'
|
|
16
|
+
rescue LoadError
|
|
17
|
+
# Rails が利用できない場合は Railtie をスキップ
|
|
18
|
+
end
|
|
14
19
|
|
|
15
20
|
module JpAddressComplement
|
|
16
21
|
class Error < StandardError; end
|
|
@@ -31,10 +36,19 @@ module JpAddressComplement
|
|
|
31
36
|
@configuration ||= Configuration.new
|
|
32
37
|
end
|
|
33
38
|
|
|
34
|
-
# PostalCode モデルの継承元。未設定時は ActiveRecord::Base
|
|
39
|
+
# PostalCode モデルの継承元。未設定時は ActiveRecord::Base(activerecord gem が利用可能な場合)。
|
|
40
|
+
# Gemfile に指定されていても require 前だと defined? が false になり得るため、未ロード時は require を試す。
|
|
35
41
|
# @rbs () -> Class
|
|
36
42
|
def base_record_class
|
|
37
|
-
configuration.postal_code_model_base
|
|
43
|
+
base = configuration.postal_code_model_base
|
|
44
|
+
return base if base
|
|
45
|
+
|
|
46
|
+
require 'active_record'
|
|
47
|
+
ActiveRecord::Base
|
|
48
|
+
rescue LoadError => e
|
|
49
|
+
raise Error,
|
|
50
|
+
'ActiveRecord is not available. Add gem "activerecord" to your Gemfile, or set ' \
|
|
51
|
+
"JpAddressComplement.configuration.postal_code_model_base. (#{e.message})"
|
|
38
52
|
end
|
|
39
53
|
|
|
40
54
|
# @rbs (Class) -> void
|
|
@@ -124,6 +138,10 @@ module JpAddressComplement
|
|
|
124
138
|
require_relative 'jp_address_complement/repositories/active_record_postal_code_repository'
|
|
125
139
|
require_relative 'jp_address_complement/models/postal_code'
|
|
126
140
|
Repositories::ActiveRecordPostalCodeRepository.new
|
|
141
|
+
rescue LoadError => e
|
|
142
|
+
raise Error,
|
|
143
|
+
'ActiveRecord is not loaded. Add gem "activerecord" to your Gemfile to use the default repository, ' \
|
|
144
|
+
"or set JpAddressComplement.configuration.repository to your own implementation. (#{e.message})"
|
|
127
145
|
end
|
|
128
146
|
end
|
|
129
147
|
end
|
|
@@ -21,30 +21,16 @@ module JpAddressComplement
|
|
|
21
21
|
# UTF-8 版 KEN_ALL(utf_ken_all.csv)を読み込み、jp_address_complement_postal_codes テーブルに upsert する
|
|
22
22
|
# UTF-8 形式の CSV を前提として処理する
|
|
23
23
|
class CsvImporter
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# 列インデックス(KEN_ALL.CSV 形式)
|
|
27
|
-
COL_PREF_CODE: Integer
|
|
28
|
-
|
|
29
|
-
COL_POSTAL_CODE: Integer
|
|
30
|
-
|
|
31
|
-
COL_KANA_PREF: Integer
|
|
32
|
-
|
|
33
|
-
COL_KANA_CITY: Integer
|
|
34
|
-
|
|
35
|
-
COL_KANA_TOWN: Integer
|
|
24
|
+
include KenAll
|
|
36
25
|
|
|
37
|
-
|
|
26
|
+
# バッチ削除・ユニークキーとして使うカラム群
|
|
27
|
+
KEY_COLUMNS: Array[Symbol]
|
|
38
28
|
|
|
39
|
-
|
|
29
|
+
# SQLite のデフォルト式木深度制限(1000)対策。
|
|
30
|
+
# reduce(:or) で N 件 OR 結合すると深さ ≈ N + 6 になるため、500 件に収める(深さ ≈ 506)。
|
|
31
|
+
DELETE_CHUNK_SIZE: Integer
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
COL_IS_PARTIAL: Integer
|
|
44
|
-
|
|
45
|
-
COL_HAS_ALIAS: Integer
|
|
46
|
-
|
|
47
|
-
COL_IS_LARGE_OFFICE: Integer
|
|
33
|
+
BATCH_SIZE: Integer
|
|
48
34
|
|
|
49
35
|
# @rbs (String csv_path) -> void
|
|
50
36
|
def initialize: (String csv_path) -> void
|
|
@@ -66,6 +52,11 @@ module JpAddressComplement
|
|
|
66
52
|
# @rbs (Array[Hash[Symbol, untyped]] batch) -> void
|
|
67
53
|
def upsert_batch: (Array[Hash[Symbol, untyped]] batch) -> void
|
|
68
54
|
|
|
55
|
+
# バッチ内の全レコードを1クエリで一括削除する
|
|
56
|
+
# Arel を使うことで NULL カラム(town 等)を IS NULL として正しく扱う
|
|
57
|
+
# @rbs (Array[Hash[Symbol, untyped]] batch) -> void
|
|
58
|
+
def batch_delete: (Array[Hash[Symbol, untyped]] batch) -> void
|
|
59
|
+
|
|
69
60
|
# 郵便番号・都道府県・市区町村・町域(漢字)が同じでも読み(カナ)が異なれば別レコードとして扱う
|
|
70
61
|
# @rbs (Hash[Symbol, untyped] record) -> Array[String]
|
|
71
62
|
def row_key: (Hash[Symbol, untyped] record) -> Array[String]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated from lib/jp_address_complement/ken_all.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module JpAddressComplement
|
|
4
|
+
# UTF-8 版 KEN_ALL(utf_ken_all.csv)のフォーマット定数
|
|
5
|
+
#
|
|
6
|
+
# CSV の各列インデックスを定義する。include することでクラス内から直接参照できる。
|
|
7
|
+
#
|
|
8
|
+
# 参考: https://www.post.japanpost.jp/zipcode/dl/readme.html
|
|
9
|
+
module KenAll
|
|
10
|
+
# 列インデックス(KEN_ALL.CSV 形式)
|
|
11
|
+
COL_PREF_CODE: ::Integer
|
|
12
|
+
|
|
13
|
+
COL_POSTAL_CODE: ::Integer
|
|
14
|
+
|
|
15
|
+
COL_KANA_PREF: ::Integer
|
|
16
|
+
|
|
17
|
+
COL_KANA_CITY: ::Integer
|
|
18
|
+
|
|
19
|
+
COL_KANA_TOWN: ::Integer
|
|
20
|
+
|
|
21
|
+
COL_PREF: ::Integer
|
|
22
|
+
|
|
23
|
+
COL_CITY: ::Integer
|
|
24
|
+
|
|
25
|
+
COL_TOWN: ::Integer
|
|
26
|
+
|
|
27
|
+
COL_IS_PARTIAL: ::Integer
|
|
28
|
+
|
|
29
|
+
COL_HAS_ALIAS: ::Integer
|
|
30
|
+
|
|
31
|
+
COL_IS_LARGE_OFFICE: ::Integer
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -40,6 +40,11 @@ module JpAddressComplement
|
|
|
40
40
|
# @rbs (String zip_path, String tmpdir) -> String?
|
|
41
41
|
def extract_zip_entries: (String zip_path, String tmpdir) -> String?
|
|
42
42
|
|
|
43
|
+
# ZIP エントリの展開先絶対パスを安全に解決する。
|
|
44
|
+
# entry_name に含まれる ../ などにより tmpdir の外を指す場合は nil を返す(Zip Slip 対策)。
|
|
45
|
+
# @rbs (String tmpdir, String entry_name) -> String?
|
|
46
|
+
def safe_extract_dest: (String tmpdir, String entry_name) -> String?
|
|
47
|
+
|
|
43
48
|
# @rbs (String entry_name, String dest) -> bool
|
|
44
49
|
def entry_csv?: (String entry_name, String dest) -> bool
|
|
45
50
|
|
|
@@ -16,28 +16,7 @@ module JpAddressComplement
|
|
|
16
16
|
# c.repository = JpAddressComplement::Repositories::CsvPostalCodeRepository.new('/path/to/utf_ken_all.csv')
|
|
17
17
|
# end
|
|
18
18
|
class CsvPostalCodeRepository < PostalCodeRepository
|
|
19
|
-
|
|
20
|
-
COL_PREF_CODE: ::Integer
|
|
21
|
-
|
|
22
|
-
COL_POSTAL_CODE: ::Integer
|
|
23
|
-
|
|
24
|
-
COL_KANA_PREF: ::Integer
|
|
25
|
-
|
|
26
|
-
COL_KANA_CITY: ::Integer
|
|
27
|
-
|
|
28
|
-
COL_KANA_TOWN: ::Integer
|
|
29
|
-
|
|
30
|
-
COL_PREF: ::Integer
|
|
31
|
-
|
|
32
|
-
COL_CITY: ::Integer
|
|
33
|
-
|
|
34
|
-
COL_TOWN: ::Integer
|
|
35
|
-
|
|
36
|
-
COL_IS_PARTIAL: ::Integer
|
|
37
|
-
|
|
38
|
-
COL_HAS_ALIAS: ::Integer
|
|
39
|
-
|
|
40
|
-
COL_IS_LARGE_OFFICE: ::Integer
|
|
19
|
+
include KenAll
|
|
41
20
|
|
|
42
21
|
# @rbs (String csv_path) -> void
|
|
43
22
|
# @param csv_path [String] 読み込む KEN_ALL 形式 UTF-8 CSV のパス
|
|
@@ -17,7 +17,8 @@ module JpAddressComplement
|
|
|
17
17
|
# @return [Configuration]
|
|
18
18
|
def self.configuration: () -> Configuration
|
|
19
19
|
|
|
20
|
-
# PostalCode モデルの継承元。未設定時は ActiveRecord::Base
|
|
20
|
+
# PostalCode モデルの継承元。未設定時は ActiveRecord::Base(activerecord gem が利用可能な場合)。
|
|
21
|
+
# Gemfile に指定されていても require 前だと defined? が false になり得るため、未ロード時は require を試す。
|
|
21
22
|
# @rbs () -> Class
|
|
22
23
|
def self.base_record_class: () -> Class
|
|
23
24
|
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jp_address_complement
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- naokirin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: activerecord
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '7.0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '7.0'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: csv
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,33 +25,33 @@ dependencies:
|
|
|
39
25
|
- !ruby/object:Gem::Version
|
|
40
26
|
version: '3.0'
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
28
|
+
name: rubyzip
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
44
30
|
requirements:
|
|
45
31
|
- - ">="
|
|
46
32
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
33
|
+
version: '2.3'
|
|
48
34
|
type: :runtime
|
|
49
35
|
prerelease: false
|
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
37
|
requirements:
|
|
52
38
|
- - ">="
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
40
|
+
version: '2.3'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
42
|
+
name: activerecord
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - ">="
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
62
|
-
type: :
|
|
47
|
+
version: '7.0'
|
|
48
|
+
type: :development
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
52
|
- - ">="
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
54
|
+
version: '7.0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
56
|
name: generator_spec
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,6 +94,20 @@ dependencies:
|
|
|
108
94
|
- - ">="
|
|
109
95
|
- !ruby/object:Gem::Version
|
|
110
96
|
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: railties
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '7.0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '7.0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: rake
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -355,6 +355,7 @@ files:
|
|
|
355
355
|
- lib/jp_address_complement/address_record.rb
|
|
356
356
|
- lib/jp_address_complement/configuration.rb
|
|
357
357
|
- lib/jp_address_complement/importers/csv_importer.rb
|
|
358
|
+
- lib/jp_address_complement/ken_all.rb
|
|
358
359
|
- lib/jp_address_complement/ken_all_downloader.rb
|
|
359
360
|
- lib/jp_address_complement/models/postal_code.rb
|
|
360
361
|
- lib/jp_address_complement/normalizer.rb
|
|
@@ -373,6 +374,7 @@ files:
|
|
|
373
374
|
- sig/generated/jp_address_complement.rbs
|
|
374
375
|
- sig/generated/jp_address_complement/configuration.rbs
|
|
375
376
|
- sig/generated/jp_address_complement/importers/csv_importer.rbs
|
|
377
|
+
- sig/generated/jp_address_complement/ken_all.rbs
|
|
376
378
|
- sig/generated/jp_address_complement/ken_all_downloader.rbs
|
|
377
379
|
- sig/generated/jp_address_complement/normalizer.rbs
|
|
378
380
|
- sig/generated/jp_address_complement/prefecture.rbs
|