Mongoのリレーションについてまとめてみた


# 通常のリレーション
## 1対1

### モデル作成

```
rails g model has_one name:string
rails g model belongs_to name:string
```

```ruby:has_one
class HasOne
  include Mongoid::Document
  field :name, type: String

  has_one :belongs_to
end
```

```ruby:belongs_to
class BelongsTo
  include Mongoid::Document
  field :name, type: String

  # inverse_ofの指定がないと BelonsTo.first.has_oneの検索が行えない
  belongs_to :has_one, :inverse_of => :belongs_to
end
```
### 検索
```ruby:RailsConsole
# HasOneに紐づくBelongsToを取得
HasOne.first.belongs_to
# BelongsToに紐づくHasOneを取得
BelongsTo.first.has_one
```

### 追加
```ruby:RailsConsole
# 個別に作成
HasOne.create(name: "HasOne name")
BelongsTo.create(name: "belongs_to")

# いっぺんに作成
HasOne.create(name: "HasOne name").belongs_to = BelongsTo.create(name: "belongs_to name")
```

## 1対多

本と本棚の関連を1対多で表してみる。

### モデル作成

```ruby:Railsコマンド
rails g model book_shelf name:string price:integer
rails g model book name:string price:integer
```

### 検索

```ruby:RailsConsole
# 本棚が複数の本を所持しているので、Arrayで帰ってくる
book_shelf = BookShelf.first.books
# 指定の本がしまってある本棚を取得
books = Book.first.book_shelf
```

### 追加

```ruby:RailsConsole
book_shelf = BookShelf.create(name: "book shelf name", price: 12000)
book_shelf.books.create(name: "book name one", price: 1200)
book_shelf.books.create(name: "book name two", price: 500)
# これでも追加できる
book_shelf.books << Book.create(name: "book name three", price: 300)
```

## 多対多

使う場面としてはRDBの中間テーブルを作るような場合に多対多(has_and_belongs_to_many)で定義する。
記事に紐づくカテゴリを例に作成してみる。


### モデル作成

```ruby:Railsコマンド
rails g model Article title:string text:string
rails g model Category name:string slang:string
```

```ruby:article.rb
class Article
  include Mongoid::Document
  field :title, type: String
  field :text, type: String

  has_and_belongs_to_many :categories
end
```

```ruby:category.rb
class Category
  include Mongoid::Document
  field :name, type: String
  field :slang, type: String

  has_and_belongs_to_many :articles
end
```

### 検索

```ruby:RailsConsole
# Categoryに紐づくArticleを取得
Category.first.articles
# Articleに紐づくCategoryを取得
Article.first.categories
```

### 追加

```ruby:RailsConsole
# Articleと紐づくCategoryを同時に新規追加
Article.create(title: "article title 1", text: "article text 1").categories.create(name: "category name", slang: "category slang")

# Articleは新規追加を行い既存のCategoryを紐付ける
Article.create(title: "article title 2", text: "article text 2").categories << Category.first
```

```MongoShell
# Article
> db.articles.find().forEach(printjson)
{
	"_id" : ObjectId("5638eb5a892bea2b5a000000"),
	"title" : "article title 1",
	"text" : "article text 1",
	"category_ids" : [
		ObjectId("5638eb5a892bea2b5a000001")
	]
}
{
	"_id" : ObjectId("5638ee0d892bea2b5a000002"),
	"title" : "article title 2",
	"text" : "article text 2",
	"category_ids" : [
		ObjectId("5638eb5a892bea2b5a000001")
	]
}

# Category
> db.categories.find().forEach(printjson)
{
	"_id" : ObjectId("5638eb5a892bea2b5a000001"),
	"name" : "category name",
	"slang" : "category slang",
	"article_ids" : [
		ObjectId("5638eb5a892bea2b5a000000"),
		ObjectId("5638ee0d892bea2b5a000002")
	]
}
```

# 継承

家族の関連を継承で表してみる。

```shell:クラス構造
Family (has_many)
│
Person (belongs_to)
  ├── Parent
  │     ├── Father
  │     └── Mother
  └── Child
        ├── Brother
        └── Sister
```

FamilyとPersonは1対多の関係でリレーションが結ばれていて、Person以下は上位のクラスを継承している

### モデル作成

```ruby:Railsコマンド
rails g model Family family_name:string
rails g model Person name:string age:integer
rails g model Parent
rails g model Father 
rails g model Mother
rails g model Child
rails g model Brother 
rails g model Sister
```

```ruby:family
class Family
  include Mongoid::Document
  field :family_name, type: String
  has_many :people

  # familyとparentのリレーションを宣言しないと Family.first.parents のように呼べない
  has_many :parents
  has_many :children
  has_many :fathers
  has_many :mothers
  has_many :sisters
  has_many :brothers
end
```

