basho 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5f5323bdc5e31562bad98e15914bb2c5f44d251331680bff9d72f62e98f3dd5
4
- data.tar.gz: 2630ca02ae2b433e1ac8b07bd01ed2a433cb9a4c71129203f7eba97a33288f26
3
+ metadata.gz: a14446b90ac21440261e1558b52c2341bbe32fb4ab719c2b04932c3a7b34a5cf
4
+ data.tar.gz: 4416cb182886440853437ebc63c85d60028063fb9d282edd9a61883cb90060e5
5
5
  SHA512:
6
- metadata.gz: a8568a1295db1355c61d52ac255ed88d6022c2443737511b68e7b94f1e99695881fb715d5d9ffc8eb54751f5229a3b9198bb7f2595ccc61361cf7fd90d2e7e22
7
- data.tar.gz: d97d0878f043c93760069c843d0908649f5d152be57435b976cc21306fc1a61b217a3000a4bb76f81a131ac3be6f28dc43908023f45a77dfb820302e4413e003
6
+ metadata.gz: 01632dfab1fcbd4c3186cf289ceb9bcd0ddbe6aee1ad8253306e1c2cb08ea676b0d84cda27c7c7bbff7de47dd802c651eb876104e4d221793e0cffe7c868936d
7
+ data.tar.gz: cda91a973465799bc067da98d038339ad71cd9095be33fe7f86f7ab0679de57ad7c7927798bce0451a1a65c6d174bddaad9ae4eea086568035558a4269d2942e
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --markup markdown
2
+ --charset utf-8
3
+ --output-dir doc
4
+ lib/**/*.rb
5
+ -
6
+ README.md
7
+ CHANGELOG.md
8
+ LICENSE.txt
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2026-02-11
9
+
10
+ ### Added
11
+
12
+ - オプションDBバックエンド(`basho_prefectures` / `basho_cities` テーブル)
13
+ - `rails generate basho:install_tables` マイグレーションジェネレータ
14
+ - `rails basho:seed` Rakeタスク(冪等、JSON→DB一括投入)
15
+ - `Basho::DB::Prefecture` / `Basho::DB::City` ActiveRecordモデル
16
+ - `Basho.db?` によるDB自動検出(スレッドセーフ、キャッシュ付き)
17
+ - テーブルが存在すれば公開API(`Prefecture.find`, `City.where`等)が自動でDB経由に切り替わる
18
+ - READMEにDB Backendセクション追加(EN/JA)
19
+
8
20
  ## [0.3.0] - 2026-02-11
9
21
 
10
22
  ### Changed
@@ -85,6 +97,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
85
97
  - GitHub Actions CI(Ruby 3.2/3.3/3.4)
86
98
  - 月次データ自動更新ワークフロー
87
99
 
100
+ [0.4.0]: https://github.com/wagai/basho/releases/tag/v0.4.0
88
101
  [0.3.0]: https://github.com/wagai/basho/releases/tag/v0.3.0
89
102
  [0.2.2]: https://github.com/wagai/basho/releases/tag/v0.2.2
90
103
  [0.2.1]: https://github.com/wagai/basho/releases/tag/v0.2.1
data/README.ja.md CHANGED
@@ -18,6 +18,7 @@ Bashoはこれらをまとめて解決します。
18
18
  ## 特徴
19
19
 
20
20
  - **DBマイグレーション不要** -- 全データをJSON同梱。`gem install`だけで使える
21
+ - **オプションDBバックエンド** -- テーブル生成でJOINや外部キー制約に対応。APIは自動で切り替わる
21
22
  - **フレームワーク非依存** -- 素のRuby、Sinatra、Rails API only、どこでも動く
22
23
  - **ActiveRecord統合** -- `include Basho` + 1行のマクロで郵便番号→住所の自動保存
23
24
  - **Hotwire対応** -- Turbo Frame + Stimulusによる郵便番号自動入力をビルトインEngine提供
@@ -388,6 +389,76 @@ class Shop < ApplicationRecord
388
389
  end
