簡単なAPIを作成し、テストコードも書いてみる

Ruby on Railsを使用して、テストコードを含めて簡単なAPIを作成してみようと思います。
私自身が初心者ですので、内容は非常に初心者向けになります。
基本は<a href="https://www.amazon.co.jp/%E3%83%91%E3%83%BC%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88-Ruby-Rails-%E3%81%99%E3%81%8C%E3%82%8F%E3%82%89-%E3%81%BE%E3%81%95%E3%81%AE%E3%82%8A/dp/4774165166" target="_blank">パーフェクト Ruby on Rails</a>を見ながら実施し、補足などあれば作業メモとして残していきます。
# 今回作成するAPIについて
クライアントからリクエストされたIDでDBの値を返します。
そのため、適当なテーブルを作成します。
簡単な`scaffold`を利用します。

```
$ rails g scaffold item name:string price:integer description:text
$ rails db:migrate
```
こんなテーブルが作成できました。

| id | name | price | description | created_at | updated_at |
|:----------------:|:-----------------:|:------------------:|:------------------:|:------------------:|:-----------------:|
| integer | varchar | integer | text | datetime | datetime |
| NOT NULL, PRIMARY KEY |  |  |  | NOT NULL | NOT NULL |
## 余談
### SQLiteで`SHOW COLUMNS`をやる
<a href="http://d.hatena.ne.jp/sutara_lumpur/20120823/1345689906" target="_blank">(PHP, MySQL, SQLite3) テーブルのフィールド名一覧を取得するには</a>

```sql
PRAGMA table_info(items);
```
### `rails g scaffold`で特定のファイルを生成しない
今回は全て生成してしまいましたが、今後`assets`などを生成したくないときは下記の設定を追記する必要があるようです。
<a href="http://tmiyamon.github.io/blog/2014/11/13/configure-the-behavior-of-scaffolding-in-rails/" target="_blank">Railsのgenerateコマンドを俺好みに設定する</a>

