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


$ 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で行う工程をお見せしたいと思います。

関連リンク


投稿者 Gaji-Labo Staff

Gaji-Laboの社内デジタル環境でいろいろなお手伝いをしているがじ専務&じら常務。みんなのシリーズ記事をまとめたり、卒業したスタッフの過去記事を記録したり、Twitterをやったりしています。