389
390
  ```
390
391
 
392
+ ## DBバックエンド(オプション)
393
+
394
+ デフォルトではBashoは同梱のJSONファイルからデータを読み込みます。DB不要です。JOINや外部キー制約が必要な場合、または自前のテーブルから`basho_prefectures` / `basho_cities`を参照したい場合は、オプションでDBテーブルを生成できます。
395
+
396
+ ### セットアップ
397
+
398
+ ```bash
399
+ rails generate basho:install_tables
400
+ rails db:migrate
401
+ rails basho:seed
402
+ ```
403
+
404
+ 2つのテーブルが作成されます:
405
+
406
+ | テーブル | 主キー | 件数 |
407
+ |---------|--------|------|
408
+ | `basho_prefectures` | `code` (integer) | 47 |
409
+ | `basho_cities` | `code` (string, 6桁) | 約1,900 |
410
+
411
+ ### 透過的な自動切り替え
412
+
413
+ テーブルが存在すれば、公開API(`Basho::Prefecture.find`、`Basho::City.where`等)は自動的にDBバックエンドを使います。**コード変更は不要です。**
414
+
415
+ ```ruby
416
+ # DBテーブルの有無に関わらず同じコードで動く
417
+ Basho::Prefecture.find(13).name # => "東京都"
418
+ Basho::City.find("131016").full_name # => "千代田区"
419
+ Basho::City.where(prefecture_code: 13) # => Array
420
+ ```
421
+
422
+ 検出は初回アクセス時に1度だけ行われ、プロセスの生存期間中キャッシュされます。
423
+
424
+ ### 自前テーブルからの外部キー
425
+
426
+ ```ruby
427
+ # アプリ側のマイグレーション
428
+ add_foreign_key :shops, :basho_cities, column: :city_code, primary_key: :code
429
+ add_foreign_key :shops, :basho_prefectures, column: :prefecture_code, primary_key: :code
430
+ ```
431
+
432
+ ### DBモデルの直接利用
433
+
434
+ ActiveRecordの機能(JOIN、スコープ、eager loading)が必要な場合は、DBモデルを直接使います:
435
+
436
+ ```ruby
437
+ # JOIN
438
+ Basho::DB::City.joins(:prefecture).where(basho_prefectures: { region_name: "関東" })
439
+
440
+ # Eager loading
441
+ Basho::DB::Prefecture.includes(:cities).find(13)
442
+
443
+ # アソシエーション
444
+ prefecture = Basho::DB::Prefecture.find(13)
445
+ prefecture.cities # => ActiveRecord::Relation
446
+ prefecture.capital # => Basho::DB::City
447
+ ```
448
+
449
+ ### 再シード
450
+
451
+ `basho:seed`は冪等です。gem更新後に再実行すればデータが更新されます:
452
+
453
+ ```bash
454
+ rails basho:seed
455
+ ```
456
+
457
+ ### 補足
458
+
459
+ - 郵便番号はDBテーブルに**含まれません**(12万件以上、月次更新のため)。常にJSONファイルから提供されます。
460
+ - `Basho.db?`はスレッドセーフでキャッシュされます。再検出が必要な場合(テスト中のマイグレーション後など)は`Basho.reset_db_cache!`を使ってください。
461
+
391
462
  ## データソース
392
463
 
393
464
  | データ | ソース | 更新頻度 |
data/README.md CHANGED
@@ -18,6 +18,7 @@ Basho solves all of these.
18
18
  ## Features
19
19
 
20
20
  - **No DB migrations** -- All data is bundled as JSON. Just `gem install` and go
21
+ - **Optional DB backend** -- Generate tables for JOINs and foreign keys. The API auto-switches transparently
21
22
  - **Framework-agnostic** -- Works with plain Ruby, Sinatra, Rails API-only, or any Ruby app
22
23
  - **ActiveRecord integration** -- `include Basho` + a one-line macro for automatic postal code to address resolution on save
23
24
  - **Hotwire-ready** -- Built-in Rails Engine with postal code auto-fill via Turbo Frame + Stimulus
@@ -388,6 +389,76 @@ class Shop < ApplicationRecord
388
389
  end
389
390
  ```
390
391
 
392
+ ## DB Backend (Optional)
393
+
394
+ By default, Basho loads all data from bundled JSON files -- no database needed. If you need JOINs, foreign key constraints, or want to reference `basho_prefectures` / `basho_cities` from your own tables, you can optionally generate DB tables.
395
+
396
+ ### Setup
397
+
398
+ ```bash
399
+ rails generate basho:install_tables
400
+ rails db:migrate
401
+ rails basho:seed
402
+ ```
403
+
404
+ This creates two tables:
405
+
406
+ | Table | Primary Key | Rows |
407
+ |-------|------------|------|
408
+ | `basho_prefectures` | `code` (integer) | 47 |
409
+ | `basho_cities` | `code` (string, 6-digit) | ~1,900 |
410
+
411
+ ### Transparent Auto-switching
412
+
413
+ Once the tables exist, the public API (`Basho::Prefecture.find`, `Basho::City.where`, etc.) automatically uses the DB backend. **No code changes required.**
414
+
415
+ ```ruby
416
+ # Works exactly the same whether DB tables exist or not
417
+ Basho::Prefecture.find(13).name # => "東京都"
418
+ Basho::City.find("131016").full_name # => "千代田区"
419
+ Basho::City.where(prefecture_code: 13) # => Array
420
+ ```
421
+
422
+ Detection happens once on first access via `Basho.db?` and is cached for the process lifetime.
423
+
424
+ ### Foreign Keys from Your Tables
425
+
426
+ ```ruby
427
+ # Your app migration
428
+ add_foreign_key :shops, :basho_cities, column: :city_code, primary_key: :code
429
+ add_foreign_key :shops, :basho_prefectures, column: :prefecture_code, primary_key: :code
430
+ ```
431
+
432
+ ### Direct DB Model Access
433
+
434
+ When you need ActiveRecord features (JOINs, scopes, eager loading), use the DB models directly:
435
+
436
+ ```ruby
437
+ # JOINs
438
+ Basho::DB::City.joins(:prefecture).where(basho_prefectures: { region_name: "関東" })
439
+
440
+ # Eager loading
441
+ Basho::DB::Prefecture.includes(:cities).find(13)
442
+
443
+ # Associations
444
+ prefecture = Basho::DB::Prefecture.find(13)
445
+ prefecture.cities # => ActiveRecord::Relation
446
+ prefecture.capital # => Basho::DB::City
447
+ ```
448
+
449
+ ### Re-seeding
450
+
451
+ `basho:seed` is idempotent. Run it again after updating the gem to refresh the data:
452
+
453
+ ```bash
454
+ rails basho:seed
455
+ ```
456
+
457
+ ### Notes
458
+
459
+ - Postal codes are **not** included in the DB tables (120k+ rows, updated monthly). They are always served from bundled JSON files.
460
+ - `Basho.db?` is thread-safe and cached. Use `Basho.reset_db_cache!` if you need to re-detect (e.g. after running migrations in a test).
461
+
391
462
  ## Data Sources
