Skip to content

Commit

Permalink
Extract the search implementation to Finder class #24
Browse files Browse the repository at this point in the history
  • Loading branch information
chocoby committed Feb 8, 2021
1 parent be516ef commit 422616f
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 235 deletions.
126 changes: 38 additions & 88 deletions lib/jp_prefecture/prefecture.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require 'jp_prefecture/prefecture/finder'
require 'jp_prefecture/mapping'
require 'jp_prefecture/zip_mapping'

Expand Down Expand Up @@ -40,28 +41,52 @@ def self.build(code, name, name_e, name_h = nil, name_k = nil, area = nil)
pref
end

# すべての都道府県クラスを返す
#
# @example
# # 都道府県の一覧を取得
# JpPrefecture::Prefecture.all
#
# # collection_select で選択肢を生成
# f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name
#
# # collection_select で選択肢を生成(英語表記)
# f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name_e
#
# @return [Array] 都道府県クラスの配列
def self.all
Mapping.data.map do |pref|
names = pref[1]
build(pref[0],
names[:name], names[:name_e],
names[:name_h], names[:name_k], names[:area])
end
end

# 都道府県を検索
#
# 文字列は前方一致で検索する
#
# @example
# # 都道府県コードから検索
# # 都道府県コードを検索
# JpPrefecture::Prefecture.find(1)
# JpPrefecture::Prefecture.find(code: 1)
#
# # 都道府県名から検索 (前方一致)
# # 都道府県名を検索
# JpPrefecture::Prefecture.find(name: '北海道')
# JpPrefecture::Prefecture.find(name: '東京')
#
# # 英語表記の都道府県名から検索
# # 英語表記の都道府県名を検索
# JpPrefecture::Prefecture.find(name_e: 'Hokkaido')
# JpPrefecture::Prefecture.find(name_e: 'hokkaido')
#
# # ひらがな表記の都道府県名から検索
# # ひらがな表記の都道府県名を検索
# JpPrefecture::Prefecture.find(name_h: 'ほっかいどう')
#
# # カタカナ表記の都道府県名から検索
# # カタカナ表記の都道府県名を検索
# JpPrefecture::Prefecture.find(name_k: 'ホッカイドウ')
#
# # マッピングに定義しているすべてのフィールドから検索
# # すべての項目を検索
# JpPrefecture::Prefecture.find(all_fields: '東')
#
# @param args [Integer] 都道府県コード
Expand All @@ -74,90 +99,15 @@ def self.build(code, name, name_e, name_h = nil, name_k = nil, area = nil)
def self.find(args)
return if args.nil?

code = case args
when Integer, String
args.to_i
when Hash
search_field = args.keys.first
search_value = args.values.first

case search_field
when :all_fields
find_code_by_name_from_all_fields(search_value)
when :name, :name_e, :name_h, :name_k, :area
find_code_by_name(search_field, search_value)
when :code
search_value.to_i
when :zip
ZipMapping.code_for_zip(search_value.to_i)
end
end

names = Mapping.data[code]

return unless names

build(code,
names[:name], names[:name_e],
names[:name_h], names[:name_k], names[:area])
end

# すべての都道府県クラスを返す
#
# @example
# # 都道府県の一覧を取得
# JpPrefecture::Prefecture.all
#
# # collection_select で選択肢を生成
# f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name
#
# # collection_select で選択肢を生成(英語表記)
# f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name_e
#
# @return [Array] 都道府県クラスの配列
def self.all
Mapping.data.map do |pref|
names = pref[1]
build(pref[0],
names[:name], names[:name_e],
names[:name_h], names[:name_k], names[:area])
end
end

# 名前から都道府県コードを前方一致で検索
#
# @param name [String] 検索する都道府県名
# @return [Integer] 見つかった場合は都道府県コード
# @return [nil] 見つからない場合は nil
def self.find_code_by_name(field, name)
return if name.nil? || name.empty?

name = name.downcase
case args
when Integer, String
JpPrefecture::Prefecture::Finder.new.find(field: nil, value: args)
when Hash
search_field = args.keys.first
search_value = args.values.first

Mapping.data.each do |m|
return m[0] if m[1][field].start_with?(name)
JpPrefecture::Prefecture::Finder.new.find(field: search_field, value: search_value)
end

nil
end

# 名前から都道府県コードを前方一致で検索
#
# @param name [String] 検索する都道府県名
# @return [Integer] 見つかった場合は都道府県コード
# @return [nil] 見つからない場合は nil
def self.find_code_by_name_from_all_fields(name)
return if name.nil? || name.empty?

name = name.downcase

Mapping.data.each do |m|
m[1].each_value do |v|
return m[0] if v.start_with?(name)
end
end

nil
end
end
end
81 changes: 81 additions & 0 deletions lib/jp_prefecture/prefecture/finder.rb
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require 'jp_prefecture/mapping'
require 'jp_prefecture/zip_mapping'

module JpPrefecture
class Prefecture
# 都道府県の検索を行うクラス
class Finder
def initialize
@mapping = Mapping.data
end

