Mongoidの基礎

## はじめに
業務をしていく中で、mongoidを使う機会があったので、この際にmongoidの基礎をまとめてみました！


Mongoidは、RailsのActive Recordに似せて作られているみたいで、Active Recordと比較して話していきます。

## Mongoidとは？
MongoDB で公式にサポートされているオブジェクト ドキュメント マッパー（ODM）

ODMとは
> 保存するデータを JSON ライクな形式で表現し、ドキュメントとして保存しています。このドキュメントとプログラミング言語のオブジェクトとを対応付ける機能のこと

ドキュメント
>ドキュメントは Mongoid のコア オブジェクトであり、データベースに永続化するすべてのオブジェクトにはMongoid::Documentを含める必要があります。 MongoDB のドキュメントの表現は、Ruby ハッシュまたは JSON オブジェクトに非常によく似た BSON オブジェクトです。 ドキュメントは データベース内の独自のコレクションに保存することも、 n レベルの深度を持つ他のドキュメントに埋め込むこともできます。

## フィールド定義
Active Recordでは、データを永続化する際、一般的にモデルとテーブルスキーマを別々に定義すると思いますが Mongoid では、モデル(フィールド)の定義のみを行います。

公式の引用
> フィールドタイプの定義は、クエリを構築し、データベースからフィールドを検索/書き込みするときの Mongoid の動作を決定します

実際の書き方

Mongoidを使うには、includeでmongoidを読み込みます。

```
class User
  include Mongoid::Document

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

  validates :name, presence: true
end
```

`
id は、(データ型がBSON::ObjectID形式でよければ)勝手についてくるので書かなくても大丈夫です。
`
ここで一緒にvalidationを行うこともできます。Active Recordと似ていますね

## 便利メソッド

### find_by

> 条件に従って最初のDocumentを見つけます。 一致するドキュメントが見つからず、Mongoid. Ops Manager が true に設定されている場合には、Mongoid::Errors::DocumentNotFound が発生します。それ以外の場合は、null を返します。

```
Person.find_by(:username => "superuser")
```

### push

> 配列に単一の値または複数の値をプッシュします。

```
配列に単一の値をプッシュします。
document.push(names: "James", aliases: "007")
配列に複数の値をプッシュします。
document.push(names: [ "James", "Bond" ])
```

`update`や`destory`などのActive Recordにある機能もあります

## リレーションの定義の仕方
Active Recordでいうところの`belongs_to`や`has_many`といった機能が、もちろんmongoidにもあります

### 1 : 1
> Mongoid はhas_one、 ActiveRecord ユーザーに馴染みのあるhas_many、 、belongs_toの has_and_belongs_to_many関連付けをサポートしています。

```
class Band
  include Mongoid::Document
  has_one :studio
end
```

```
class Studio
  include Mongoid::Document
  belongs_to :band
end
```

子のドキュメントに親の参照が含まれる
```
band = Band.create!(studio: Studio.new)
# => #<Band _id: 600114fa48966848ad5bd392, >

band.studio
# => #<Studio _id: 600114fa48966848ad5bd391, band_id: BSON::ObjectId('600114fa48966848ad5bd392')>
```

### 1 : N
>関連付けを使用して、has_many親に 0 個以上の子があり、それらが別のコレクションに格納されていることを宣言します。

```
class Band
  include Mongoid::Document

  has_many :members
end
```

```
class Member
  include Mongoid::Document

  belongs_to :band
end
```
同じようにそれぞれの子のドキュメントに親の参照が含まれる

```
band = Band.create!(members: [Member.new])
# => #<Band _id: 6001166d4896684910b8d1c5, >

band.members
# => [#<Member _id: 6001166d4896684910b8d1c6, band_id: BSON::ObjectId('6001166d4896684910b8d1c5')>]
```

### 埋め込み関連付け 1 : 1

> MongoDB のドキュメント モデルのおかげで、Mongoid は埋め込み関連付けも提供しており、異なるタイプのドキュメントを同じコレクション内に階層的に保存できます。埋め込み関連付けは `embeds_one`、`embeds_many`および`embedded_in`マクロを使用して定義されます

定義
・親にembeds_oneマクロを定義。
・子にembedded_inマクロを定義。
・関連付けの両側で定義が必要です。

```
class Band
  include Mongoid::Document
  embeds_one :label
end

class Label
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end
```

```
band = Band.create!(label: Label.new(name:'Mute'))
# => #<Band _id: 6001166d4896684910b8d1c5, >
```


`embeds_one親のデータベース コレクション内の親内にハッシュとして保存されます。
`

```
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "label" : {
    "_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
    "name" : "Mute",
  }
}
```

### 埋め込み関連付け 1 : N
> 子が親ドキュメントに埋め込まれている 1 対多の関係は、Mongoidembeds_manyとembedded_inマクロを使用して定義されます。

定義
・親にembeds_manyマクロを定義。
・子にembedded_inマクロを定義。
・関連付けの両側で定義が必要です。

```
class Band
  include Mongoid::Document
  embeds_many :albums
end

class Album
  include Mongoid::Document
  field :name, type: String
  embedded_in :band
end
```
`embeds_many`親のデータベース コレクション内の親内にハッシュの配列として保存されます。

```
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "albums" : [
    {
      "_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
      "name" : "Violator",
    }
  ]
}
```


## まとめ
今回、Active RecordからMongoidを使ってみた感想としては、フィールド定義めっちゃ便利で楽だなと思いました。ですが、トランザクションの利用に制限があったり、外部キー制約等(合成外部キーというのがあるらしいが、、)がないこともあり、
整合性が求められるアプリケーションでは、使うことがないかもしれません😨
やっぱActive Recordですね〜

## 参考文献

https://www.mongodb.com/ja-jp/docs/mongoid/current/release-notes/mongoid-9.0/