```ruby:person
class Person
  include Mongoid::Document
  field :name, type: String
  field :age, type: Integer

  belongs_to :family
end
```

```ruby:parent
class Parent < Person
  include Mongoid::Document
end
```

```ruby:mother
class Mother < Parent
  include Mongoid::Document
end
```

```ruby:father
class Father < Parent
  include Mongoid::Document
end
```

```ruby:child
class Child < Person
  include Mongoid::Document
end
```

```ruby:sister
class Sister < Child
  include Mongoid::Document
end
```

```ruby:brother
class Brother < Child
  include Mongoid::Document
end
```

### 検索

```ruby:RailsConsole
# 指定の家族全員を取得
Family.first.people
# 指定の家族の親のみ取得
Family.first.parents
# 指定の家族の子供のみ取得
Family.first.children
# 指定の家族の兄弟のみ取得
Family.first.brothers
```

### 追加

#### newを使う方法

```ruby:RailsConsole
# 家族を追加
family = Family.create(family_name: "Yamada")

# 家族の人を追加
father = Father.new(name: "father_name", age: 30)
mother = Mother.new(name: "mother_name", age: 29)
brother1 = Brother.new(name: "first_brother_name", age: 6)
brother2 = Brother.new(name: "second_brother_name", age: 4)
sister = Sister.new(name: "first_sister_name", age: 7)
family.people = [father, mother, brother1, brother2, sister]
```

```MongoShell
# families
> db.families.find().forEach(printjson)
{ "_id" : ObjectId("56361e08892bea1af6000006"), "family_name" : "Yamada" }

# people
> db.people.find().forEach(printjson)
{
	"_id" : ObjectId("56361e08892bea1af6000007"),
	"_type" : "Father",
	"name" : "father_name",
	"age" : 30,
	"family_id" : ObjectId("56361e08892bea1af6000006")
}
{
	"_id" : ObjectId("56361e08892bea1af6000008"),
	"_type" : "Mother",
	"name" : "mother_name",
	"age" : 29,
	"family_id" : ObjectId("56361e08892bea1af6000006")
}
{
	"_id" : ObjectId("56361e08892bea1af6000009"),
	"_type" : "Brother",
	"name" : "first_brother_name",
	"age" : 6,
	"family_id" : ObjectId("56361e08892bea1af6000006")
}
{
	"_id" : ObjectId("56361e08892bea1af600000a"),
	"_type" : "Brother",
	"name" : "second_brother_name",
	"age" : 4,
	"family_id" : ObjectId("56361e08892bea1af6000006")
}
{
	"_id" : ObjectId("56361e08892bea1af600000b"),
	"_type" : "Sister",
	"name" : "first_sister_name",
	"age" : 7,
	"family_id" : ObjectId("56361e08892bea1af6000006")
}
```

#### createを使う方法
```ruby:RailsConsole
Family.first.sisters.create(name: "build_sister_name", age: 5)
```

# 埋め込み型のリレーション
## 埋め込み (embedds)

### モデル作成

```railsコマンド
rails g model parents name:string
rails g model children parent:references name:string
```

```model/parent.rb
class Parent
  include Mongoid::Document
  field :name, type: String

  embeds_many :children
end
```

```model/child.rb
class Child
  include Mongoid::Document
  field :name, type: String

  embedded_in :parent, :inverse_of => :children
end
```

### 追加

```ruby:RailsConsole
parent = Parent.create(parent_name: "parent_name", parent_age: 24)
# embedds_manyなのでArrayを追加する
parent.children << [
  Child.new(child_name: "child_name_1", child_age: 1),
  Child.new(child_name: "child_name_2", child_age: 2)
]
```

```MongoShell
> db.parents.find().forEach(printjson)
{
	"_id" : ObjectId("562fc7c0892bea15e2000000"),
	"parent_name" : "parent_name",
	"parent_age" : 24,
	"children" : [
		{
			"_id" : ObjectId("562fc973892bea16cf000000"),
			"child_name" : "child_name_1",
			"child_age" : 1
		},
		{
			"_id" : ObjectId("562fc978892bea16cf000001"),
			"child_name" : "child_name_2",
			"child_age" : 2
		}
	]
}
```

### 更新

```
# childrenはArrayなので、firstやfindでChildクラスを取得してからsave
child = Parent.first.children.first
child.child_name = "child_name_save"
child.save

# update_attributesでも可能
Parent.first.children.first.update_attributes(child_name: "child_name_update", child_age: 1)
```

### 削除

```shell:RailsConsole
# childrenはArrayなので、firstやfindでChildクラスを取得してからdelete
Parent.first.children.first.delete
```