# 指定した項目を検索
#
# @param field [Symbol] 検索する項目。nil の場合は都道府県コードとして扱う
# @param value [String, Integer] 検索する内容
# @return [JpPrefecture::Prefecture] 都道府県が見つかった場合は都道府県クラス
# @return [nil] 都道府県が見つからない場合は nil
def find(field:, value:)
code = find_code(field, value)
prefecture = @mapping[code]
return unless prefecture

JpPrefecture::Prefecture.build(
code,
prefecture[:name],
prefecture[:name_e],
prefecture[:name_h],
prefecture[:name_k],
prefecture[:area]
)
end

private

# @param field [Symbol] 検索する項目
# @param value [String, Integer] 検索する内容
# @return [Integer] 見つかった場合は都道府県コード
# @return [nil] 見つからない場合は nil
def find_code(field, value)
return value.to_i if field.nil?

case field
when :all_fields
find_code_by_name_from_all_fields(value)
when :name, :name_h, :name_k, :name_e
find_code_by_name(field, value)
when :code
value.to_i
when :zip
ZipMapping.code_for_zip(value.to_i)
end
end

# すべての項目を前方一致で検索
def find_code_by_name_from_all_fields(value)
return if value.nil? || value.empty?

value = value.downcase

@mapping.each do |m|
m[1].each_value do |v|
return m[0] if v.start_with?(value)
end
end
end

# 指定した項目を前方一致で検索
def find_code_by_name(field, value)
return if value.nil? || value.empty?

value = value.downcase

@mapping.each do |m|
return m[0] if m[1][field].start_with?(value)
end
end
end
end
end
72 changes: 72 additions & 0 deletions spec/prefecture/finder_spec.rb
@@ -0,0 +1,72 @@
# frozen_string_literal: true

require 'spec_helper'

describe JpPrefecture::Prefecture::Finder do
describe '#find' do
shared_examples '都道府県が見つかる' do |field, value, expected_result|
let(:result) { JpPrefecture::Prefecture::Finder.new.find(field: field, value: value) }
it { expect(result.name).to eq(expected_result) }
end

shared_examples '都道府県が見つからない' do |field, value|
let(:result) { JpPrefecture::Prefecture::Finder.new.find(field: field, value: value) }
it { expect(result).to be_nil }
end

describe 'field に all_fields (すべての項目) を指定する' do
# area の「東北」が最初にマッチする
it_behaves_like '都道府県が見つかる', :all_fields, '東', '青森県'
it_behaves_like '都道府県が見つからない', :all_fields, '饂飩'
end

describe 'field に name (漢字表記) を指定する' do
it_behaves_like '都道府県が見つかる', :name, '北海道', '北海道'
it_behaves_like '都道府県が見つかる', :name, '東京', '東京都'
it_behaves_like '都道府県が見つかる', :name, '京都', '京都府'
it_behaves_like '都道府県が見つからない', :name, '饂飩'
end

describe 'field に name_e (英語表記) を指定する' do
it_behaves_like '都道府県が見つかる', :name_e, 'HOKKAIDO', '北海道'
it_behaves_like '都道府県が見つかる', :name_e, 'Hokkaido', '北海道'
it_behaves_like '都道府県が見つかる', :name_e, 'hokkaido', '北海道'
it_behaves_like '都道府県が見つからない', :name_e, 'Udon'
end

describe 'field に name_h (ひらがな表記) を指定する' do
it_behaves_like '都道府県が見つかる', :name_h, 'ほっかいどう', '北海道'
it_behaves_like '都道府県が見つかる', :name_h, 'ほっかい', '北海道'
it_behaves_like '都道府県が見つからない', :name_h, 'うどん'
end

describe 'field に name_k (カタカナ表記) を指定する' do
it_behaves_like '都道府県が見つかる', :name_k, 'ホッカイドウ', '北海道'
it_behaves_like '都道府県が見つかる', :name_k, 'ホッカイ', '北海道'
it_behaves_like '都道府県が見つからない', :name_k, 'ウドン'
end

describe 'field に code (都道府県コード) を指定する' do
it_behaves_like '都道府県が見つかる', :code, 1, '北海道'
it_behaves_like '都道府県が見つかる', :code, '1', '北海道'
it_behaves_like '都道府県が見つかる', :code, '01', '北海道'
it_behaves_like '都道府県が見つからない', :code, 999
it_behaves_like '都道府県が見つからない', :code, '999'
end

describe 'field に zip (郵便番号) を指定する' do
it_behaves_like '都道府県が見つかる', :zip, 10_000, '北海道'
it_behaves_like '都道府県が見つかる', :zip, '010000', '北海道'
it_behaves_like '都道府県が見つからない', :zip, 999_999
it_behaves_like '都道府県が見つからない', :zip, '999999'
end

describe 'field を指定しない' do
it_behaves_like '都道府県が見つかる', nil, 1, '北海道'
it_behaves_like '都道府県が見つかる', nil, '1', '北海道'
it_behaves_like '都道府県が見つかる', nil, '01', '北海道'
it_behaves_like '都道府県が見つからない', nil, 999
it_behaves_like '都道府県が見つからない', nil, '999'
end
end
end

0 comments on commit 422616f

Please sign in to comment.