サービスで利用する金融機関コードのAPIを公開するには 全国銀行協会 から 金融機関店舗情報 を購入するのでしょうが、まず入手までに時間がかかります。そして入手できても厳しいハードルがあって、メディアが CD-ROM で届きます。さらに動作OSが Windows(日本語) という!もちろんファイルのコードは シフトJIS です。
そんなわけでOSSの金融機関コードを使ってプロトタイプを作ります。全銀協の注文フォームを入力する時間より早くAPIが完成するでしょう。
ZenginCode source data は JavaScript や Python にも対応していますが Ruby on Rails で作成しました。
GitHub に今回のコードを公開しています。
Tutorial
Rails New
- Ruby on Rails は
--api
のオプションで簡単に REST API を作成することができます。
$ rails new zengin-code-search --api
$ cd zengin-code-search/
Generate Scaffold
- モデルは一つの金融機関に複数の店舗が属する関係なので
bank has many branches
を構築します。 - 名前には漢字と片仮名さらに平仮名とローマ字があるので、それぞれに項目を作成します。
$ rails generate scaffold bank code name name_kana name_hira name_en
$ rails generate scaffold branch code name name_kana name_hira name_en bank:references
Kana
や Hira
は ISO 15924 で分類されているコードで、ちなみに漢字は Hani
です。
Rails Database Migration
-
code
にUNIQUE制約
を設定します。他にNOT NULL制約
などデータベースの設定をします。
class CreateBanks < ActiveRecord::Migration[5.1]
def change
create_table :banks do |t|
t.string :code, null: false, default: '', index: { unique: true }
t.string :name, null: false, default: ''
t.string :name_kana, null: false, default: ''
t.string :name_hira, null: false, default: ''
t.string :name_en, null: false, default: ''
t.timestamps
end
end
end
-
bank_id
とcode
にUNIQUE制約
を設定します。他にNOT NULL制約
などデータベースの設定をします。(大事なことなので)
class CreateBranches < ActiveRecord::Migration[5.1]
def change
create_table :branches do |t|
t.string :code, null: false, default: ''
t.string :name, null: false, default: ''
t.string :name_kana, null: false, default: ''
t.string :name_hira, null: false, default: ''
t.string :name_en, null: false, default: ''
t.references :bank, foreign_key: true
t.timestamps
end
add_index :branches, [:bank_id, :code], unique: true
end
end
-
rails db:migrate
コマンドでデータベースを作成します。
$ rails db:migrate
Nested Resources
-
ネストしたリソースのパターンはよく使います。ですが
rails generate
コマンドで作成したコードを少し編集する必要があります。
Rails Models
-
has_many :branches
は金融機関が複数の店舗を持つことを示します。 -
dependent: :destroy
は金融機関が削除されると店舗も削除されます。
class Bank < ApplicationRecord
has_many :branches, dependent: :destroy
end
-
belongs_to :bank
は店舗が金融機関に属することを示します。
class Branch < ApplicationRecord
belongs_to :bank
end
Rails Controllers
-
POST:create
,GET:index
,GET:show
,PATCH/PUT:update
,DELETE:destroy
のHTTPとRubyのメソッドで CRUD に対応しています。
class BanksController < ApplicationController
before_action :set_bank, only: [:show, :update, :destroy]
# GET /banks
def index
@banks = Bank.all
render json: @banks
end
# GET /banks/1
def show
render json: @bank
end
# POST /banks
def create
@bank = Bank.new(bank_params)
if @bank.save
render json: @bank, status: :created, location: @bank
else
render json: @bank.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /banks/1
def update
if @bank.update(bank_params)
render json: @bank
else
render json: @bank.errors, status: :unprocessable_entity
end
end
# DELETE /banks/1
def destroy
@bank.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bank
@bank = Bank.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def bank_params
params.require(:bank).permit(:code, :name, :name_kana, :name_hira, :name_en)
end
end
- 入れ子にするには
Branch
クラスを@bank.branches
に変更します。
class BranchesController < ApplicationController
before_action :set_bank
before_action :set_branch, only: [:show, :update, :destroy]
# GET /branches
def index
@branches = @bank.branches
render json: @branches
end
# GET /branches/1
def show
render json: @branch
end
# POST /branches
def create
@branch = @bank.branches.new(branch_params)
if @branch.save
render json: @branch, status: :created, location: [@bank, @branch]
else
render json: @branch.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /branches/1
def update
if @branch.update(branch_params)
render json: @branch
else
render json: @branch.errors, status: :unprocessable_entity
end
end
# DELETE /branches/1
def destroy
@branch.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bank
@bank = Bank.find(params[:bank_id])
end
def set_branch
@branch = Branch.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def branch_params
params.require(:branch).permit(:code, :name, :name_kana, :name_hira, :name_en, :bank_id)
end
end
Rails Routes
-
resources
のルーティングをdo
とend
に記述するこで入れ子にします。
Rails.application.routes.draw do
resources :banks do
resources :branches
end
end
Rails Server
-
rails server
コマンドで起動して URL にアクセスします。
$ rails server
$ open http://localhost:3000/banks
APIを作るだけなら数分で完成します。
Rails Database Seed
- 金融コードは ZenginCode のライブラリを使ってデータベースに登録します。
-
Gemfile
ファイルにrequire: false
を記述すると明示的にrequire
を記述しないとロードされません。
gem 'zengin_code', require: false
-
zengin_code
を使うのは Seed にのみになのでrequire
をこちら記述します。
require 'zengin_code'
ZenginCode::Bank.all.each do |original_code, original_bank|
puts "== #{original_code}:#{original_bank.name}"
bank = Bank.find_or_initialize_by(code: original_code)
bank.name = original_bank.name
bank.name_kana = original_bank.kana
bank.name_hira = original_bank.hira
bank.name_en = original_bank.roma
bank.touch unless bank.new_record?
bank.save!
original_bank.branches.each do |original_code, original_branch|
puts "-- #{bank.code}:#{bank.name} #{original_code}:#{original_branch.name}"
branch = bank.branches.find_or_initialize_by(code: original_code)
branch.name = original_branch.name
branch.name_kana = original_branch.kana
branch.name_hira = original_branch.hira
branch.name_en = original_branch.roma
branch.touch unless branch.new_record?
branch.save!
end
end
puts "Bank: #{Bank.count}, Branch: #{Branch.count}"
-
rails db:seed
コマンドをで金融コードを登録します。
$ rails db:seed
Bank: 1345, Branch: 31169
数分かかりますがサンプルデータが作成されます。
Rails Server
- 入れ子で URL にアクセスすることができます。
$ rails server
$ open http://localhost:3000/banks/1
$ open http://localhost:3000/banks/1/branches/1
Overriding Named Route Parameters
-
名前付きルーティングのパラメータをオーバーライドすることでデフォルトのリソース識別子(
:id
)を変更します。
Rails Models
-
to_param
メソッドに変更する項目を記述します。
class Bank < ApplicationRecord
has_many :branches, dependent: :destroy
def to_param
code
end
end
class Branch < ApplicationRecord
belongs_to :bank
def to_param
code
end
end
Rails Controllers
-
find(params[:id])
メソッドをfind_by(code: params[:code])
メソッドに変更します。
class BanksController < ApplicationController
before_action :set_bank, only: [:show, :update, :destroy]
# GET /banks
def index
@banks = Bank.all
render json: @banks
end
# GET /banks/:code
def show
render json: @bank
end
# POST /banks
def create
@bank = Bank.new(bank_params)
if @bank.save
render json: @bank, status: :created, location: @bank
else
render json: @bank.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /banks/:code
def update
if @bank.update(bank_params)
render json: @bank
else
render json: @bank.errors, status: :unprocessable_entity
end
end
# DELETE /banks/:code
def destroy
@bank.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bank
@bank = Bank.find_by(code: params[:code])
end
# Only allow a trusted parameter "white list" through.
def bank_params
params.require(:bank).permit(:code, :name, :name_kana, :name_hira, :name_en)
end
end
class BranchesController < ApplicationController
before_action :set_bank
before_action :set_branch, only: [:show, :update, :destroy]
# GET /banks/:bank_code/branches
def index
@branches = @bank.branches
render json: @branches
end
# GET /banks/:bank_code/branches/:code
def show
render json: @branch
end
# POST /banks/:bank_code/branches
def create
@branch = @bank.branches.new(branch_params)
if @branch.save
render json: @branch, status: :created, location: [@bank, @branch]
else
render json: @branch.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /banks/:bank_code/branches/:code
def update
if @branch.update(branch_params)
render json: @branch
else
render json: @branch.errors, status: :unprocessable_entity
end
end
# DELETE /banks/:bank_code/branches/:code
def destroy
@branch.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bank
@bank = Bank.find_by(code: params[:bank_code])
end
def set_branch
@branch = @bank.branches.find_by(code: params[:code])
end
# Only allow a trusted parameter "white list" through.
def branch_params
params.require(:branch).permit(:code, :name, :name_kana, :name_hira, :name_en, :bank_id)
end
end
Rails Routes
-
param
オプションに変更する項目を記述します。
Rails.application.routes.draw do
resources :banks, param: :code do
resources :branches, param: :code
end
end
Rails Server
- 金融コードで URL にアクセスすることができます。
$ open http://localhost:3000/banks/0001
$ open http://localhost:3000/banks/0001/branches/001
FriendlyId も同様の機能のライブラリです。
Search API
- 検索機能には SearchCop のライブラリを使います。
# https://github.com/mrkamel/search_cop
gem 'search_cop'
Rails Models
-
search_scope
のオプションのleft_wildcard: false
で前方一致検索ができます。
class Bank < ApplicationRecord
include SearchCop
search_scope :search do
attributes names: [:name_kana, :name_hira, :name_en]
options :names, left_wildcard: false
end
has_many :branches, dependent: :destroy
def to_param
code
end
end
class Branch < ApplicationRecord
include SearchCop
search_scope :search do
attributes names: [:name_kana, :name_hira, :name_en]
options :names, left_wildcard: false
end
belongs_to :bank
def to_param
code
end
end
片仮名と平仮名とローマ字を検索の対象に設定しています。
Rails Controllers
- 検索のコントローラを作成します。
$ rails generate controller search/banks index
$ rails generate controller search/branches index
search
メソッドで前方一致検索ができます。
class Search::BanksController < ApplicationController
# GET /search/banks
def index
@banks = Bank.search(params.require(:query))
render json: @banks
end
end
class Search::BranchesController < ApplicationController
before_action :set_bank
# GET /search/banks/:bank_code/branches
def index
@branches = @bank.branches.search(params.require(:query))
render json: @branches
end
private
# Use callbacks to share common setup or constraints between actions.
def set_bank
@bank = Bank.find_by(code: params[:bank_code])
end
end
params.require(:query)
で query
パラメータを必須にすることができます。
Rails Routes
- 検索のルーティングを記述します。
Rails.application.routes.draw do
namespace :search do
resources :banks, param: :code, only: [:index] do
resources :branches, only: [:index]
end
end
resources :banks, param: :code do
resources :branches, param: :code
end
end
Rails Server
- 入れ子で検索の URL にアクセスすることができます。
open http://localhost:3000/search/banks?query=mizu
open http://localhost:3000/search/banks/0001/branches?query=hon
Model Serializer
- レスポンスの項目に
id
やタイムスタンプが含まれるので ActiveModelSerializers のライブラリで必要な項目のみを設定します。
# https://github.com/rails-api/active_model_serializers
gem 'active_model_serializers'
$ rails generate serializer bank
$ rails generate serializer branch
class BankSerializer < ActiveModel::Serializer
attributes :code, :name, :name_kana, :name_hira, :name_en
end
class BranchSerializer < ActiveModel::Serializer
attributes :code, :name, :name_kana, :name_hira, :name_en
end
Tips
OSSのライブラリだけで簡単に金融コードのAPIができましたね。フロントエンドは React でインクリメンタルサーチができるコンポーネントを作るところまで書くと長くなるのでまたの機会に公開します。この記事の作成中に Rails ガイド(日本語) に Overriding Named Route Parameters の翻訳がないのに気がついて Pull Request をしました。こんな感じにOSS開発に参加したいなと思ったら OSS Gate のワークショップに参加してみましょう。