jp_address_complement 0.1.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 +7 -0
- data/.agent/rules/specify-rules.md +29 -0
- data/.agent/workflows/speckit.analyze.md +184 -0
- data/.agent/workflows/speckit.checklist.md +294 -0
- data/.agent/workflows/speckit.clarify.md +181 -0
- data/.agent/workflows/speckit.constitution.md +84 -0
- data/.agent/workflows/speckit.implement.md +135 -0
- data/.agent/workflows/speckit.plan.md +90 -0
- data/.agent/workflows/speckit.specify.md +258 -0
- data/.agent/workflows/speckit.tasks.md +137 -0
- data/.agent/workflows/speckit.taskstoissues.md +30 -0
- data/.claude/commands/speckit.analyze.md +184 -0
- data/.claude/commands/speckit.checklist.md +294 -0
- data/.claude/commands/speckit.clarify.md +181 -0
- data/.claude/commands/speckit.constitution.md +84 -0
- data/.claude/commands/speckit.implement.md +135 -0
- data/.claude/commands/speckit.plan.md +90 -0
- data/.claude/commands/speckit.specify.md +258 -0
- data/.claude/commands/speckit.tasks.md +137 -0
- data/.claude/commands/speckit.taskstoissues.md +30 -0
- data/.cursor/commands/speckit.analyze.md +184 -0
- data/.cursor/commands/speckit.checklist.md +294 -0
- data/.cursor/commands/speckit.clarify.md +181 -0
- data/.cursor/commands/speckit.constitution.md +84 -0
- data/.cursor/commands/speckit.implement.md +135 -0
- data/.cursor/commands/speckit.plan.md +90 -0
- data/.cursor/commands/speckit.specify.md +258 -0
- data/.cursor/commands/speckit.tasks.md +137 -0
- data/.cursor/commands/speckit.taskstoissues.md +30 -0
- data/.cursor/rules/specify-rules.mdc +32 -0
- data/.rubocop.yml +118 -0
- data/.specify/memory/constitution.md +130 -0
- data/.specify/scripts/bash/check-prerequisites.sh +166 -0
- data/.specify/scripts/bash/common.sh +156 -0
- data/.specify/scripts/bash/create-new-feature.sh +297 -0
- data/.specify/scripts/bash/setup-plan.sh +61 -0
- data/.specify/scripts/bash/update-agent-context.sh +810 -0
- data/.specify/templates/agent-file-template.md +28 -0
- data/.specify/templates/checklist-template.md +40 -0
- data/.specify/templates/constitution-template.md +50 -0
- data/.specify/templates/plan-template.md +104 -0
- data/.specify/templates/spec-template.md +115 -0
- data/.specify/templates/tasks-template.md +251 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE +9 -0
- data/README.md +274 -0
- data/Rakefile +19 -0
- data/Steepfile +9 -0
- data/examples/rails/jp_address_complement_demo/.gitignore +15 -0
- data/examples/rails/jp_address_complement_demo/.ruby-version +1 -0
- data/examples/rails/jp_address_complement_demo/Gemfile +22 -0
- data/examples/rails/jp_address_complement_demo/Gemfile.lock +252 -0
- data/examples/rails/jp_address_complement_demo/README.md +57 -0
- data/examples/rails/jp_address_complement_demo/Rakefile +6 -0
- data/examples/rails/jp_address_complement_demo/app/assets/images/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/app/assets/stylesheets/application.css +1 -0
- data/examples/rails/jp_address_complement_demo/app/controllers/addresses_controller.rb +59 -0
- data/examples/rails/jp_address_complement_demo/app/controllers/application_controller.rb +4 -0
- data/examples/rails/jp_address_complement_demo/app/controllers/concerns/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/app/helpers/application_helper.rb +2 -0
- data/examples/rails/jp_address_complement_demo/app/models/application_record.rb +3 -0
- data/examples/rails/jp_address_complement_demo/app/models/concerns/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/app/views/addresses/index.html.erb +22 -0
- data/examples/rails/jp_address_complement_demo/app/views/addresses/prefecture.html.erb +20 -0
- data/examples/rails/jp_address_complement_demo/app/views/addresses/prefix.html.erb +25 -0
- data/examples/rails/jp_address_complement_demo/app/views/addresses/reverse.html.erb +30 -0
- data/examples/rails/jp_address_complement_demo/app/views/addresses/validate.html.erb +23 -0
- data/examples/rails/jp_address_complement_demo/app/views/layouts/application.html.erb +40 -0
- data/examples/rails/jp_address_complement_demo/app/views/pwa/manifest.json.erb +22 -0
- data/examples/rails/jp_address_complement_demo/app/views/pwa/service-worker.js +26 -0
- data/examples/rails/jp_address_complement_demo/bin/ci +6 -0
- data/examples/rails/jp_address_complement_demo/bin/dev +2 -0
- data/examples/rails/jp_address_complement_demo/bin/rails +4 -0
- data/examples/rails/jp_address_complement_demo/bin/rake +4 -0
- data/examples/rails/jp_address_complement_demo/bin/setup +35 -0
- data/examples/rails/jp_address_complement_demo/config/application.rb +42 -0
- data/examples/rails/jp_address_complement_demo/config/boot.rb +3 -0
- data/examples/rails/jp_address_complement_demo/config/ci.rb +15 -0
- data/examples/rails/jp_address_complement_demo/config/credentials.yml.enc +1 -0
- data/examples/rails/jp_address_complement_demo/config/database.yml +31 -0
- data/examples/rails/jp_address_complement_demo/config/environment.rb +5 -0
- data/examples/rails/jp_address_complement_demo/config/environments/development.rb +54 -0
- data/examples/rails/jp_address_complement_demo/config/environments/production.rb +67 -0
- data/examples/rails/jp_address_complement_demo/config/environments/test.rb +42 -0
- data/examples/rails/jp_address_complement_demo/config/initializers/content_security_policy.rb +29 -0
- data/examples/rails/jp_address_complement_demo/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails/jp_address_complement_demo/config/initializers/inflections.rb +16 -0
- data/examples/rails/jp_address_complement_demo/config/locales/en.yml +31 -0
- data/examples/rails/jp_address_complement_demo/config/puma.rb +39 -0
- data/examples/rails/jp_address_complement_demo/config/routes.rb +19 -0
- data/examples/rails/jp_address_complement_demo/config.ru +6 -0
- data/examples/rails/jp_address_complement_demo/db/migrate/20260228083709_create_jp_address_complement_postal_codes.rb +44 -0
- data/examples/rails/jp_address_complement_demo/db/schema.rb +33 -0
- data/examples/rails/jp_address_complement_demo/db/seeds.rb +24 -0
- data/examples/rails/jp_address_complement_demo/lib/tasks/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/log/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/public/400.html +135 -0
- data/examples/rails/jp_address_complement_demo/public/404.html +135 -0
- data/examples/rails/jp_address_complement_demo/public/406-unsupported-browser.html +135 -0
- data/examples/rails/jp_address_complement_demo/public/422.html +135 -0
- data/examples/rails/jp_address_complement_demo/public/500.html +135 -0
- data/examples/rails/jp_address_complement_demo/public/icon.png +0 -0
- data/examples/rails/jp_address_complement_demo/public/icon.svg +3 -0
- data/examples/rails/jp_address_complement_demo/public/robots.txt +1 -0
- data/examples/rails/jp_address_complement_demo/script/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/storage/.keep +0 -0
- data/examples/rails/jp_address_complement_demo/vendor/.keep +0 -0
- data/lib/generators/jp_address_complement/install_generator.rb +34 -0
- data/lib/generators/jp_address_complement/templates/create_jp_address_complement_postal_codes.rb.erb +45 -0
- data/lib/jp_address_complement/address_record.rb +36 -0
- data/lib/jp_address_complement/configuration.rb +21 -0
- data/lib/jp_address_complement/importers/csv_importer.rb +148 -0
- data/lib/jp_address_complement/ken_all_downloader.rb +122 -0
- data/lib/jp_address_complement/models/postal_code.rb +21 -0
- data/lib/jp_address_complement/normalizer.rb +77 -0
- data/lib/jp_address_complement/prefecture.rb +105 -0
- data/lib/jp_address_complement/railtie.rb +27 -0
- data/lib/jp_address_complement/repositories/active_record_postal_code_repository.rb +78 -0
- data/lib/jp_address_complement/repositories/csv_postal_code_repository.rb +200 -0
- data/lib/jp_address_complement/repositories/postal_code_repository.rb +36 -0
- data/lib/jp_address_complement/searcher.rb +85 -0
- data/lib/jp_address_complement/validators/address_validator.rb +41 -0
- data/lib/jp_address_complement/version.rb +6 -0
- data/lib/jp_address_complement.rb +129 -0
- data/lib/tasks/jp_address_complement.rake +32 -0
- data/rbs_collection.lock.yaml +380 -0
- data/rbs_collection.yaml +19 -0
- data/sig/generated/generators/jp_address_complement/install_generator.rbs +18 -0
- data/sig/generated/jp_address_complement/configuration.rbs +18 -0
- data/sig/generated/jp_address_complement/importers/csv_importer.rbs +78 -0
- data/sig/generated/jp_address_complement/ken_all_downloader.rbs +49 -0
- data/sig/generated/jp_address_complement/normalizer.rbs +37 -0
- data/sig/generated/jp_address_complement/prefecture.rbs +27 -0
- data/sig/generated/jp_address_complement/railtie.rbs +8 -0
- data/sig/generated/jp_address_complement/repositories/active_record_postal_code_repository.rbs +38 -0
- data/sig/generated/jp_address_complement/repositories/csv_postal_code_repository.rbs +100 -0
- data/sig/generated/jp_address_complement/repositories/postal_code_repository.rbs +29 -0
- data/sig/generated/jp_address_complement/searcher.rbs +43 -0
- data/sig/generated/jp_address_complement/validators/address_validator.rbs +24 -0
- data/sig/generated/jp_address_complement/version.rbs +5 -0
- data/sig/generated/jp_address_complement.rbs +84 -0
- data/sig/manual/address_record.rbs +40 -0
- data/sig/manual/gem_rubyzip.rbs +17 -0
- data/sig/manual/postal_code.rbs +9 -0
- data/sig/manual/stdlib_csv_invalid_encoding_error.rbs +5 -0
- data/sig/manual/stdlib_net_http.rbs +33 -0
- data/sig/manual/stdlib_openuri.rbs +9 -0
- data/sig/manual/stdlib_tmpdir.rbs +4 -0
- data/specs/001-jp-address-complement-gem/checklists/requirements.md +36 -0
- data/specs/001-jp-address-complement-gem/contracts/public-api.md +209 -0
- data/specs/001-jp-address-complement-gem/data-model.md +207 -0
- data/specs/001-jp-address-complement-gem/plan.md +124 -0
- data/specs/001-jp-address-complement-gem/quickstart.md +151 -0
- data/specs/001-jp-address-complement-gem/research.md +139 -0
- data/specs/001-jp-address-complement-gem/spec.md +153 -0
- data/specs/001-jp-address-complement-gem/tasks.md +279 -0
- data/specs/002-rbs-type-annotations/checklists/requirements.md +37 -0
- data/specs/002-rbs-type-annotations/contracts/rbs-public-api.md +116 -0
- data/specs/002-rbs-type-annotations/data-model.md +119 -0
- data/specs/002-rbs-type-annotations/plan.md +116 -0
- data/specs/002-rbs-type-annotations/quickstart.md +105 -0
- data/specs/002-rbs-type-annotations/research.md +173 -0
- data/specs/002-rbs-type-annotations/spec.md +125 -0
- data/specs/002-rbs-type-annotations/tasks.md +189 -0
- data/specs/003-csv-remove-obsolete/checklists/requirements.md +34 -0
- data/specs/003-csv-remove-obsolete/contracts/csv-import.md +41 -0
- data/specs/003-csv-remove-obsolete/data-model.md +47 -0
- data/specs/003-csv-remove-obsolete/plan.md +73 -0
- data/specs/003-csv-remove-obsolete/quickstart.md +40 -0
- data/specs/003-csv-remove-obsolete/research.md +71 -0
- data/specs/003-csv-remove-obsolete/spec.md +85 -0
- data/specs/003-csv-remove-obsolete/tasks.md +167 -0
- data/specs/004-prefecture-code-reverse-lookup/checklists/requirements.md +34 -0
- data/specs/004-prefecture-code-reverse-lookup/contracts/public-api-prefecture-and-reverse.md +122 -0
- data/specs/004-prefecture-code-reverse-lookup/data-model.md +81 -0
- data/specs/004-prefecture-code-reverse-lookup/plan.md +92 -0
- data/specs/004-prefecture-code-reverse-lookup/quickstart.md +91 -0
- data/specs/004-prefecture-code-reverse-lookup/research.md +62 -0
- data/specs/004-prefecture-code-reverse-lookup/spec.md +120 -0
- data/specs/004-prefecture-code-reverse-lookup/tasks.md +190 -0
- metadata +451 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# net/http の Steep 用スタブ(KenAllDownloader で使用)
|
|
2
|
+
module Net
|
|
3
|
+
class HTTPError < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class OpenTimeout < Timeout::Error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class ReadTimeout < Timeout::Error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class HTTP
|
|
13
|
+
def self.start: (String host, Integer port, ?use_ssl: bool, ?read_timeout: Integer, ?open_timeout: Integer) { (Net::HTTP) -> untyped } -> untyped
|
|
14
|
+
|
|
15
|
+
def request: (Net::HTTPRequest request) { (Net::HTTPResponse) -> void } -> Net::HTTPResponse
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class HTTP::Get < Net::HTTPRequest
|
|
19
|
+
def initialize: (URI::Generic uri) -> void
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class HTTPSuccess < Net::HTTPResponse
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class HTTPResponse
|
|
26
|
+
attr_reader code: String
|
|
27
|
+
attr_reader message: String
|
|
28
|
+
attr_reader body_io: IO
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class HTTPRequest
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Specification Quality Checklist: 日本住所補完 Gem
|
|
2
|
+
|
|
3
|
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
|
4
|
+
**Created**: 2026-02-22
|
|
5
|
+
**Feature**: [spec.md](../spec.md)
|
|
6
|
+
|
|
7
|
+
## Content Quality
|
|
8
|
+
|
|
9
|
+
- [x] No implementation details (languages, frameworks, APIs)
|
|
10
|
+
- [x] Focused on user value and business needs
|
|
11
|
+
- [x] Written for non-technical stakeholders
|
|
12
|
+
- [x] All mandatory sections completed
|
|
13
|
+
|
|
14
|
+
## Requirement Completeness
|
|
15
|
+
|
|
16
|
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
|
17
|
+
- [x] Requirements are testable and unambiguous
|
|
18
|
+
- [x] Success criteria are measurable
|
|
19
|
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
|
20
|
+
- [x] All acceptance scenarios are defined
|
|
21
|
+
- [x] Edge cases are identified
|
|
22
|
+
- [x] Scope is clearly bounded
|
|
23
|
+
- [x] Dependencies and assumptions identified
|
|
24
|
+
|
|
25
|
+
## Feature Readiness
|
|
26
|
+
|
|
27
|
+
- [x] All functional requirements have clear acceptance criteria
|
|
28
|
+
- [x] User scenarios cover primary flows
|
|
29
|
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
|
30
|
+
- [x] No implementation details leak into specification
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- Constitution Compliance Criteria(CC-001〜CC-004)は、プロジェクト憲章に基づく必須成功条件として意図的に技術用語(RSpec, RuboCop)を含んでいるため、技術非依存の通常成功基準とは区別して評価している。
|
|
35
|
+
- 全チェック項目が1回目のバリデーションで通過した(2026-02-22 実施)。
|
|
36
|
+
- `/speckit.plan` または `/speckit.clarify` への移行の準備が整っている。
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Public API Contract: JpAddressComplement
|
|
2
|
+
|
|
3
|
+
**Gem**: `jp_address_complement`
|
|
4
|
+
**Namespace**: `JpAddressComplement`
|
|
5
|
+
**Contract Type**: Ruby Public API(ライブラリの公開インターフェース定義)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## モジュールメソッド(`JpAddressComplement.*`)
|
|
10
|
+
|
|
11
|
+
### `search_by_postal_code(code) → Array<AddressRecord>`
|
|
12
|
+
|
|
13
|
+
郵便番号(7桁)に対応する住所レコードを返す。
|
|
14
|
+
|
|
15
|
+
**引数**
|
|
16
|
+
|
|
17
|
+
| 名前 | 型 | 説明 |
|
|
18
|
+
|-----|----|------|
|
|
19
|
+
| `code` | `String` | 郵便番号。ハイフンあり(`"100-0001"`)・全角数字・`〒` 記号はすべて自動正規化する |
|
|
20
|
+
|
|
21
|
+
**戻り値**: `Array<AddressRecord>`
|
|
22
|
+
|
|
23
|
+
- 一致するレコードが存在しない場合は `[]`(空配列)
|
|
24
|
+
- 同一郵便番号に複数レコードが存在する場合(大口事業所等)は全件返す
|
|
25
|
+
|
|
26
|
+
**エラー**: 不正入力(`nil`・空文字・数字以外を含む文字列)に対しては例外を発生させず `[]` を返す。
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# 成功例
|
|
30
|
+
JpAddressComplement.search_by_postal_code("1000001")
|
|
31
|
+
# => [#<data JpAddressComplement::AddressRecord
|
|
32
|
+
# postal_code="1000001",
|
|
33
|
+
# pref_code="13",
|
|
34
|
+
# pref="東京都",
|
|
35
|
+
# city="千代田区",
|
|
36
|
+
# town="千代田",
|
|
37
|
+
# kana_pref="トウキョウト",
|
|
38
|
+
# kana_city="チヨダク",
|
|
39
|
+
# kana_town="チヨダ",
|
|
40
|
+
# has_alias=false,
|
|
41
|
+
# is_partial=false,
|
|
42
|
+
# is_large_office=false>]
|
|
43
|
+
|
|
44
|
+
# 正規化される入力例
|
|
45
|
+
JpAddressComplement.search_by_postal_code("100-0001") # ハイフンあり
|
|
46
|
+
JpAddressComplement.search_by_postal_code("〒100-0001") # 〒付き
|
|
47
|
+
JpAddressComplement.search_by_postal_code("1000001") # 全角数字
|
|
48
|
+
|
|
49
|
+
# 存在しない場合
|
|
50
|
+
JpAddressComplement.search_by_postal_code("0000000") # => []
|
|
51
|
+
|
|
52
|
+
# 不正入力(エラーなし)
|
|
53
|
+
JpAddressComplement.search_by_postal_code(nil) # => []
|
|
54
|
+
JpAddressComplement.search_by_postal_code("") # => []
|
|
55
|
+
JpAddressComplement.search_by_postal_code("abc") # => []
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### `search_by_postal_code_prefix(prefix) → Array<AddressRecord>`
|
|
61
|
+
|
|
62
|
+
郵便番号の先頭4桁以上に一致する住所候補一覧を返す。
|
|
63
|
+
|
|
64
|
+
**引数**
|
|
65
|
+
|
|
66
|
+
| 名前 | 型 | 説明 |
|
|
67
|
+
|-----|----|------|
|
|
68
|
+
| `prefix` | `String` | 郵便番号の先頭部分(4桁以上)。全角数字は自動正規化 |
|
|
69
|
+
|
|
70
|
+
**戻り値**: `Array<AddressRecord>`
|
|
71
|
+
|
|
72
|
+
- 先頭4桁未満の場合は `[]`(過大結果防止のガード)
|
|
73
|
+
- 7桁完全一致の場合は `search_by_postal_code` と同等の結果
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# 先頭4桁
|
|
77
|
+
JpAddressComplement.search_by_postal_code_prefix("1000")
|
|
78
|
+
# => [#<data ... postal_code="1000001">, #<data ... postal_code="1000002">, ...]
|
|
79
|
+
|
|
80
|
+
# 先頭6桁
|
|
81
|
+
JpAddressComplement.search_by_postal_code_prefix("100000")
|
|
82
|
+
# => [#<data ... postal_code="1000001">]
|
|
83
|
+
|
|
84
|
+
# 先頭3桁以下 → 空返却
|
|
85
|
+
JpAddressComplement.search_by_postal_code_prefix("100") # => []
|
|
86
|
+
JpAddressComplement.search_by_postal_code_prefix("10") # => []
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### `valid_combination?(postal_code, address) → Boolean`
|
|
92
|
+
|
|
93
|
+
郵便番号と住所文字列が整合しているかを検証する。
|
|
94
|
+
|
|
95
|
+
**引数**
|
|
96
|
+
|
|
97
|
+
| 名前 | 型 | 説明 |
|
|
98
|
+
|-----|----|------|
|
|
99
|
+
| `postal_code` | `String` | 郵便番号(自動正規化) |
|
|
100
|
+
| `address` | `String` | 住所文字列(番地・建物名が含まれていても可) |
|
|
101
|
+
|
|
102
|
+
**戻り値**: `Boolean`
|
|
103
|
+
|
|
104
|
+
- 郵便番号に対応するレコードの「都道府県名 + 市区町村名 + 町域名」が `address` に**部分一致**する場合は `true`
|
|
105
|
+
- 同一郵便番号に複数レコードがある場合はいずれか1件が一致すれば `true`
|
|
106
|
+
- 郵便番号が不正・存在しない場合は `false`
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
JpAddressComplement.valid_combination?("1000001", "東京都千代田区千代田1-1 ○○ビル") # => true
|
|
110
|
+
JpAddressComplement.valid_combination?("1000001", "東京都千代田区千代田") # => true
|
|
111
|
+
JpAddressComplement.valid_combination?("1000001", "大阪府大阪市北区梅田") # => false
|
|
112
|
+
JpAddressComplement.valid_combination?("0000000", "東京都千代田区千代田") # => false(存在しない)
|
|
113
|
+
JpAddressComplement.valid_combination?(nil, "東京都...") # => false
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### `configure { |config| ... }` — 設定ブロック
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
JpAddressComplement.configure do |config|
|
|
122
|
+
# カスタムリポジトリを注入(Rails 以外の環境向け)
|
|
123
|
+
config.repository = MyCustomPostalCodeRepository.new
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**設定オプション**
|
|
128
|
+
|
|
129
|
+
| オプション | 型 | デフォルト | 説明 |
|
|
130
|
+
|-----------|-----|-----------|------|
|
|
131
|
+
| `repository` | `PostalCodeRepository` 準拠オブジェクト | `ActiveRecordPostalCodeRepository.new` | データアクセス実装 |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 値オブジェクト: `AddressRecord`
|
|
136
|
+
|
|
137
|
+
`search_by_postal_code` および `search_by_postal_code_prefix` が返す値オブジェクト。
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
record = JpAddressComplement.search_by_postal_code("1000001").first
|
|
141
|
+
|
|
142
|
+
record.postal_code # => "1000001" (String)
|
|
143
|
+
record.pref_code # => "13" (String)
|
|
144
|
+
record.pref # => "東京都" (String)
|
|
145
|
+
record.city # => "千代田区" (String)
|
|
146
|
+
record.town # => "千代田" (String or nil)
|
|
147
|
+
record.kana_pref # => "トウキョウト" (String or nil)
|
|
148
|
+
record.kana_city # => "チヨダク" (String or nil)
|
|
149
|
+
record.kana_town # => "チヨダ" (String or nil)
|
|
150
|
+
record.has_alias # => false (Boolean) 通称あり
|
|
151
|
+
record.is_partial # => false (Boolean) 丁目区分あり
|
|
152
|
+
record.is_large_office # => false (Boolean) 大口事業所専用
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## ActiveModel バリデーター
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
validates :postal_code_field, jp_address_complement: { address_field: :address_field_name }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**オプション**
|
|
164
|
+
|
|
165
|
+
| キー | 型 | 必須 | 説明 |
|
|
166
|
+
|-----|----|------|------|
|
|
167
|
+
| `address_field` | `Symbol` | ✅ | 住所文字列が格納されているモデルの属性名 |
|
|
168
|
+
|
|
169
|
+
**バリデーションの動作**
|
|
170
|
+
|
|
171
|
+
| 条件 | 結果 |
|
|
172
|
+
|------|------|
|
|
173
|
+
| `postal_code` フィールドが空 | バリデーターをスキップ(他バリデーターに委ねる) |
|
|
174
|
+
| 整合している | 通過 |
|
|
175
|
+
| 整合しない | `errors.add(:postal_code_field, "と住所が一致しません")` |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Rake タスク
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# インポート(Shift_JIS → UTF-8 変換を自動実行)
|
|
183
|
+
bundle exec rake jp_address_complement:import CSV=/path/to/KEN_ALL.CSV
|
|
184
|
+
|
|
185
|
+
# インポート進捗ログ付き
|
|
186
|
+
bundle exec rake jp_address_complement:import CSV=/path/to/KEN_ALL.CSV VERBOSE=true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**環境変数**
|
|
190
|
+
|
|
191
|
+
| 変数 | 必須 | 説明 |
|
|
192
|
+
|------|------|------|
|
|
193
|
+
| `CSV` | ✅ | KEN_ALL.CSV へのパス |
|
|
194
|
+
| `VERBOSE` | ❌ | `true` で進捗ログを出力(デフォルト: `false`) |
|
|
195
|
+
| `BATCH_SIZE` | ❌ | バッチ件数(デフォルト: `1000`) |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Rails Generator
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
rails g jp_address_complement:install
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**生成ファイル**
|
|
206
|
+
|
|
207
|
+
| ファイル | 説明 |
|
|
208
|
+
|---------|------|
|
|
209
|
+
| `db/migrate/YYYYMMDDHHMMSS_create_jp_address_complement_postal_codes.rb` | 住所テーブルマイグレーション |
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Data Model: 日本住所補完 Gem
|
|
2
|
+
|
|
3
|
+
**Branch**: `001-jp-address-complement-gem`
|
|
4
|
+
**Date**: 2026-02-22
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## エンティティ一覧
|
|
9
|
+
|
|
10
|
+
### 1. `postal_codes` テーブル(DB永続化)
|
|
11
|
+
|
|
12
|
+
利用アプリケーションの DB に作成される住所データテーブル。Gem からマイグレーションを提供する。
|
|
13
|
+
|
|
14
|
+
#### スキーマ定義
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# db/migrate/YYYYMMDDHHMMSS_create_jp_address_complement_postal_codes.rb
|
|
18
|
+
create_table :jp_address_complement_postal_codes do |t|
|
|
19
|
+
t.string :postal_code, limit: 7, null: false # 郵便番号(7桁、数字のみ)
|
|
20
|
+
t.string :pref_code, limit: 2, null: false # 都道府県コード(JIS X 0401)
|
|
21
|
+
t.string :pref, limit: 10, null: false # 都道府県名(漢字)
|
|
22
|
+
t.string :city, limit: 50, null: false # 市区町村名(漢字)
|
|
23
|
+
t.string :town, limit: 100, null: true # 町域名(漢字)- nil 可(大口事業所等)
|
|
24
|
+
t.string :kana_pref, limit: 20, null: true # 都道府県名(カナ)
|
|
25
|
+
t.string :kana_city, limit: 100, null: true # 市区町村名(カナ)
|
|
26
|
+
t.string :kana_town, limit: 200, null: true # 町域名(カナ)
|
|
27
|
+
t.boolean :has_alias, default: false # 通称あり(KEN_ALL フラグ)
|
|
28
|
+
t.boolean :is_partial, default: false # 丁目区分あり
|
|
29
|
+
t.boolean :is_large_office, default: false # 大口事業所専用フラグ
|
|
30
|
+
t.timestamps
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# インデックス
|
|
34
|
+
add_index :jp_address_complement_postal_codes, :postal_code # 完全一致
|
|
35
|
+
add_index :jp_address_complement_postal_codes, [:postal_code, :pref_code, :city, :town],
|
|
36
|
+
unique: true, name: 'idx_jp_address_complement_unique' # 冪等性保証
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### フィールド定義
|
|
40
|
+
|
|
41
|
+
| カラム名 | 型 | NOT NULL | 説明 |
|
|
42
|
+
|---------|-----|----------|------|
|
|
43
|
+
| `id` | bigint | ✅ | PK(自動採番) |
|
|
44
|
+
| `postal_code` | varchar(7) | ✅ | ハイフンなし7桁郵便番号 |
|
|
45
|
+
| `pref_code` | varchar(2) | ✅ | 都道府県コード(01〜47) |
|
|
46
|
+
| `pref` | varchar(10) | ✅ | 都道府県名(漢字) |
|
|
47
|
+
| `city` | varchar(50) | ✅ | 市区町村名(漢字) |
|
|
48
|
+
| `town` | varchar(100) | ❌ | 町域名(漢字)、以下一律で適用 |
|
|
49
|
+
| `kana_pref` | varchar(20) | ❌ | 都道府県カナ |
|
|
50
|
+
| `kana_city` | varchar(100) | ❌ | 市区町村カナ |
|
|
51
|
+
| `kana_town` | varchar(200) | ❌ | 町域カナ |
|
|
52
|
+
| `has_alias` | boolean | ✅ | 通称フラグ |
|
|
53
|
+
| `is_partial` | boolean | ✅ | 丁目区分フラグ |
|
|
54
|
+
| `is_large_office` | boolean | ✅ | 大口事業所フラグ |
|
|
55
|
+
| `created_at` | datetime | ✅ | 作成日時 |
|
|
56
|
+
| `updated_at` | datetime | ✅ | 更新日時 |
|
|
57
|
+
|
|
58
|
+
#### インデックス戦略
|
|
59
|
+
|
|
60
|
+
| インデックス名 | カラム | 用途 |
|
|
61
|
+
|--------------|--------|------|
|
|
62
|
+
| `idx_postal_code` | `(postal_code)` | US-1: 完全一致検索 O(log n) |
|
|
63
|
+
| `idx_jp_address_complement_unique` | `(postal_code, pref_code, city, town)` | 冪等 upsert の一意性制約 |
|
|
64
|
+
|
|
65
|
+
**先頭4桁前方一致**: `postal_code` への B-tree インデックスは `LIKE '1000%'` の先頭一致クエリに対して有効(PostgreSQL/MySQL/SQLite 共通)。追加インデックス不要。
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### 2. `AddressRecord` — Plain Ruby 値オブジェクト
|
|
70
|
+
|
|
71
|
+
DB から取得したデータを Repository が返す際の値オブジェクト。ActiveRecord インスタンスを返さないことでコアロジックの AR 依存を排除する。
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
module JpAddressComplement
|
|
75
|
+
# Ruby 3.2+ Data クラス(immutable struct)
|
|
76
|
+
AddressRecord = Data.define(
|
|
77
|
+
:postal_code, # String — 7桁、ハイフンなし
|
|
78
|
+
:pref_code, # String — 都道府県コード
|
|
79
|
+
:pref, # String — 都道府県名
|
|
80
|
+
:city, # String — 市区町村名
|
|
81
|
+
:town, # String? — 町域名(nilable)
|
|
82
|
+
:kana_pref, # String? — 都道府県カナ
|
|
83
|
+
:kana_city, # String? — 市区町村カナ
|
|
84
|
+
:kana_town, # String? — 町域カナ
|
|
85
|
+
:has_alias, # Boolean
|
|
86
|
+
:is_partial, # Boolean
|
|
87
|
+
:is_large_office # Boolean
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **不変(Immutable)**: `Data.define` により凍結された値オブジェクト。
|
|
93
|
+
- **Ruby 3.0 互換**: `Data` クラスは Ruby 3.2 以降。3.0/3.1 では `Struct.new(..., keyword_init: true)` にフォールバック。
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 3. `PostalCodeRepository` — データアクセス抽象化インターフェース
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
module JpAddressComplement
|
|
101
|
+
module Repositories
|
|
102
|
+
class PostalCodeRepository
|
|
103
|
+
# @param code [String] 正規化済み7桁郵便番号
|
|
104
|
+
# @return [Array<AddressRecord>]
|
|
105
|
+
def find_by_code(code)
|
|
106
|
+
raise NotImplementedError, "#{self.class}#find_by_code is not implemented"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @param prefix [String] 4桁以上の郵便番号プレフィックス
|
|
110
|
+
# @return [Array<AddressRecord>]
|
|
111
|
+
def find_by_prefix(prefix)
|
|
112
|
+
raise NotImplementedError, "#{self.class}#find_by_prefix is not implemented"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### 4. `ActiveRecordPostalCodeRepository` — Rails 向け実装
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
module JpAddressComplement
|
|
125
|
+
module Repositories
|
|
126
|
+
class ActiveRecordPostalCodeRepository < PostalCodeRepository
|
|
127
|
+
MODEL_CLASS = "JpAddressComplement::PostalCode" # AR モデル(遅延定数参照)
|
|
128
|
+
|
|
129
|
+
def find_by_code(code)
|
|
130
|
+
model.where(postal_code: code).map { to_record(_1) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def find_by_prefix(prefix)
|
|
134
|
+
model.where("postal_code LIKE ?", "#{prefix}%").map { to_record(_1) }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def model = Object.const_get(MODEL_CLASS)
|
|
140
|
+
|
|
141
|
+
def to_record(ar)
|
|
142
|
+
AddressRecord.new(
|
|
143
|
+
postal_code: ar.postal_code,
|
|
144
|
+
pref_code: ar.pref_code,
|
|
145
|
+
pref: ar.pref,
|
|
146
|
+
city: ar.city,
|
|
147
|
+
town: ar.town,
|
|
148
|
+
kana_pref: ar.kana_pref,
|
|
149
|
+
kana_city: ar.kana_city,
|
|
150
|
+
kana_town: ar.kana_town,
|
|
151
|
+
has_alias: ar.has_alias,
|
|
152
|
+
is_partial: ar.is_partial,
|
|
153
|
+
is_large_office: ar.is_large_office
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### 5. `PostalCode` — ActiveRecord モデル(Rails 環境のみ)
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
module JpAddressComplement
|
|
167
|
+
class PostalCode < ActiveRecord::Base
|
|
168
|
+
self.table_name = "jp_address_complement_postal_codes"
|
|
169
|
+
|
|
170
|
+
validates :postal_code, presence: true, format: { with: /\A\d{7}\z/ }
|
|
171
|
+
validates :pref, :city, presence: true
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## エンティティ関係図
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
KEN_ALL.CSV (Shift_JIS, 外部ファイル)
|
|
182
|
+
│
|
|
183
|
+
│ Rake Task (Shift_JIS→UTF-8 変換 + upsert_all, 1,000件/バッチ)
|
|
184
|
+
▼
|
|
185
|
+
jp_address_complement_postal_codes (利用アプリの DB テーブル)
|
|
186
|
+
│
|
|
187
|
+
│ ActiveRecordPostalCodeRepository#find_by_code / find_by_prefix
|
|
188
|
+
▼
|
|
189
|
+
AddressRecord(Plain Ruby 値オブジェクト)
|
|
190
|
+
│
|
|
191
|
+
│ JpAddressComplement::Searcher (コアロジック)
|
|
192
|
+
▼
|
|
193
|
+
呼び出し元(Rails アプリ / Rack アプリ / RSpec テスト)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## データ検証ルール
|
|
199
|
+
|
|
200
|
+
| ルール | 適用箇所 |
|
|
201
|
+
|--------|---------|
|
|
202
|
+
| 郵便番号は7桁数字のみ(正規化後) | `Normalizer` クラス、`PostalCode` モデル |
|
|
203
|
+
| 全角数字→半角正規化 | `Normalizer` クラス(入力受付時) |
|
|
204
|
+
| ハイフン除去(〒100-0001 → 1000001) | `Normalizer` クラス(入力受付時) |
|
|
205
|
+
| 4桁未満プレフィックスは空返却 | `Searcher#search_by_prefix` のガード節 |
|
|
206
|
+
| nil/空文字/数字以外はエラーなしで空返却 | `Normalizer` では nil 返却、`Searcher` でガード |
|
|
207
|
+
| 不正 CSV 行は警告ログ出力してスキップ | `Importer` クラス |
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Implementation Plan: 日本住所補完 Gem
|
|
2
|
+
|
|
3
|
+
**Branch**: `001-jp-address-complement-gem` | **Date**: 2026-02-22 | **Spec**: [spec.md](./spec.md)
|
|
4
|
+
**Input**: Feature specification from `/specs/001-jp-address-complement-gem/spec.md`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Summary
|
|
9
|
+
|
|
10
|
+
日本国内の住所を郵便番号から補完・検証できる Rails 7.x 以上向け Ruby gem を実装する。
|
|
11
|
+
**技術方針**: 住所データは利用アプリケーションの DB に永続化し(メモリロードなし)、インポートは Rake タスク経由とする。コア検索ロジックは Repository パターンで ActiveRecord 依存を排除し、Rails 以外の環境でも動作可能な設計とする。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Technical Context
|
|
16
|
+
|
|
17
|
+
**Language/Version**: Ruby 3.0+(3.2 以上推奨)
|
|
18
|
+
**Primary Dependencies**: Rails 7.x 以上、RSpec、SimpleCov、RuboCop
|
|
19
|
+
**Storage**: 利用アプリケーションの DB(ActiveRecord 対応 DB であれば何でも可)。開発・テスト環境は SQLite。
|
|
20
|
+
**Testing**: RSpec(カバレッジ 90% 以上、SimpleCov 計測)
|
|
21
|
+
**Target Platform**: Ruby gem(Rail Engine ではなく Railtie を使用)
|
|
22
|
+
**Project Type**: Ruby gem(Rails 組み込み型ライブラリ)
|
|
23
|
+
**Performance Goals**:
|
|
24
|
+
- 郵便番号完全一致検索: < 10ms p99(DBインデックス使用前提)
|
|
25
|
+
- 先頭4桁前方一致検索: < 50ms
|
|
26
|
+
- Rake インポート(12万件): < 60秒
|
|
27
|
+
**Constraints**: Rubocop 100% PASS(disable 禁止)、TDD 必須
|
|
28
|
+
**Scale/Scope**: 全国郵便番号(約12万件)を管理
|
|
29
|
+
**Encoding**: KEN_ALL.CSV は Shift_JIS。Rake タスクが UTF-8 に自動変換。
|
|
30
|
+
**Batch Strategy**: `upsert_all` + 1,000件/バッチ分割。冪等性は `(postal_code, pref_code, city, town)` の一意制約で保証。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Constitution Check
|
|
35
|
+
|
|
36
|
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
37
|
+
|
|
38
|
+
- [x] **I. Gem-First**: `JpAddressComplement` 名前空間に閉じる。Railtie 経由で Rails 統合。Rails 非依存コアロジックを Repository パターンで実現。
|
|
39
|
+
- [x] **II. TDD**: テストを先に書く。RSpec + SimpleCov カバレッジ 90%+ の計画あり。FakeRepository でコアロジックを DB なしテスト可能。
|
|
40
|
+
- [x] **III. Rubocop**: `rubocop:disable` 使用禁止。`.rubocop.yml` で設定変更時は理由明記。CI に Rubocop ゲートを設定。
|
|
41
|
+
- [x] **IV. データ整合性**: `upsert_all`(ON CONFLICT DO UPDATE)で冪等インポート。1,000件/バッチで DB ロック最小化。Shift_JIS→UTF-8 変換を Rake タスクで自動実行。
|
|
42
|
+
- [x] **V. 機能要件**: 3機能(`search_by_postal_code` / `search_by_postal_code_prefix` / `valid_combination?`)のインターフェースを contracts/public-api.md で明確化。引数バリデーション・空返却挙動を定義済み。
|
|
43
|
+
- [x] **VI. シンプルさ**: Repository パターンの採用は FR-010/FR-015(Rails 非依存要件)で正当化済み。DI は Devise/Kaminari と同レベルのシンプルなコンストラクタ注入で実現。YAGNI 遵守(差分インポート・RBS・rom-rb は Deferred)。
|
|
44
|
+
|
|
45
|
+
**Constitution Check Result**: ✅ 全ゲート PASS
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Project Structure
|
|
50
|
+
|
|
51
|
+
### Documentation (this feature)
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
specs/001-jp-address-complement-gem/
|
|
55
|
+
├── plan.md ✅ このファイル
|
|
56
|
+
├── spec.md ✅ 機能仕様
|
|
57
|
+
├── research.md ✅ Phase 0 調査結果
|
|
58
|
+
├── data-model.md ✅ データモデル設計
|
|
59
|
+
├── quickstart.md ✅ 利用者向けガイド
|
|
60
|
+
├── contracts/
|
|
61
|
+
│ └── public-api.md ✅ 公開 API コントラクト
|
|
62
|
+
└── tasks.md (/speckit.tasks コマンドで生成)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Source Code (repository root)
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
jp_address_complement.gemspec
|
|
69
|
+
lib/
|
|
70
|
+
├── jp_address_complement.rb # エントリポイント・モジュールメソッド定義
|
|
71
|
+
├── jp_address_complement/
|
|
72
|
+
│ ├── version.rb # VERSION 定数
|
|
73
|
+
│ ├── configuration.rb # Config クラス(repository DI 設定)
|
|
74
|
+
│ ├── railtie.rb # Rails Railtie(Rails 統合・initializer)
|
|
75
|
+
│ ├── address_record.rb # AddressRecord 値オブジェクト(Data/Struct)
|
|
76
|
+
│ ├── normalizer.rb # 郵便番号正規化(全角→半角・ハイフン除去)
|
|
77
|
+
│ ├── searcher.rb # コア検索ロジック(Repository 経由)
|
|
78
|
+
│ ├── repositories/
|
|
79
|
+
│ │ ├── postal_code_repository.rb # 抽象基底クラス(インターフェース)
|
|
80
|
+
│ │ └── active_record_postal_code_repository.rb # ActiveRecord 実装
|
|
81
|
+
│ ├── models/
|
|
82
|
+
│ │ └── postal_code.rb # ActiveRecord モデル
|
|
83
|
+
│ ├── validators/
|
|
84
|
+
│ │ └── jp_address_complement_validator.rb # ActiveModel バリデーター
|
|
85
|
+
│ ├── importers/
|
|
86
|
+
│ │ └── csv_importer.rb # CSV インポートロジック
|
|
87
|
+
│ └── generators/
|
|
88
|
+
│ └── jp_address_complement/
|
|
89
|
+
│ ├── install_generator.rb # rails g jp_address_complement:install
|
|
90
|
+
│ └── templates/
|
|
91
|
+
│ └── create_postal_codes.rb.erb # マイグレーションテンプレート
|
|
92
|
+
|
|
93
|
+
lib/tasks/
|
|
94
|
+
└── jp_address_complement.rake # jp_address_complement:import タスク
|
|
95
|
+
|
|
96
|
+
spec/
|
|
97
|
+
├── spec_helper.rb
|
|
98
|
+
├── jp_address_complement_spec.rb # トップレベルモジュールメソッドの統合テスト
|
|
99
|
+
├── normalizer_spec.rb
|
|
100
|
+
├── searcher_spec.rb
|
|
101
|
+
├── repositories/
|
|
102
|
+
│ └── active_record_repository_spec.rb
|
|
103
|
+
├── validators/
|
|
104
|
+
│ └── jp_address_complement_validator_spec.rb
|
|
105
|
+
├── importers/
|
|
106
|
+
│ └── csv_importer_spec.rb
|
|
107
|
+
├── generators/
|
|
108
|
+
│ └── install_generator_spec.rb
|
|
109
|
+
└── support/
|
|
110
|
+
├── fake_repository.rb # テスト用 FakeRepository
|
|
111
|
+
└── database_helper.rb
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Structure Decision**: Ruby gem の標準構成(`lib/`, `spec/`, `lib/tasks/`)を採用。Generator テンプレートは Rails のコンベンションに従い `lib/generators/` 配下に配置。
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Complexity Tracking
|
|
119
|
+
|
|
120
|
+
| 複雑化要素 | 正当化理由 | より単純な代替案が却下される理由 |
|
|
121
|
+
|-----------|-----------|-------------------------------|
|
|
122
|
+
| Repository パターン | FR-010/FR-015: Rails 非依存コアが必須 | ActiveRecord 直接依存では Rack/Sinatra 環境での利用不可 |
|
|
123
|
+
| Railtie + Generator | FR-014: Rails 標準のマイグレーション管理 | 自動テーブル作成は利用者の制御を奪い、既存マイグレーション設計に干渉する |
|
|
124
|
+
| upsert_all バッチ分割 | SC-004: 60秒以内インポート+ Constitution IV の冪等性要件 | 逐次 `find_or_create_by` では12万件に数分かかり SC-004 を満足できない |
|