Sinatra+MongoDB(Mongoid)のメモ


この記事↓の続編（？）です
http://qiita.com/IzumiSy/items/e2b2b05d95f55880e7a0

## MongoDBのインストール＆起動

```bash:Bash
brew update
brew install mongodb
mongod --config /usr/local/etc/mongod.conf
```

## Mongoidの追加と設定
```Ruby:Gemfile
...

gem 'mongoid'
gem 'bson_ext'

...
```
bson_extはMongoDBが内部で使っているデータ交換フォーマットのBSONを高速化するためのgemらしい。

```mongoid.yml
development:
  sessions:
    default:
      database: test
      hosts:
        - localhost:27017

production:
  sessions:
    default:
      uri: <%= ENV['MONGOLAB_URI'] =>
```
- `development/../default/database`に指定するdbを事前に作っておく必要はない。MongoDBは使う際に自動でデータベースを作成してくれる。
- 今回はherokuでMongoLabを使って動かすため、productionのuriには`MONGOLAB_URI`を指定しておく。

## モデル
```user.rb
require 'mongoid'

Mongoid.load!('mongoid.yml')

class User
  include Mongoid::Document

  field :name,  type: String
  field :email, type: String

  validates :name, presence: true
  validates :name, uniqueness: true
  validates :email, presence: true
  validates :email, uniqueness: true
end
```
- MongoidでDBモデルを追加したいときは`Mongoid::Document`を必ずインクルードする
- MySQLやPostgreSQLを使う時とは違ってマイグレーションファイルを用意する必要がない。カラムは直接モデルのクラスの中に`field`を用いて書き足していけば良い。

## コントローラ
エラーハンドリングとか適当だけどこんな感じに。

```main.rb
require 'sinatra/base'
require 'sinatra/param'

require_relative 'models/user'

class UserRoutes < Sinatra::Base
  helpers Sinatra::Param

  get '/api/users/:id' do
    param :id, String, require: true
    user = User.find(params[:id])
    body user.to_json
    status 200
  end

  post '/api/user/signup' do
    param :name,  String, required: true
    param :email, String, required: true
    user = User.create(name:  params[:name], email: params[:email])
    body user.to_json
    status 202
  end

  post '/api/user/signin' do
    param :name,  String, required: true
    param :email, String, required: true

    # Log-in transaction

    status 202
  end
end

class Application < Sinatra::Base
  configure do
    set :raise_errors, true
    set :show_exceptions, false
  end

  enable :logging

  use UserRoutes
end
```
- sinatraでもrailsのstrong_parameterみたいの使いたいな〜と思って調べたら、sinatra-param(https://github.com/mattt/sinatra-param) というのがあるらしいことが分かった。デフォルトだとparamsが指定した条件を満足していないときに`400 Bad Request`を返してくれる
- いまのこのコードだと`User.find(...)`のところで存在しないidが指定されたときにMongoidが例外を出してくるので、APIサーバとしてはこれもどうにかしてハンドリングしないとアカン。

## テストを書く
テストはRSpecとrack-testを使って書いた

```spec_helper.rb
require 'rspec'
require 'json'
require 'rack/test'
require 'database_cleaner'
require_relative '../main.rb'

ENV['RACK_ENV'] = 'test'

RSpec.configure do |config|
  include Rack::Test::Methods

  def app()
    Application.new
  end

  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  config.profile_examples = 10
end
```
- database_cleanerを使っているが、今の時点（2015/11/15）での最新版ではMongoidと併せて使うと`DatabaseCleaner.strategy`に`:truncation`を指定する箇所でエラー（`The 'truncation' strategy does not exist for the mongoid ORM!`）になる。
- database_cleaner自体はMongoidでのtruncationをサポートしているので、これはバグらしい。解決策はGemfileの中でGithubのフェッチ先を指定すること。

```Ruby:Gemfile
...
  gem 'database_cleaner', git: 'https://github.com/DatabaseCleaner/database_cleaner.git'
...
```
テストのファイルそのものはこんな感じに。
基本的にAPIサーバなのでその挙動をチェックするだけのテスト。

```user_routes_spec.rb
require_relative "./spec_helper.rb"

describe "GET /api/:users" do
  it "should get an error in trying to fetch an undefined user " do
   get "/api/users/100"
   expect(last_response.status).to eq(500)
  end
end

describe "POST /api/user/signup" do
  let(:signup_params) {
    { name: "Jonathan",
      email: "jonathan@test.com" }
  }

  it "should NOT make users sign up without params" do
    post "/api/user/signup"
    expect(last_response.status).to eq(400)
  end

  it "should make users sign up with params" do
    post "/api/user/signup", signup_params
    expect(last_response.status).to eq(202)

    userId = JSON.parse(last_response.body)['_id']
    get "/api/users/#{userId}"
    expect(last_response.status).to eq(200)
  end
end

describe "POST /api/user/signin" do
  it "should not make users sign in without params" do
    post "/api/user/signin"
    expect(last_response.status).to eq(400)
  end
end
```
- `be_success`とか`have_http_status(:ok)`が使えなくて困ってたら、この辺は全部rspec-railsの持ってるメソッドらしい。なのでここではeqでマッチ指定をする。

## 参考資料
- http://qiita.com/morizyun/items/b9f7d8f79803d80e9a21
- https://rubygems.org/gems/bson_ext/versions/1.12.3
- http://qiita.com/k-ta-yamada/items/72956c13049d583150e9
- https://github.com/DatabaseCleaner/database_cleaner/issues/299
- http://qiita.com/ori_ika/items/ea29455212f2e5e71e0b
- http://stackoverflow.com/questions/18638745/mongoiderrorsdocumentnotfound-raise-not-found-error