392
463
 
393
464
  | Data | Source | Update Frequency |
@@ -3,9 +3,22 @@
3
3
  require_relative "postal_auto_resolve"
4
4
 
5
5
  module Basho
6
+ # ActiveRecord統合機能を提供する名前空間。
6
7
  module ActiveRecord
7
- # ActiveRecordモデルにbasho / basho_postalマクロを提供する
8
+ # ActiveRecordモデルに +basho+ / +basho_postal+ マクロを提供するモジュール。
9
+ # +include Basho+ で自動的に extend される。
10
+ #
11
+ # @example
12
+ # class Shop < ApplicationRecord
13
+ # include Basho
14
+ # basho :city_code
15
+ # basho_postal :postal_code, prefecture: :pref_name, city: :city_name
16
+ # end
8
17
  module Base
18
+ # 自治体コードカラムから +city+, +prefecture+, +full_address+ メソッドを定義する。
19
+ #
20
+ # @param column [Symbol, String] 6桁自治体コードを格納するカラム名
21
+ # @return [void]
9
22
  def basho(column)
10
23
  column_name = column.to_s
11
24
 
@@ -18,6 +31,17 @@ module Basho
18
31
  end
19
32
  end
20
33
 
34
+ # 郵便番号カラムから +postal_address+ メソッドを定義する。
35
+ # マッピングオプションを渡すと +before_save+ で住所カラムを自動入力する。
36
+ #
37
+ # @param column [Symbol, String] 郵便番号を格納するカラム名
38
+ # @param mappings [Hash] マッピングオプション
39
+ # @option mappings [Symbol] :prefecture 都道府県名の保存先カラム
40
+ # @option mappings [Symbol] :city 市区町村名の保存先カラム
41
+ # @option mappings [Symbol] :town 町域名の保存先カラム
42
+ # @option mappings [Symbol] :prefecture_code 都道府県コードの保存先カラム
43
+ # @option mappings [Symbol] :city_code 自治体コードの保存先カラム
44
+ # @return [void]
21
45
  def basho_postal(column, **mappings)
22
46
  column_name = column.to_s
23
47
 
@@ -2,12 +2,22 @@
2
2
 
3
3
  module Basho
4
4
  module ActiveRecord
5
- # 郵便番号変更時にbefore_saveで住所カラムを自動解決する
5
+ # 郵便番号変更時に +before_save+ で住所カラムを自動解決する。
6
+ # {Base#basho_postal} のマッピングオプション指定時に使用される。
7
+ #
8
+ # @api private
6
9
  module PostalAutoResolve
10
+ # サポートするマッピングキーの一覧
7
11
  MAPPING_KEYS = %i[prefecture city town prefecture_code city_code].freeze
8
12
 
9
13
  module_function
10
14
 
15
+ # モデルに +before_save+ コールバックを登録する。
16
+ #
17
+ # @param model_class [Class] ActiveRecordモデルクラス
18
+ # @param postal_column [String] 郵便番号カラム名
19
+ # @param mappings [Hash] マッピング設定
20
+ # @return [void]
11
21
  def install(model_class, postal_column, mappings)
12
22
  unless model_class.respond_to?(:before_save)
13
23
  raise Basho::Error, "#{model_class} does not support before_save callbacks"
@@ -27,6 +37,11 @@ module Basho
27
37
  end
28
38
  end
29
39
 
40
+ # マッピングキーに対応する値を郵便番号データから解決する。
41
+ #
42
+ # @param postal [PostalCode, nil] 郵便番号データ
43
+ # @param key [Symbol] マッピングキー
44
+ # @return [String, Integer, nil]
30
45
  def resolve_value(postal, key)
31
46
  return nil unless postal
32
47
 
@@ -39,6 +54,10 @@ module Basho
39
54
  end
40
55
  end
41
56
 
57
+ # 郵便番号データから自治体コードを逆引きする。
58
+ #
59
+ # @param postal [PostalCode] 郵便番号データ
60
+ # @return [String, nil] 6桁自治体コード
42
61
  def resolve_city_code(postal)
43
62
  City.where(prefecture_code: postal.prefecture_code)