```diff:config/application.rb
+ config.generators do |g|
+   g.helper false
+   g.assets false
+   g.test_framework false
+   g.orm :active_record, migration: false
```
# itemsテーブルのレコードを返すAPI
まず、コントローラーとビューを作成しました。
(erbファイルについては割愛します。）

```rb:app/controllers/api_controller.rb
class ApiController < ApplicationController
  # GET /get_items
  # GET /get_items.json
  def get_items
    @items = Item.all
    respond_to do |format|
      format.html
      format.json { render json: @items }
    end
  end
end
```

```diff:routes.rb
+ get 'api/get_items', to: 'api#get_items'
```
これで`api/get_items.json`でアクセスすると、JSON形式で返してきます。

![スクリーンショット 2016-10-02 18.07.53.png](https://qiita-image-store.s3.amazonaws.com/0/118817/2a0f21e1-f3cf-3cc2-cffd-41be8d6a237b.png)
これはこれで良いかな、とも思いましたが、Ruby on RailsにはJbuilderというJSON生成のGemがあります。
こちらは初めから`Gemfile`に記述されています。

```rb:Gemfile
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
```
実は`scaffold`で生成すると勝手に生成してくれてもいました。
jbuilderファイルを作成する意味としては、複数のモデルの値を返却したり、URLなどを返却するなど複雑な返却値のとき使用するようです。
今回は、`scaffold`で生成されていたファイルを参考に、以下のように作成しました。

```rb:get_items.json.jbuilder
json.array! @items do |item|
  json.extract! item, :id, :name, :price, :description, :created_at, :updated_at
  json.url item_url(item, format: :json)
end
```
これで、`items`テーブルの各カラムの値とURLが返されるようになりました。
（URLはお好みかと思います。）
このjbuilderファイルを作成しておくと、コントローラーの方でもjbuilderファイルを参照するため、下記のように変更できます。

```diff:app/controllers/api_controller.rb
- format.json { render json: @items }
+ format.json
```
これでも下記のようにブラウザで確認できると思います。

![スクリーンショット 2016-10-02 18.05.43.png](https://qiita-image-store.s3.amazonaws.com/0/118817/a3fb6102-131c-84d6-2ee6-fd53b31ebf23.png)
### APIリクエスト時にパラメーターを渡される想定
例えば、items.idの2〜5まで欲しいなどのリクエストを想定し、下記のように修正します。

```diff:app/controllers/api_controller.rb
- @items = Item.all
+ before_id = params[:before_id]
+ after_id = params[:after_id]
+ @items = Item.where(id: before_id..after_id)
```
これで`@items`に指定された範囲のレコードが入るので、JSON生成も絞られます。
ブラウザで確認もできますが、テストコードも書いてみたいと思います。
# Rspecのインストール
Ruby on Railsには多くのテストフレームワークがあるようですが、今回は`Rspec`を利用します。
Rspec → <a href="http://rspec.info/about/" target="_blank">http://rspec.info/about/</a>

```diff:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
+ gem 'rspec-rails'
end
```
インストール後、`Rspec`の設定ファイルを生成します。

```
$ bundle install
$ rails g rspec:install
    create .rspec
    create spec
    create spec/spec_helper.rb
```
とりあえず設定ファイルは置いておき、モデル用のテストファイルを生成します。

```
$ rails g rspec:model item
    create spec/models/item_spec.rb
```
簡単なテストコードを書いてみます。
`Rspec`の書き方については、こちらの方が非常に分かりやすく記事にしてくださっております。
<a href="http://qiita.com/jnchito/items/42193d066bd61c740612" target="_blank">使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」</a>

```rb:spec/models/event_spec.rb
require 'rails_helper'

RSpec.describe Item, type: :model do
  describe '#name' do
    context '空白のとき' do
      let(:item) { Item.new(name: '') }

      it 'validでないこと' do
        item.valid?
        expect(item.errors[:name]).to be_present
      end
    end
  end
end
```
テストしてグリーンになればOKです。

```
$ bundle exec rspec spec/models/item_spec.rb
```
## 余談
### Rspecをインストールした後にscaffoldを生成すると、specディレクトリ以下が生成される
<a href="http://blog.jnito.com/entry/2015/08/23/084834" target="_blank"> Rails Q＆A「Scaffoldで作成されるテストはそのまま使うべきか？」 </a>
# FactoryGirl
テスト用にレコードを作成してくれる`FactoryGirl`を使ってみます。
FactoryGirlのGithub → <a href="https://github.com/thoughtbot/factory_girl" target="_blank">https://github.com/thoughtbot/factory_girl</a>

```diff:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
  gem 'rspec-rails'
+ gem 'factory_girl_rails'
end
```
`FactoryGirl`用のファイルも生成します。

```
$ bundle install
$ rails g factory_girl:model item
    create spec/factories/item.rb
```
`FactoryGirl`の定義をします。

```rb:spec/factories/items.rb
FactoryGirl.define do
  factory :item do
    sequence(:name) { |i| "商品名#{i}" }
    sequence(:price) { |i| i }
    sequence(:description) { |i| "説明#{i}" }
  end
end
```
これで、`FactoryGirl.create(:item)`でレコードをインサートしてくれます。
試しにコンソールで確認します。

```
$ rails c
Running via Spring preloader in process 50595
Loading development environment (Rails 5.0.0.1)
irb(main):001:0> FactoryGirl.create(:item)
   (0.1ms)  begin transaction
  SQL (2.9ms)  INSERT INTO "items" ("name", "price", "description", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "商品名1"], ["price", 0], ["description", "説明1"], ["created_a016-10-02 05:32:52 UTC], ["updated_at", 2016-10-02 05:32:52 UTC]]
   (3.6ms)  commit transaction
=> #<Item id: 1, name: "商品名1", price: 0, description: "説明1", created_at: "2016-10-02 05:32:52", updated_at: "2016-10-02 05:32:52">
```
2回目は「商品名2」、3回目は「商品名3」。。。と入っていきます。
こちらをテストコード中で呼べば、オブジェクトを生成してくれますが、`FactoryGirl`の設定ファイルに下記のように記述すれば、`create(:item)`のみで呼べるようになります。

```diff:spec/spec/helper.rb
+ require 'factory_girl_rails'

RSpec.configure do |config|

+  config.include FactoryGirl::Syntax::Methods

# 以下、省略
```
この`FactoryGirl`を利用して先ほどのテストコードに追記してみます。
今回はちゃんとしたレコードが入るので、「名前が空欄でないときはエラーが出ない」ことを確認します。

```diff:spec/models/item_rspec.rb
+    context '空白じゃないとき' do
+      let(:item) { create(:item) }
+
+      it 'validであること' do
+        item.valid?
+        expect(item.errors[:name]).to_not be_present
+      end
+    end
```
こちらでもオールグリーンになりました。

```
bundle exec rspec spec/models/item_spec.rb
```
# API用のテストコードを書いてみる
下記のような形で、GETでパラメーターを渡し、取得したレコード数、`before_id`で取得した値、`after_id`で取得した値を比較しております。
今回はAPI用のテストコードになるため、`spec/requests`ディレクトリに作成します。
`FactoryGirl`の`create_list`メソッドですが、これは第2引数で渡した数だけレコードを生成してくれます。

```rb:spec/requests/items_rspec.rb
require 'rails_helper'

RSpec.describe "Items", type: :request do
  describe "GET /api/get_items" do
    let(:path) { '/api/get_items.json' }
    context "GETパラメータ-が渡されているとき" do
      before do
        @items = FactoryGirl.create_list(:item, 10)
        @params = { before_id: 2, after_id: 5 }
      end

      it "指定範囲のレコードが取得できる" do
        get path, params: @params

        expect(response.status).to eq 200

        json = JSON.parse(response.body)
        expect(json[0]['id']).to eq @items[1].id
        expect(json[3]['id']).to eq @items[4].id

        expect(json.size).to eq(4)

      end
    end
  end
end
```
## 余談
### デバッグするとき
いろいろ方法はあるかと思いますが、とりあえずテストコード中に`puts`で埋め込んでおりました。
（PHPのときも`var_dump`やログ出力の簡単なものしか使っていなかったので。。。）

```diff:spec/requests/items_rspec.rb
      it "指定範囲のレコードが取得できる" do
        get path, params: @params

        expect(response.status).to eq 200
+ puts response.body
        json = JSON.parse(response.body)
        expect(json[0]['id']).to eq @items[1].id
        expect(json[3]['id']).to eq @items[4].id

        expect(json.size).to eq(4)

      end
```
# 最後に
私がRuby on Railsの勉強を開始するときに参考にさせていただいた<a href="https://www.railstutorial.org/book/beginning" target="_blank">RUBY ON RAILS TUTORIAL</a>でも著者が書いておりましたが、「何かの機能を追加するとき、まずはテストコードから書く」という習慣を身に付けたいと思います。
簡単ではございますが、今回はこれで終了とさせていただきます。