diff --git a/README.md b/README.md index 096d344..0b185f9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ JIS X 0401 で定義されている都道府県コードをベースに、 また、Rails のプラグインとして使用することもできます。 - ## 使い方 ### ライブラリの読み込み @@ -69,6 +68,8 @@ JpPrefecture::Prefecture.find(code: 13) ### 都道府県を検索 +`find` は複数の都道府県に一致する場合、都道府県コード順で最初の 1 件を返します。 + 前方一致で都道府県を検索します: ```ruby @@ -91,12 +92,24 @@ JpPrefecture::Prefecture.find(name_h: "とうきょうと") JpPrefecture::Prefecture.find(name_k: "トウキョウト") ``` -マッピングのすべての項目を検索します (推奨しません): +マッピングのすべての項目を検索します (非推奨): ```ruby JpPrefecture::Prefecture.find(all_fields: "東京") ``` +一致したすべての都道府県が必要な場合は `where` を使用します: + +```ruby +JpPrefecture::Prefecture.where(name: "山") +# => [山形県, 山梨県, 山口県] + +JpPrefecture::Prefecture.where(name_e: "o") +# => [大阪府, 岡山県, 大分県, 沖縄県] +``` + +`where` で検索できる項目は `name` / `name_e` / `name_r` / `name_h` / `name_k` です。対応していない項目を指定した場合は `ArgumentError` が発生します。 + ### 都道府県の一覧を取得 ```ruby @@ -192,7 +205,6 @@ end データのフォーマットについては [zip.yml](https://github.com/chocoby/jp_prefecture/blob/main/data/zip.yml) を参考にしてください。 - ## インストール 以下の行を `Gemfile` に記述してから: @@ -209,15 +221,14 @@ gem 'jp_prefecture' $ gem install jp_prefecture ``` - ## ドキュメント [https://rubydoc.info/gems/jp_prefecture](https://rubydoc.info/gems/jp_prefecture) ## サポートしているバージョン -* Ruby: 2.4 - 4.0 -* Rails: 5.0 - 8.1 +- Ruby: 2.4 - 4.0 +- Rails: 5.0 - 8.1 これより古い Ruby/Rails バージョンを使用する場合は、[`v0.11.0`](https://github.com/chocoby/jp_prefecture/tree/0.x) を利用してください。 diff --git a/README_EN.md b/README_EN.md index 76156bc..bd12568 100644 --- a/README_EN.md +++ b/README_EN.md @@ -66,6 +66,8 @@ JpPrefecture::Prefecture.find(code: 13) ### Search by Prefecture Name +`find` returns the first match in prefecture code order when multiple prefectures match. + Search for a prefecture by forward match. ```ruby @@ -94,6 +96,18 @@ Search all items in the mapping (not recommended). JpPrefecture::Prefecture.find(all_fields: "東京") ``` +Use `where` when you need all matching prefectures. + +```ruby +JpPrefecture::Prefecture.where(name: "山") +# => [山形県, 山梨県, 山口県] + +JpPrefecture::Prefecture.where(name_e: "o") +# => [大阪府, 岡山県, 大分県, 沖縄県] +``` + +`where` supports `name` / `name_e` / `name_r` / `name_h` / `name_k`. Specifying any other item raises `ArgumentError`. + ### All Prefectures ```ruby @@ -191,7 +205,6 @@ end Check out [zip.yml](https://github.com/chocoby/jp_prefecture/blob/main/data/zip.yml) for data format. - ## Installation Add this line in Gemfile. @@ -212,15 +225,14 @@ Or install gem with `gem install` $ gem install jp_prefecture ``` - ## Documentation [https://rubydoc.info/gems/jp_prefecture](https://rubydoc.info/gems/jp_prefecture) ## Supported versions -* Ruby: 2.4 - 4.0 -* Rails: 5.0 - 8.1 +- Ruby: 2.4 - 4.0 +- Rails: 5.0 - 8.1 If you are using an older Ruby/Rails version, please use [`v0.11.0`](https://github.com/chocoby/jp_prefecture/tree/0.x). diff --git a/lib/jp_prefecture/prefecture.rb b/lib/jp_prefecture/prefecture.rb index d07edec..2088667 100644 --- a/lib/jp_prefecture/prefecture.rb +++ b/lib/jp_prefecture/prefecture.rb @@ -62,6 +62,9 @@ def self.all # # 文字列は前方一致で検索する # + # 複数の都道府県に一致する場合は、都道府県コード順で最初の 1 件を返す。 + # 一致したすべての都道府県が必要な場合は {.where} を使用する + # # @example # # 都道府県コードを検索 # JpPrefecture::Prefecture.find(1) @@ -108,5 +111,35 @@ def self.find(args) JpPrefecture::Prefecture::Finder.new.find(field: search_field, value: search_value) end end + + # 都道府県を検索し、一致したすべての都道府県を返す + # + # 文字列は前方一致で検索する + # + # @example + # # 複数の都道府県に一致する検索 + # JpPrefecture::Prefecture.where(name: '山') + # # => [山形県, 山梨県, 山口県] + # + # # 英語表記で検索 + # JpPrefecture::Prefecture.where(name_e: 'o') + # # => [大阪府, 岡山県, 大分県, 沖縄県] + # + # @param args [Hash] :name 漢字表記/:name_e 英語表記/:name_r ローマ字表記/:name_h ひらがな表記/:name_k カタカナ表記 + # @return [Array] 一致した都道府県インスタンスの配列 (コード順)。一致しない場合は空配列 + # @raise [ArgumentError] 対応していない項目、または項目が 1 つでない場合 + def self.where(args) + unless args.is_a?(Hash) && args.size == 1 + raise ArgumentError, "expected a Hash with exactly one key, got: #{args.inspect}" + end + + field, value = args.first + + unless Finder::NAME_FIELDS.include?(field) + raise ArgumentError, "unsupported field: #{field.inspect} (supported: #{Finder::NAME_FIELDS.join(', ')})" + end + + JpPrefecture::Prefecture::Finder.new.where(field: field, value: value) + end end end diff --git a/lib/jp_prefecture/prefecture/finder.rb b/lib/jp_prefecture/prefecture/finder.rb index 1e7de9f..02e7362 100644 --- a/lib/jp_prefecture/prefecture/finder.rb +++ b/lib/jp_prefecture/prefecture/finder.rb @@ -7,6 +7,9 @@ module JpPrefecture class Prefecture # 都道府県の検索を行うクラス class Finder + # 名前を前方一致で検索できる項目 + NAME_FIELDS = %i[name name_e name_r name_h name_k].freeze + def initialize @mapping = Mapping.data end @@ -22,6 +25,21 @@ def find(field:, value:) JpPrefecture::Prefecture.build_by_code(code) end + # 指定した項目を前方一致で検索し、一致したすべての都道府県を返す + # + # @param field [Symbol] 検索する項目。NAME_FIELDS 以外を指定した場合は空配列 + # @param value [String] 検索する内容 + # @return [Array] 一致した都道府県インスタンスの配列 (コード順) + def where(field:, value:) + return [] unless NAME_FIELDS.include?(field) + + value = value.to_s.downcase + return [] if value.empty? + + @mapping.select { |_code, names| names[field].start_with?(value) } + .map { |code, _names| JpPrefecture::Prefecture.build_by_code(code) } + end + private # @param field [Symbol] 検索する項目 @@ -34,7 +52,7 @@ def find_code(field, value) case field when :all_fields find_code_by_name_from_all_fields(value.to_s) - when :name, :name_h, :name_k, :name_e, :name_r + when *NAME_FIELDS find_code_by_name(field, value.to_s) when :code value.to_i diff --git a/spec/prefecture/finder_spec.rb b/spec/prefecture/finder_spec.rb index bd45906..0a1dd6b 100644 --- a/spec/prefecture/finder_spec.rb +++ b/spec/prefecture/finder_spec.rb @@ -86,4 +86,54 @@ it_behaves_like '都道府県が見つからない', nil, '999' end end + + describe '#where' do + shared_examples '都道府県が見つかる' do |field, value, expected_names| + let(:result) { JpPrefecture::Prefecture::Finder.new.where(field: field, value: value) } + it { expect(result.map(&:name)).to eq(expected_names) } + it { expect(result).to all(be_an_instance_of(JpPrefecture::Prefecture)) } + end + + shared_examples '都道府県が見つからない' do |field, value| + let(:result) { JpPrefecture::Prefecture::Finder.new.where(field: field, value: value) } + it { expect(result).to eq([]) } + end + + describe '複数の都道府県に一致する' do + it_behaves_like '都道府県が見つかる', :name, '山', %w[山形県 山梨県 山口県] + it_behaves_like '都道府県が見つかる', :name_e, 'o', %w[大阪府 岡山県 大分県 沖縄県] + end + + describe '一つの都道府県に一致する' do + it_behaves_like '都道府県が見つかる', :name, '北海道', %w[北海道] + it_behaves_like '都道府県が見つかる', :name_h, 'ほっかい', %w[北海道] + it_behaves_like '都道府県が見つかる', :name_k, 'ホッカイ', %w[北海道] + it_behaves_like '都道府県が見つかる', :name_r, 'hokkaidō', %w[北海道] + end + + describe '大文字と小文字を区別しない' do + it_behaves_like '都道府県が見つかる', :name_e, 'O', %w[大阪府 岡山県 大分県 沖縄県] + it_behaves_like '都道府県が見つかる', :name_e, 'HOKKAIDO', %w[北海道] + end + + describe 'String 以外の値を指定する' do + it_behaves_like '都道府県が見つかる', :name_e, :hokkaido, %w[北海道] + end + + describe 'どの都道府県にも一致しない' do + it_behaves_like '都道府県が見つからない', :name, '饂飩' + end + + describe '空の値を指定する' do + it_behaves_like '都道府県が見つからない', :name, '' + it_behaves_like '都道府県が見つからない', :name, nil + end + + describe '名前以外の field を指定する' do + it_behaves_like '都道府県が見つからない', :code, 1 + it_behaves_like '都道府県が見つからない', :zip, 10_000 + it_behaves_like '都道府県が見つからない', :all_fields, '東' + it_behaves_like '都道府県が見つからない', nil, '山' + end + end end diff --git a/spec/prefecture_spec.rb b/spec/prefecture_spec.rb index 7fd4b0e..756e7dc 100644 --- a/spec/prefecture_spec.rb +++ b/spec/prefecture_spec.rb @@ -82,4 +82,41 @@ it { expect(result).to be_nil } end end + + describe '.where' do + context '名前系の項目を指定' do + before do + finder = spy('finder') + allow(JpPrefecture::Prefecture::Finder).to receive(:new).and_return(finder) + JpPrefecture::Prefecture.where(name: '山') + end + + it { expect(JpPrefecture::Prefecture::Finder.new).to have_received(:where).with(field: :name, value: '山') } + end + + context '複数の都道府県に一致' do + let(:result) { JpPrefecture::Prefecture.where(name: '山') } + it { expect(result.map(&:name)).to eq(%w[山形県 山梨県 山口県]) } + end + + context '対応していない項目を指定' do + it { expect { JpPrefecture::Prefecture.where(code: 1) }.to raise_error(ArgumentError) } + it { expect { JpPrefecture::Prefecture.where(zip: 1_000_000) }.to raise_error(ArgumentError) } + it { expect { JpPrefecture::Prefecture.where(all_fields: '東') }.to raise_error(ArgumentError) } + it { expect { JpPrefecture::Prefecture.where(name_j: '山') }.to raise_error(ArgumentError) } + end + + context '項目を複数指定' do + it { expect { JpPrefecture::Prefecture.where(name: '山', name_e: 'y') }.to raise_error(ArgumentError) } + end + + context '空の Hash を指定' do + it { expect { JpPrefecture::Prefecture.where({}) }.to raise_error(ArgumentError) } + end + + context 'Hash 以外を指定' do + it { expect { JpPrefecture::Prefecture.where('山') }.to raise_error(ArgumentError) } + it { expect { JpPrefecture::Prefecture.where(nil) }.to raise_error(ArgumentError) } + end + end end