44
63
  .find { |c| c.full_name == postal.city_name }&.code
data/lib/basho/city.rb CHANGED
@@ -1,33 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
+ # 市区町村を表すイミュータブルなデータクラス。
5
+ #
6
+ # DBバックエンドが有効な場合、クラスメソッドは自動的に {DB::City} 経由で検索する。
7
+ #
8
+ # @!attribute [r] code
9
+ # @return [String] 6桁自治体コード(JIS X 0401 + チェックディジット、例: "131016")
10
+ # @!attribute [r] prefecture_code
11
+ # @return [Integer] 都道府県コード(1〜47)
12
+ # @!attribute [r] name
13
+ # @return [String] 市区町村名(例: "千代田区")
14
+ # @!attribute [r] name_kana
15
+ # @return [String] カタカナ名(例: "チヨダク")
16
+ # @!attribute [r] district
17
+ # @return [String, nil] 郡名(例: "島尻郡")。郡に属する町村のみ設定
18
+ # @!attribute [r] capital
19
+ # @return [Boolean] 県庁所在地フラグ
4
20
  City = ::Data.define(:code, :prefecture_code, :name, :name_kana, :district, :capital) do
5
21
  def initialize(district: nil, capital: false, **)
6
22
  super
7
23
  end
8
24
 
25
+ # 県庁所在地かどうかを返す。
26
+ #
27
+ # @return [Boolean]
9
28
  def capital? = capital
10
29
 
30
+ # 郡名付きの正式名を返す。郡がない場合は {#name} と同じ。
31
+ #
32
+ # @return [String] 例: "島尻郡八重瀬町"
11
33
  def full_name
12
34
  district ? "#{district}#{name}" : name
13
35
  end
14
36
 
37
+ # 所属する都道府県を返す。
38
+ #
39
+ # @return [Prefecture, DB::Prefecture]
15
40
  def prefecture
16
41
  Prefecture.find(prefecture_code)
17
42
  end
18
43
 
19
44
  class << self
45
+ # 6桁自治体コードで市区町村を検索する。
46
+ #
47
+ # @param code [String] 6桁自治体コード(例: "131016")
48
+ # @return [City, DB::City, nil]
20
49
  def find(code)
21
50
  return nil unless code.is_a?(String) && code.size == 6
51
+ return DB::City.find_by(code: code) if Basho.db?
22
52
 
23
- prefecture_code = code[0..1].to_i
24
- where(prefecture_code: prefecture_code).find { |city| city.code == code }
53
+ pref_code = code[0..1].to_i
54
+ where(prefecture_code: pref_code).find { |city| city.code == code }
25
55
  end
26
56
 
57
+ # 都道府県コードで市区町村を絞り込む。
58
+ #
59
+ # @param prefecture_code [Integer] 都道府県コード
60
+ # @return [Array<City>, Array<DB::City>]
27
61
  def where(prefecture_code:)
62
+ return DB::City.where(prefecture_code: prefecture_code).to_a if Basho.db?
63
+
28
64
  Data::Loader.cities(prefecture_code).map { |data| new(**data) }
29
65
  end
30
66
 
67
+ # JIS X 0401 チェックディジットで自治体コードを検証する。
68
+ #
69
+ # @param code [String] 6桁自治体コード
70
+ # @return [Boolean]
31
71
  def valid_code?(code)
32
72
  CodeValidator.valid?(code)
33
73
  end
@@ -1,14 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
- # JIS X 0401 チェックディジットによる自治体コード検証
4
+ # JIS X 0401 チェックディジットによる6桁自治体コードの検証。
5
+ #
6
+ # @example
7
+ # Basho::CodeValidator.valid?("131016") # => true
8
+ # Basho::CodeValidator.valid?("131019") # => false
5
9
  module CodeValidator
10
+ # チェックディジットの桁位置(0始まり)
6
11
  CHECK_DIGITS_INDEX = 5
12
+ # モジュラス演算の基数
7
13
  CHECK_BASE = 11
14
+ # 有効な都道府県コード範囲
8
15
  PREFECTURE_RANGE = (1..47)
9
16
 
10
17
  module_function
11
18
 
19
+ # 自治体コードの形式とチェックディジットを検証する。
20
+ #
21
+ # @param code [String] 6桁自治体コード
22
+ # @return [Boolean]
12
23
  def valid?(code)
13
24
  return false unless code.is_a?(String) && code.match?(/\A\d{6}\z/)
14
25
  return false unless PREFECTURE_RANGE.cover?(code[0..1].to_i)
@@ -16,6 +27,10 @@ module Basho
16
27
  code[CHECK_DIGITS_INDEX].to_i == compute_check_digit(code)
17
28
  end
18
29
 
30
+ # チェックディジットを計算する。
31
+ #
32
+ # @param code [String] 6桁自治体コード
33
+ # @return [Integer] 0〜9のチェックディジット
19
34
  def compute_check_digit(code)
20
35
  sub_total = code.chars
21
36
  .take(CHECK_DIGITS_INDEX)
@@ -3,24 +3,43 @@
3
3
  require "json"
