RailsでGemを使わずにミニマムなAPIサーバーを書いてみる

この記事は[自分のブログ](http://itidiary.hatenablog.com/)をqiitaにも投稿した形の記事ですです。

皆さんこんにちは。Ruby on Railsもversion5.2.0がリリースされたのを知って何か作りたくなったので作ってみた

最寄のバス停のバス時間がわかるオレオレAPI(時間しか書いて無くて他の人が見ても得しない)だ

# なんで作ったの
別にサイトとかで確認するなり、もしくはエンジニアらしくスクレイピングしたアプリケーションでも作ればいいじゃない、と思うだろう。だが、私の乗るバスの公式サイトはとにかく重い、重すぎるんだ。マジでどんな実装してるのか信じられない。そしてUIがわかりづらくて探しづらいのだ

だからAPIを作ってみた。
# 作っていく
まず、Railsのupdateをしてなかったのでする

```
gem update rails
```

そしてrails new(APIモードにしてもよかったけど忘れて作ってた)

```
rails new mybus_api
```

## DBモデルを作る
とりあえず、平日、土、日祝で別れてるのでモデルを３つ作ろうと考えたが、平日は本数が当たり前だがかなり多いので朝、昼、夕分のモデルを作った

時間だけだと物足りないので料金のカラムも追加してみた(駅まで行くバスだから全部同じ値段なんだけどね)

平日のモデル(朝、昼、夕)

```
rails g model morning time:string money:string
rails g model noon time:string money:string
rails g model afternoon time:string money:string
```

土曜日のモデル

```
rails g model sat time:string money:string
```

日祝のモデル

```
rails g model sun time:string money:string
```

モデルを作り終えたらマイグレーションしよう

```
rake db:migrate // もしくはrails db:migrate
```
## APIのコントローラーを書く
apiだと認識できるように、且つバージョニングをしたいので以下のようにする
```
cd app/controllers
mkdir -p api/v1
```
平日はその中でもモデルを3つ持つのでweekdayという名前のディレクトリを作ることにした
```
cd api/v1
mkdir weekday
```
そしてコントローラーは今回ビューを使わないので、touchコマンドで新規ファイル作成をした。

```
~/api/v1$ touch sat_controller.rb
~/api/v1$ touch sun_controller.rb

// 平日はweekdayの下で
~/api/v1/weekday$ touch morning_controller.rb
~/api/v1/weekday$ touch noon_controller.rb
~/api/v1/weekday$ touch afternoon_controller.rb
```


### コントローラーの書き方
平日

```ruby
class Api::V1::Weekday::MorningController < ApplicationController
  def index
    time = Morning.all
    render json: time
  end
  def show
    time = Morning.where(id:params[:id])
    render json: time
  end
end
```

土曜日

```ruby
class Api::V1::SatController < ApplicationController
  def index
    time = Sat.all
    render json: time
  end
  def show
    time = Sat.where(id:params[:id])
    render json: time
  end
end
```

以上のような感じで他の曜日や時間帯のものも書く

### 例外処理
404と500に対応した。例外のクラスを扱って手軽にできるのでいいね

```ruby
class ApplicationController < ActionController::Base
  # protect_from_forgery with: :null_session
  rescue_from ActiveRecord::RecordNotFound, with: :render_404
  rescue_from ActionController::RoutingError, with: :render_404
  rescue_from Exception, with: :render_500

  def render_404
    render :json => { message:'This page is not found', status:404 }
  end

  def render_500
    render :json =>{ message:'Internal server error', status:500 }
  end
end
```
## ルーティング
作ったディレクトリに合わせてnamespaceを使って普通に書いてみた。もっといい方法があるかも（？）
specifyルーティングはid指定用のものだ。

```ruby
Rails.application.routes.draw do
  namespace :api, { format: 'json'} do
    namespace :v1 do
      # Weekday has many time of bus. so divide to three time zone
      namespace :weekday do
        namespace :morning do
          get "/", :action => "index"
          get "/specify", :action =>"show"
        end
        namespace :noon do
          get "/", :action => "index"
          get "/specify", :action =>"show"
        end
        namespace :afternoon do
          get "/", :action => "index"
          get "/specify", :action =>"show"
        end
      end
      # saturday
      namespace :sat do
        get "/", :action => "index"
        get "/specify", :action =>"show"
      end
      # sunday or holiday
      namespace :sun do
        get "/", :action => "index"
        get "/specify", :action =>"show"
      end
    end
  end
  # 404だった場合の書き方　*pathで存在しないルーティングになるみたいだ
  get '*path', controller: 'application', action: 'render_404'
  root 'home#index'
end
```

## リソースの作成
今回は取得(GETリクエスト)のみをするAPIなので、新たに作成する(POSTリクエスト)データはない。あらかじめ時間と料金のデータを作る。

```
mkdir data
cd data
touch sunholiday.json
touch saturday.json

touch morning.json
touch noon.json
touch afternoon.json
```

dataディレクトリに以下のようなjsonを作成した。あまり頭のいいやり方じゃないけど本数が少ない田舎なので手打ちしてしまった。

```json
[
  {
    "time":"11:31",
    "money":"330JPY"
  },
  {
    "time":"12:04",
    "money":"330JPY"
  },
  {
    "time":"12:46",
    "money":"330JPY"
  },
  {
    "time":"13:36",
    "money":"330JPY"
  },
  {
    "time":"14:06",
    "money":"330JPY"
  },
  {
    "time":"14:36",
    "money":"330JPY"
  },
  {
    "time":"15:01",
    "money":"330JPY"
  },....もっと続く
```

## db/seed.rbを使って初期データをモデルに投下する
以下リンクを参考にした


[https://www.rubylife.jp/rails/model/index10.html:embed:cite]


簡単にjsonをパースして保存するスクリプトを書いてみた。

```ruby
def make_data(file_name,model)
  File.open("data/#{file_name}") do |file|
    json = JSON.load(file)
    json.each do |get_object|
      time  = get_object['time']
      money = get_object['money']
      data = model.new({time:time,money:money})
      data.save
    end
  end
end

make_data('sunholiday.json',Sun)
make_data('saturday.json',Sat)
make_data('morning.json',Morning)
make_data('noon.json',Noon)
make_data('afternoon.json',Afternoon)
```

コマンドを実行してseedする

```
rake db:seed
```

## 動かしてみよう
ローカルサーバーを立ち上げてGETリクエストを送ってみる

```
rails s
```

curlで普通に叩く

```
curl http://localhost:3000/api/v1/sat/specify.json?id=2
```

結果

```json
[{"id":2,"time":"07:34","money":"330yen","created_at":"2018-04-17T12:37:44.052Z","updated_at":"2018-04-17T12:37:44.052Z"}]
```

うまくいった。作りかたをここまで書いただけだが、丁寧にやるならこまめに動作させたり、railsコンソールでデバッグ、ユニットテストを書くことをしたりするといいだろう。

コードはGitHubにあげておいた。APIの仕様もここのREADMEに書いてある。

https://github.com/ItinoseSan/MyBusAPI

# 反省
もっと良い方法があると思った。とりあえず気になってる点はあるので気が向いたら直すかな。