Rails
mongoid
mongo

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

More than 3 years have passed since last update.

通常のリレーション

1対1

モデル作成

rails g model has_one name:string
rails g model belongs_to name:string
has_one
class HasOne
  include Mongoid::Document
  field :name, type: String

  has_one :belongs_to
end
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

検索

RailsConsole
# HasOneに紐づくBelongsToを取得
HasOne.first.belongs_to
# BelongsToに紐づくHasOneを取得
BelongsTo.first.has_one

追加

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対多で表してみる。

モデル作成

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

検索

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

追加

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)で定義する。
記事に紐づくカテゴリを例に作成してみる。

モデル作成

Railsコマンド
rails g model Article title:string text:string
rails g model Category name:string slang:string
article.rb
class Article
  include Mongoid::Document
  field :title, type: String
  field :text, type: String

  has_and_belongs_to_many :categories
end
category.rb
class Category
  include Mongoid::Document
  field :name, type: String
  field :slang, type: String

  has_and_belongs_to_many :articles
end

検索

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

追加

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
# 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")
    ]
}

継承

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

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

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

モデル作成

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
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
person
class Person
  include Mongoid::Document
  field :name, type: String
  field :age, type: Integer

  belongs_to :family
end
parent
class Parent < Person
  include Mongoid::Document
end
mother
class Mother < Parent
  include Mongoid::Document
end
father
class Father < Parent
  include Mongoid::Document
end
child
class Child < Person
  include Mongoid::Document
end
sister
class Sister < Child
  include Mongoid::Document
end
brother
class Brother < Child
  include Mongoid::Document
end

検索

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

追加

newを使う方法

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]
# 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を使う方法

RailsConsole
Family.first.sisters.create(name: "build_sister_name", age: 5)

埋め込み型のリレーション

埋め込み (embedds)

モデル作成

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

追加

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)
]
> 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)

削除

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