4
4
 
5
5
  module Basho
6
+ # 同梱データの読み込みに関する名前空間。
6
7
  module Data
7
- # 組み込みJSONデータの遅延読み込みとキャッシュ
8
+ # 同梱JSONデータの遅延読み込みとキャッシュ。
9
+ # 各メソッドは初回呼び出し時にJSONファイルを読み込み、以降はキャッシュを返す。
10
+ #
11
+ # @api private
8
12
  class Loader
13
+ # 同梱JSONデータのディレクトリパス
9
14
  DATA_DIR = File.expand_path("../../../data", __dir__)
10
15
 
11
16
  class << self
17
+ # 全47都道府県データを返す。
18
+ #
19
+ # @return [Array<Hash>]
12
20
  def prefectures
13
21
  @prefectures ||= load_json("prefectures.json")
14
22
  end
15
23
 
24
+ # 指定した都道府県の市区町村データを返す。
25
+ #
26
+ # @param prefecture_code [Integer] 都道府県コード
27
+ # @return [Array<Hash>]
16
28
  def cities(prefecture_code)
17
29
  cities_cache[prefecture_code] ||= load_json("cities/#{format("%02d", prefecture_code)}.json")
18
30
  end
19
31
 
32
+ # 指定したプレフィックスの郵便番号データを返す。
33
+ #
34
+ # @param prefix [String] 3桁プレフィックス(例: "154")
35
+ # @return [Array<Hash>]
20
36
  def postal_codes(prefix)
21
37
  postal_cache[prefix] ||= load_json("postal_codes/#{prefix}.json")
22
38
  end
23
39
 
40
+ # 全キャッシュをクリアする。
41
+ #
42
+ # @return [void]
24
43
  def reset!
25
44
  instance_variables.each { |var| remove_instance_variable(var) }
26
45
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basho
4
+ module DB
5
+ # 市区町村のActiveRecordモデル(+basho_cities+ テーブル)。
6
+ # メモリ版 {Basho::City} と同じAPI(+full_name+, +capital?+)を提供する。
7
+ class City < ::ActiveRecord::Base
8
+ self.table_name = "basho_cities"
9
+ self.primary_key = "code"
10
+
11
+ belongs_to :prefecture,
12
+ class_name: "Basho::DB::Prefecture",
13
+ foreign_key: :prefecture_code,
14
+ inverse_of: :cities
15
+
16
+ # 郡名付きの正式名を返す。
17
+ #
18
+ # @return [String]
19
+ def full_name
20
+ district ? "#{district}#{name}" : name
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basho
4
+ module DB
5
+ # 都道府県のActiveRecordモデル(+basho_prefectures+ テーブル)。
6
+ # メモリ版 {Basho::Prefecture} と同じAPI(+type+, +region+, +capital+)を提供する。
7
+ class Prefecture < ::ActiveRecord::Base
8
+ self.table_name = "basho_prefectures"
9
+ self.primary_key = "code"
10
+
11
+ has_many :cities,
12
+ class_name: "Basho::DB::City",
13
+ foreign_key: :prefecture_code,
14
+ inverse_of: :prefecture
15
+
16
+ # @return [String] 種別("都" / "道" / "府" / "県")
17
+ def type = prefecture_type
18
+
19
+ # @return [Region]
20
+ def region = Region.find(region_name)
21
+
22
+ # @return [DB::City, nil] 県庁所在地
23
+ def capital = capital_code && Basho::DB::City.find_by(code: capital_code)
24
+ end
25
+ end
26
+ end
data/lib/basho/db.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require_relative "db/prefecture"
5
+ require_relative "db/city"
6
+
7
+ module Basho
8
+ # ActiveRecordバックエンド(オプション)。
9
+ # +basho_prefectures+ / +basho_cities+ テーブルへのアクセスとシードを提供する。
10
+ module DB
11
+ # JSONデータをDBに一括投入する。冪等(何度実行しても同じ結果)。
12
+ #
13
+ # @return [Hash{Symbol => Integer}] 投入件数(+:prefectures+, +:cities+)
14
+ def self.seed!
15
+ prefs = prefecture_rows
16
+ cities = city_rows
17
+
18
+ ::ActiveRecord::Base.transaction do
19
+ City.delete_all
20
+ Prefecture.delete_all
21
+
22
+ Prefecture.insert_all!(prefs)
23
+ City.insert_all!(cities)
24
+ end
25
+
26
+ { prefectures: prefs.size, cities: cities.size }
27
+ end
28
+
29
+ def self.prefecture_rows
30
+ Data::Loader.prefectures.map do |pref|
31
+ pref.except(:type).merge(prefecture_type: pref[:type])
32
+ end
33
+ end
34
+ private_class_method :prefecture_rows
35
+
36
+ def self.city_rows
37
+ (1..47).flat_map do |code|
38
+ Data::Loader.cities(code).map do |city|
39
+ { district: nil, capital: false, **city }
40
+ end
41
+ end
42
+ end
43
+ private_class_method :city_rows
44
+ end
45
+ end
data/lib/basho/engine.rb CHANGED
@@ -18,5 +18,9 @@ module Basho
18
18
  initializer "basho.assets" do |app|
