【備忘録】Rails＋MongoDBでDocumentNotFoundが出力された際のmongoid.yml設定

Rails+MongoDBにて
RailsからDBアクセスした際、DBにレコードがなかった時に
本来はエラーメッセージを画面内に設定するなど、
次のアクションを起こしたいところを
エラー画面DocumentNotFoundが出た時の対策です。

ローカル環境の例
---

ログインを行い、DBにレコードがあった場合、
次画面遷移を行う。なければエラーメッセージを出力し
再度ログイン画面を読み込む。
という処理を行いたい想定です。

```eb
    def Login
        @user = User.find_by(name: params[:name])
        if @user && @user.authenticate(params[:password])
            # 次画面遷移
            redirect_to("/Hoge")
        else
　　　　　　　# エラーメッセージだす
            @errorMessage = "ログインに失敗"
            render("/top")
        end
    end
```

![スクリーンショット 2018-12-15 18.14.22.png](https://qiita-image-store.s3.amazonaws.com/0/238856/65812e05-7866-e1aa-5009-bd2cb8422a0c.png)

↓存在しないユーザでログインする
![スクリーンショット 2018-12-15 18.20.34.png](https://qiita-image-store.s3.amazonaws.com/0/238856/2a020779-5254-f66e-b7d5-09b20d106779.png)

再度ログイン画面を読み込みたいところ、謎のDocumentNotFoudエラー発生です。。

原因
---
この例では、DBを検索して一件のみを取得する際に、find_byを使用して検索を行なっていますが、ここでエラーがおきています。
ググった結果、mongoDBのfind_byメソッドにエラーの設定がされています。
[MongoDB公式ドキュメントのfind_byのクエリメソッドの実装]
(https://github.com/mongodb/mongoid/blob/71c29a80599087008ecfbd057d3d049c2153c7ce/lib/mongoid/findable.rb#L113)
を確認すると

```findable.rb
    # Find the first +Document+ given the conditions.
    # If a matching Document is not found and
    # Mongoid.raise_not_found_error is true it raises
    # Mongoid::Errors::DocumentNotFound, return null nil elsewise.
    #
    # @example Find the document by attribute other than id
    #   Person.find_by(:username => "superuser")
    #
    # @param [ Hash ] attrs The attributes to check.
    #
    # @raise [ Errors::DocumentNotFound ] If no document found
    # and Mongoid.raise_not_found_error is true.
    #
    # @return [ Document, nil ] A matching document.
    #
    # @since 3.0.0
    def find_by(attrs = {})
      result = where(attrs).find_first
      if result.nil? && Mongoid.raise_not_found_error
        raise(Errors::DocumentNotFound.new(self, attrs))
      end
      yield(result) if result && block_given?
      result
    end
```

コメントをみると、
>　# @raise [ Errors::DocumentNotFound ] If no document found
>　# and Mongoid.raise_not_found_error is true.

とあります。レコードが存在しなくMongoid.ymlのraise_not_found_errorがtrueの場合はDocumentNotFoundが出力されます。



対策(ローカル環境編)
---
`config/mongoid.yml`の設定を見てみる

```mongoid.yml
development:
  clients:
    default:
・・・
  options:
・・・
    # Raise an error when performing a #find and the document is not found.
    # (default: true)
    #raise_not_found_error: true

```

デフォルトではraise_not_found_errorはtrueに設定されているため、DocumentNotFoundが出力される・・・
ここをfalseにします。

` #raise_not_found_error: false`

また、設定ファイルを反映するためには
ローカルのサーバを再起動させる必要があります。

`$ rails s`


動作確認(ローカル環境)
---

![スクリーンショット 2018-12-15 18.14.22.png](https://qiita-image-store.s3.amazonaws.com/0/238856/65812e05-7866-e1aa-5009-bd2cb8422a0c.png)
↓ログイン

![スクリーンショット 2018-12-16 1.36.43.png](https://qiita-image-store.s3.amazonaws.com/0/238856/7cff4490-01b2-4561-8da0-e16b5c84a8e9.png)

無事DocumentNotFoundにならなくなりました。

heroku環境の例と対策
---
次にローカル以外の環境の例です
ここではherokuでの例をあげます
![スクリーンショット 2018-12-15 18.14.22.png](https://qiita-image-store.s3.amazonaws.com/0/238856/65812e05-7866-e1aa-5009-bd2cb8422a0c.png)
↓存在しないユーザでログイン

![スクリーンショット 2018-12-15 18.20.48.png](https://qiita-image-store.s3.amazonaws.com/0/238856/eecbdb98-df9a-ee6e-9d11-967c6917b559.png)

ページが存在しないエラーになりました。
コマンドにてログを確認します。
(事前にherokuにログインが必要です`$ heroku login`)

`$ heroku logs -t`

```
2018-12-15T08:50:55.791954+00:00 app[web.1]: F, [2018-12-15T08:50:55.791887 #4] FATAL -- : [a510ff0f-8ab4-4197-a560-8ef1fc93e3b5] Mongoid::Errors::DocumentNotFound (
2018-12-15T08:50:55.791957+00:00 app[web.1]: message:
2018-12-15T08:50:55.791960+00:00 app[web.1]: Document not found for class Like with attributes {:user_id=>BSON::ObjectId('hoge'), :post_id=>BSON::ObjectId('piyo')}.
2018-12-15T08:50:55.791961+00:00 app[web.1]: summary:
2018-12-15T08:50:55.791963+00:00 app[web.1]: When calling Like.find_by with a hash of attributes, all attributes provided must match a document in the database or this error will be raised.
2018-12-15T08:50:55.791965+00:00 app[web.1]: resolution:
2018-12-15T08:50:55.791967+00:00 app[web.1]: Search for attributes that are in the database or set the Mongoid.raise_not_found_error configuration option to false, which will cause a nil to be returned instead of raising this error.):
```
この例では次画面遷移時にLikeというコレクションを読んでいますが、その際にMongoid::Errors::DocumentNotFoundが発生しています。原因がLike.find_byを実行時にレコードがなかったためにエラってます。対策としてデータを用意するか、mongoid.ymlのraise_not_found_errorオプションをfalseにすると良い。と書いています。

ここではデータがない場合も想定しているのでMongoid.ymlのraise_not_found_error:falseにします。
デフォルトではoptions自体がないので追記します。
(optionsの階層に注意してください。
productionの直下です。clientsの直下ではないです。)

```mongid.yml
production:
 clients:
   default:
    uri: mongodb://heroku_9v5bk0vx:fuga@hage.mlab.com:37977/heroku_9v5bk0vx
    options:
      connect_timeout: 15
 options:
    raise_not_found_error: false
```

mongoid.ymlを設定後にherokuにデプロイ

動作確認(heroku環境)
---
![スクリーンショット 2018-12-15 18.14.22.png](https://qiita-image-store.s3.amazonaws.com/0/238856/65812e05-7866-e1aa-5009-bd2cb8422a0c.png)
↓ログイン
![スクリーンショット 2018-12-16 2.08.13.png](https://qiita-image-store.s3.amazonaws.com/0/238856/5da0a21b-5055-452f-6487-0074fdab7bd2.png)


herokuでも無事DocumentNotFoundにならなくなりました。


環境
---
(今回のエラーはvarsionは関係ないですが載せておきます)

```
Rails 5.2.1
ruby 2.3.7p456
MongoDB v4.0.2
```


参照
---
[Mongoidの隠れクエリメソッドfind_by!とraise_not_found_errorオプションの関係](https://qiita.com/IzumiSy/items/9c1500dcc907db6b44b6)
ありがとうございます。