Webアプリつくってみよう 2日目 / 全5回
こんにちは、Gaji-Labo エンジニアの山崎です。
この記事は Gaji-Labo AdventCalendar 2015 7日目の記事です。
前回は仕様周りのお話でしたが、今回は実装を進めていきます。
まずは API から実装します。
前提として、API のI/F定義は済んでいるものとします。
URLパラメータ、JSON形式はUI側との認識が揃っていることが必須です。
なにはともあれ rails new
それでは、プロジェクトを作成していきましょう。
global に rails をインストールすることは避けたいのでプロジェクトディレクトリを先に作成します。
作業ディレクトリは Works/repos/postcode_searcher
とします。
$ mkdir -p Works/repos/postcode_searcher
$ cd Works/repos/postcode_searcher
$ bundle init
$ vim Gemfile
$ bundle install --path vendor/bundle --without staging production --jobs=4
$ bundle exec rails new . -BJT
$ bundle exec rails g rspec:install
郵便番号データインポート
まずはDB設定周りを進めます。
$ vim config/database.yml
$ bundle exec rails g model postcode
$ vim db/migrate/***********_create_postcodes.rb
$ bundle exec rake db:create
$ bundle exec rake db:migrate
migrate が完了したら、郵便番号データ投入用のrakeタスクを用意しましたので実行します。
lib/tasks/postcode.rake
require 'open-uri'
namespace :postcode do
POSTCODE_URI = 'http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip'
POSTCODE_CSV = "#{Rails.root}/tmp/KEN_ALL.CSV"
desc "郵便番号データを取得し、 postcodes モデルに反映させる"
task :update => :environment do
%w(download conv_utf8 set).each do |task|
Rake::Task["postcode:#{task}"].invoke
end
end
desc "郵便局の郵便番号データを取得して展開する"
task :download => :environment do
file = "#{Rails.root}/tmp/#{File.basename(POSTCODE_URI)}"
# 現在 tmp 以下にある不要なファイルを削除
system("rm #{file} #{POSTCODE_CSV}")
# ファイルを取得して保存
open(file, 'wb') do |output|
open(POSTCODE_URI) do |data|
output.write(data.read)
end
end
puts "downloaded."
# csv ファイルに展開する
system("unzip #{file} -d#{Rails.root}/tmp/")
puts "unzipped."
end
desc "郵便局の郵便番号データを SHIFT_JIS -> UTF-8 に変換する"
task :conv_utf8 => :environment do
system("iconv -f SHIFT_JIS -t UTF8 #{POSTCODE_CSV} > #{POSTCODE_CSV}.utf8")
puts "converted."
end
desc "郵便番号データを postcode モデルに反映させる"
task :set => :environment do
ActiveRecord::Base.transaction do
Postcode.connection.truncate(:postcodes)
Postcode.connection.execute <<-eof load="" data="" infile="" '#{postcode_csv}.utf8'="" into="" table="" postcodes="" fields="" terminated="" by="" ','="" enclosed="" '"'="" lines="" 'rn'="" (@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11,@12,@13,@14,@15)="" set="" postcode="@3," prefecture="@7," city="@8," address="@9;" eof="" end="" puts="" "inserted."="" end<="" p="">
end
-eof>
$ bundle exec rake postcode:download
$ bundle exec rake postcode:conv_utf8
$ bundle exec rake postcode:set
これで郵便番号データが使用可能になりました。
テスト
今回は、Postcodeモデルは参照のみに使用しますので特にテストは不要です。
バリデーションなどを実装する場合はちゃんとユニットテストを書きましょう。
まずは、テストを動かすために空のコントローラと空のテストを用意します。
$ bundle exec rails g controller postcode
$ bundle exec rails g rspec:controller postcode
次に、アクセスできることをテストします。
spec/controllers/postcode_controller_spec.rb
require 'rails_helper'
RSpec.describe PostcodeController, type: :controller do
describe "GET #search" do
before do
get :search, postcode: "103-0003"
end
it "get success" do
expect(response).to be_success
expect(response.status).to eq(200)
end
end
end
もちろん routes を記載していないのでエラーになります。
routes と controller を実装しましょう。
config/routes.rb
Rails.application.routes.draw do
get '/search/:postcode' => "postcode#search"
end
app/controllers/postcode_controller.rb
class PostcodeController < ApplicationController
def search
render text: "OK"
end
end
これでテストが通るようになりました。
TDD
上記の流れは TDD(テスト駆動開発) といいます。
一連の流れを繰り返すことで正常に動作するコードを作り続けることができます。
一箇所変更したことで他の箇所でエラーとなってしまうような事態を避けることができます。
では、続きを実装していきましょう。
郵便番号データを検索し、JSONを返すところまで実装します。
spec/controllers/postcode_controller_spec.rb
require 'rails_helper'
RSpec.describe PostcodeController, type: :controller do
describe "GET #search" do
before do
get :search, postcode: "103-0003"
end
it "get success" do
expect(response).to be_success
expect(response.status).to eq(200)
end
it "get json" do
json = JSON.parse(response.body)
expect(json["prefecture"]).to match("東京都")
expect(json["city"]).to match("中央区")
expect(json["address"]).to match("日本橋横山町")
end
it "not found" do
get :search, postcode: "000-0000"
json = JSON.parse(response.body)
expect(json["message"]).to match("not found.")
end
end
end
app/controllers/postcode_controller.rb
class PostcodeController < ApplicationController
def search
postcode = Postcode.search(params[:postcode])
render json: postcode.to_json
end
end
app/models/postcode.rb
class Postcode < ActiveRecord::Base
def self.search(query)
query = query.gsub(/-|ー/, "")
query.tr!("0-9", "0-9")
postcode = self.where(postcode: query).first
if postcode.present?
return postcode
else
return { message: "not found." }
end
end
end
これで、要件を満たしたAPIが完成しました。
2日目まとめ
API実装をTDDで進めていく手法を取り入れました。
本来であればAPIのバージョニングなども考える必要があるのですが、
長くなってしまいますので機会があればご紹介させていただきます。
次回は、UI側の実装をJavaScriptで行う工程をお見せしたいと思います。