19
19
  app.config.assets.paths << root.join("app/assets/javascripts") if app.config.respond_to?(:assets)
20
20
  end
21
+
22
+ rake_tasks do
23
+ load File.expand_path("../../tasks/basho.rake", __dir__)
24
+ end
21
25
  end
22
26
  end
@@ -1,24 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
+ # 郵便番号を表すイミュータブルなデータクラス。
5
+ # 常にJSONファイルから読み込む(DBバックエンド対象外)。
6
+ #
7
+ # @!attribute [r] code
8
+ # @return [String] 7桁郵便番号(ハイフンなし、例: "1540011")
9
+ # @!attribute [r] prefecture_code
10
+ # @return [Integer] 都道府県コード(1〜47)
11
+ # @!attribute [r] city_name
12
+ # @return [String] 市区町村名(例: "世田谷区")
13
+ # @!attribute [r] town
14
+ # @return [String] 町域名(例: "上馬")
4
15
  PostalCode = ::Data.define(:code, :prefecture_code, :city_name, :town) do
16
+ # ハイフン付きの郵便番号を返す。
17
+ #
18
+ # @return [String] 例: "154-0011"
5
19
  def formatted_code
6
20
  "#{code[0..2]}-#{code[3..]}"
7
21
  end
8
22
 
23
+ # 都道府県名を返す。
24
+ #
25
+ # @return [String, nil] 例: "東京都"
9
26
  def prefecture_name
10
27
  prefecture&.name
11
28
  end
12
29
 
30
+ # 所属する都道府県を返す。
31
+ #
32
+ # @return [Prefecture, DB::Prefecture, nil]
13
33
  def prefecture
14
34
  Prefecture.find(prefecture_code)
15
35
  end
16
36
 
17
37
  class << self
38
+ # 郵便番号で検索する。ハイフン有無どちらも可。
39
+ #
40
+ # @param code [String] 郵便番号(例: "154-0011", "1540011")
41
+ # @return [PostalCode, nil]
18
42
  def find(code)
19
43
  where(code: code).first
20
44
  end
21
45
 
46
+ # 郵便番号で検索し、配列で返す。共有郵便番号の場合は複数件。
47
+ #
48
+ # @param code [String] 郵便番号
49
+ # @return [Array<PostalCode>]
22
50
  def where(code:)
23
51
  normalized = code.to_s.delete("-")
24
52
  return [] unless normalized.match?(/\A\d{7}\z/)
@@ -1,46 +1,94 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
+ # 都道府県を表すイミュータブルなデータクラス。
5
+ #
6
+ # DBバックエンドが有効な場合、クラスメソッドは自動的に {DB::Prefecture} 経由で検索する。
7
+ #
8
+ # @!attribute [r] code
9
+ # @return [Integer] JIS X 0401 都道府県コード(1〜47)
10
+ # @!attribute [r] name
11
+ # @return [String] 都道府県名(例: "東京都")
12
+ # @!attribute [r] name_en
13
+ # @return [String] 英語名(例: "Tokyo")
14
+ # @!attribute [r] name_kana
15
+ # @return [String] カタカナ名(例: "トウキョウト")
16
+ # @!attribute [r] name_hiragana
17
+ # @return [String] ひらがな名(例: "とうきょうと")
18
+ # @!attribute [r] region_name
19
+ # @return [String] 地方名(例: "関東")
20
+ # @!attribute [r] type
21
+ # @return [String] 種別("都" / "道" / "府" / "県")
22
+ # @!attribute [r] capital_code
23
+ # @return [String] 県庁所在地の6桁自治体コード(例: "131041")
4
24
  Prefecture = ::Data.define(:code, :name, :name_en, :name_kana, :name_hiragana, :region_name, :type, :capital_code) do
25
+ # 所属する地方を返す。
26
+ #
27
+ # @return [Region]
5
28
  def region
6
29
  Region.find(region_name)
7
30
  end
8
31
 
32
+ # 所属する市区町村の一覧を返す。
33
+ #
34
+ # @return [Array<City>, Array<DB::City>]
9
35
  def cities
10
36
  City.where(prefecture_code: code)
11
37
  end
12
38
 
39
+ # 県庁所在地を返す。
40
+ #
41
+ # @return [City, DB::City, nil]
13
42
  def capital
14
43
  City.find(capital_code)
15
44
  end
16
45
 
17
46
  class << self
47
+ # 全47都道府県を返す。
48
+ #
49
+ # @return [Array<Prefecture>, Array<DB::Prefecture>]
18
50
  def all
19
- @all ||= Data::Loader.prefectures.map { |data| new(**data) }
51
+ return DB::Prefecture.all.to_a if Basho.db?
52
+
53
+ @all ||= Data::Loader.prefectures.map { |data| new(**data) }.freeze
20
54
  end
21
55
 
56
+ # 都道府県を検索する。
57
+ #
58
+ # @overload find(code)
59
+ # @param code [Integer] 都道府県コード
60
+ # @overload find(name:)
61
+ # @param name [String] 日本語名(例: "東京都")
62
+ # @overload find(name_en:)
63
+ # @param name_en [String] 英語名(例: "Tokyo")
64
+ # @return [Prefecture, DB::Prefecture, nil]
22
65
  def find(code = nil, **options)
23
- if code
24
- all.find { |pref| pref.code == code }
25
- elsif options.any?
26
- find_by_options(options)
27
- end
66
+ attrs = code.nil? ? options : { code: code }
67
+ return if attrs.empty?
68
+
69
+ key, value = attrs.first
70
+ return DB::Prefecture.find_by(key => value) if Basho.db?
71
+
72
+ all.find { |pref| pref.public_send(key) == value }
28
73
  end
29
74
 
75
+ # 都道府県を地方名で絞り込む。引数なしで全件返す。
76
+ #
77
+ # @param region [String, nil] 地方名(例: "関東")
78
+ # @return [Array<Prefecture>, Array<DB::Prefecture>]
30
79
  def where(region: nil)
31
80
  return all unless region
81
+ return DB::Prefecture.where(region_name: region).to_a if Basho.db?
32
82
 
33
- all.select { |pref| pref.region&.name == region }
83
+ all.select { |pref| pref.region_name == region }
34
84
  end
35
85
 
36
- private
37
-
38
- def find_by_options(options)
39
- if options[:name]
40
- all.find { |pref| pref.name == options[:name] }
41
- elsif options[:name_en]
42
- all.find { |pref| pref.name_en == options[:name_en] }
43
- end
86
+ # メモリキャッシュをクリアする。
87
+ #
88
+ # @return [void]
89
+ # @api private
90
+ def reset_cache!
91
+ remove_instance_variable(:@all) if defined?(@all)
44
92
  end
45
93
  end
46
94
  end
data/lib/basho/region.rb CHANGED
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
+ # 地方区分を表すイミュータブルなデータクラス。
5
+ # 北海道、東北、関東、中部、近畿、中国、四国、九州、沖縄の9地方。
6
+ #
7
+ # @!attribute [r] name
8
+ # @return [String] 地方名(例: "関東")
9
+ # @!attribute [r] name_en
10
+ # @return [String] 英語名(例: "Kanto")
11
+ # @!attribute [r] prefecture_codes
12
+ # @return [Array<Integer>] 所属する都道府県コードの配列
4
13
  Region = ::Data.define(:name, :name_en, :prefecture_codes) do
14
+ # 所属する都道府県の一覧を返す。
15
+ #
16
+ # @return [Array<Prefecture>]
5
17
  def prefectures
6
18
  prefecture_codes.map { |code| Prefecture.find(code) }
7
19
  end
8
20
 
9
21
  class << self
22
+ # 全9地方を返す。
23
+ #
24
+ # @return [Array<Region>]
10
25
  def all
11
26
  @all ||= [
12
27
  new(name: "北海道", name_en: "Hokkaido", prefecture_codes: [1]),
@@ -21,6 +36,10 @@ module Basho
21
36
  ].freeze
22
37
  end
23
38
 
39
+ # 日本語名または英語名で地方を検索する。
40
+ #
41
+ # @param name [String] 地方名(例: "関東", "Kanto")
42
+ # @return [Region, nil]
24
43
  def find(name)
25
44
  all.find { |region| region.name == name || region.name_en == name }
26
45
  end
data/lib/basho/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basho
4
- VERSION = "0.3.0"
4
+ # @return [String] 現在のgemバージョン
5
+ VERSION = "0.4.0"
5
6
  end
data/lib/basho.rb CHANGED
@@ -10,10 +10,60 @@ require_relative "basho/postal_code"
10
10
  require_relative "basho/active_record/base"
11
11
  require_relative "basho/engine" if defined?(Rails::Engine)
12
12
 
13
- # 日本の住所データ(都道府県・市区町村・郵便番号・地方区分)を統一的に扱うgem
13
+ # 日本の住所データ(都道府県・市区町村・郵便番号・地方区分)を統一的に扱うgem
14
+ #
15
+ # デフォルトでは同梱のJSONファイルからデータを読み込む。
16
+ # +basho_prefectures+ / +basho_cities+ テーブルが存在すれば自動的にDBバックエンドに切り替わる。
17
+ #
18
+ # @example ActiveRecordモデルでの利用
19
+ # class Shop < ApplicationRecord
20
+ # include Basho
21
+ # basho :city_code
22
+ # basho_postal :postal_code, prefecture: :pref_name, city: :city_name
23
+ # end
14
24
  module Basho
25
+ # basho gem固有のエラー基底クラス
15
26
  class Error < StandardError; end
16
27
 
28
+ @db_mutex = Mutex.new
29
+
30
+ # +basho_prefectures+ テーブルが存在するかを検出する。
31
+ # 結果はプロセスの生存期間中キャッシュされる。スレッドセーフ。
32
+ #
33
+ # @return [Boolean] DBバックエンドが利用可能なら +true+
34
+ def self.db?
35
+ return @db if defined?(@db)
36
+
37
+ @db_mutex.synchronize do
38
+ return @db if defined?(@db)
39
+
40
+ @db = defined?(::ActiveRecord::Base) &&
41
+ ::ActiveRecord::Base.connection.table_exists?("basho_prefectures")
42
+ require "basho/db" if @db
43
+ @db
44
+ end
45
+ rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
46
+ @db = false
47
+ end
48
+
49
+ # DB検出キャッシュをリセットする。テスト時やマイグレーション後の再検出に使用。
50
+ #
51
+ # @return [void]
52
+ def self.reset_db_cache!
53
+ remove_instance_variable(:@db) if defined?(@db)
54
+ Prefecture.reset_cache! if Prefecture.respond_to?(:reset_cache!)
55
+ end
56
+
57
+ # バックエンドを強制指定する。主にテスト用。
58
+ #
59
+ # @param value [Boolean] +true+ でDBバックエンド、+false+ でメモリバックエンド
60
+ # @return [void]
61
+ def self.db=(value)
62
+ @db = value
63
+ require "basho/db" if value
64
+ end
65
+
66
+ # @private
17
67
  def self.included(base)
18
68
  base.extend ActiveRecord::Base
19
69
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Basho
7
+ # Railsジェネレータの名前空間。
8
+ module Generators
9
+ # +basho_prefectures+ / +basho_cities+ テーブルのマイグレーションジェネレータ。
10
+ #
11
+ # @example
12
+ # rails generate basho:install_tables
13
+ class InstallTablesGenerator < Rails::Generators::Base
14
+ include ::ActiveRecord::Generators::Migration
15
+
16
+ source_root File.expand_path("templates", __dir__)
17
+
18
+ desc "basho_prefectures / basho_cities テーブルのマイグレーションを生成"
19
+
20
+ # マイグレーションファイルを生成する。
21
+ # @return [void]
22
+ def create_migration_files
23
+ migration_template "create_basho_prefectures.rb.erb", "db/migrate/create_basho_prefectures.rb"
24
+ migration_template "create_basho_cities.rb.erb", "db/migrate/create_basho_cities.rb"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ class CreateBashoCities < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :basho_cities, id: false do |t|
4
+ t.string :code, limit: 6, null: false, primary_key: true
5
+ t.integer :prefecture_code, null: false
6
+ t.string :name, null: false
7
+ t.string :name_kana, null: false
8
+ t.string :district
9
+ t.boolean :capital, null: false, default: false
10
+ end
11
+
12
+ add_index :basho_cities, :prefecture_code
13
+ add_foreign_key :basho_cities, :basho_prefectures, column: :prefecture_code, primary_key: :code
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ class CreateBashoPrefectures < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :basho_prefectures, id: false do |t|
4
+ t.integer :code, null: false, primary_key: true
5
+ t.string :name, null: false
6
+ t.string :name_en, null: false
7
+ t.string :name_kana, null: false
8
+ t.string :name_hiragana, null: false
9
+ t.string :region_name, null: false
10
+ t.string :prefecture_type, null: false
11
+ t.string :capital_code, limit: 6
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :basho do
4
+ desc "basho_prefectures / basho_cities テーブルにデータを投入"
5
+ task seed: :environment do
6
+ require "basho/db"
7
+
8
+ counts = Basho::DB.seed!
9
+ puts "basho:seed 完了 — 都道府県: #{counts[:prefectures]}件, 市区町村: #{counts[:cities]}件"
10
+ end
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basho
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hirotaka Wagai
@@ -17,6 +17,7 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".yardopts"
20
21
  - CHANGELOG.md
21
22
  - LICENSE.txt
22
23
  - README.ja.md
@@ -1030,11 +1031,18 @@ files:
1030
1031
  - lib/basho/city.rb
1031
1032
  - lib/basho/code_validator.rb
1032
1033
  - lib/basho/data/loader.rb
1034
+ - lib/basho/db.rb
1035
+ - lib/basho/db/city.rb
1036
+ - lib/basho/db/prefecture.rb
1033
1037
  - lib/basho/engine.rb
1034
1038
  - lib/basho/postal_code.rb
1035
1039
  - lib/basho/prefecture.rb
1036
1040
  - lib/basho/region.rb
1037
1041
  - lib/basho/version.rb
1042
+ - lib/generators/basho/install_tables/install_tables_generator.rb
1043
+ - lib/generators/basho/install_tables/templates/create_basho_cities.rb.erb
1044
+ - lib/generators/basho/install_tables/templates/create_basho_prefectures.rb.erb
1045
+ - lib/tasks/basho.rake
1038
1046
  - sig/basho.rbs
1039
1047
  homepage: https://github.com/wagai/basho
1040
1048
  licenses:
@@ -1058,7 +1066,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1058
1066
  - !ruby/object:Gem::Version
1059
1067
  version: '0'
1060
1068
  requirements: []
1061
- rubygems_version: 3.6.9
1069
+ rubygems_version: 4.0.4
1062
1070
  specification_version: 4
1063
1071
  summary: Japanese address data (prefectures, cities, postal codes, regions) in a single
1064
1072
  gem