Docs Menu
Docs Home
/ / /
몽고이드
/

연관 관계

이 페이지의 내용

  • 참조된 연관 관계
  • Has One
  • Has Many
  • Belongs To
  • Has And Belongs To Many
  • 참조된 연관 관계 쿼리
  • 내장된 연관 관계
  • Embeds One
  • Embeds Many
  • 재귀적 임베딩
  • 참조 대 임베딩
  • 내장된 연관 관계 쿼리
  • _id 필드의 생략
  • 삭제
  • 해시 할당
  • 일반적인 행동
  • 확장 프로그램
  • 사용자 정의 연관 관계 이름
  • 사용자 정의 프라이머리 & 외래 키
  • 사용자 정의 범위
  • 유효성 검사
  • 다형성
  • 계단식 콜백
  • 종속 행동
  • 자동 저장
  • 존재 여부 판단 조건문
  • 자동 빌딩
  • 터치
  • 카운터 캐시 옵션
  • 연관 관계 프록시
  • 연관 관계 메타데이터
  • 속성
  • 연관 관계 객체

Mongoid는 ActiveRecord 사용자에게 익숙한 has_one, has_many, belongs_tohas_and_belongs_to_many 연관 관계를 지원합니다.

has_one 매크로를 사용하여 부모가 별도의 collection에 저장된 자식을 가지고 있음을 선언합니다. 자식은 기본적으로 선택 사항입니다:

class Band
include Mongoid::Document
has_one :studio
end

has_one을 사용하는 경우, 자식 모델은 부모 모델과의 연관 관계를 선언하기 위해 belongs_to 를 사용해야 합니다.

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

자식의 존재를 요구하기 위해 유효성 검사를 사용하세요:

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

has_many 연관 관계를 사용하여 부모가 별도의 collection에 저장된 하나 이상의 자식을 가지고 있음을 선언합니다.

class Band
include Mongoid::Document
has_many :members
end

has_one과 마찬가지로 자식 모델은 belongs_to를 사용하여 부모 모델과의 연결을 선언해야 합니다.

class Member
include Mongoid::Document
belongs_to :band
end

또한 has_one과 마찬가지로 자식 문서들은 각각의 부모에 대한 참조가 포함되어 있습니다:

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

유효성 검사를 사용하여 적어도 한 명의 자녀가 존재하도록 요구하세요:

class Band
include Mongoid::Document
has_many :members
validates_presence_of :members
end

연관 관계에 any? 메소드를 사용하면 데이터베이스에서 모든 문서 집합을 검색하지 않고도 연관 관계에 문서가 포함되어 있는지 효율적으로 확인할 수 있습니다:

band = Band.first
band.members.any?

any? Enumerable#any? API, 차단 으로 필터링을 허용합니다.

band = Band.first
band.members.any? { |member| member.instrument == 'piano' }

또는 다형성 연결에 유용할 수 있는 클래스 이름으로 지정할 수도 있습니다.

class Drummer < Member
end
band = Band.first
band.members.any?(Drummer)

연관 관계가 이미 로드된 경우, any? 는 로드된 문서를 검사하고 데이터베이스를 쿼리하지 않습니다.

band = Band.first
# Queries the database
band.members.any?
band.members.to_a
# Does not query the database
band.members.any?

단순히 any? 을 호출하면 연관 관계가 로드되지 않습니다. (any? 은 첫 번째 일치하는 문서의 _id 필드만 조회하기 때문입니다).

연관 관계의 exists? 메소드는 연관 관계에 저장된 문서가 있는지 여부를 확인합니다. any? 방법과 다릅니다:

  • exists? 연관 관계가 이미 로드된 경우에도 항상 데이터베이스를 쿼리합니다.

  • exists? 저장되지 않은 문서는 고려하지 않습니다.

  • exists? any? 처럼 애플리케이션에서 필터링을 허용하지 않으며, 어떤 인자도 받지 않습니다.

다음 예시에서는 exists?any?의 차이점을 보여 줍니다.

band = Band.create!
# Member is not persisted.
band.members.build
band.members.any?
# => true
band.members.exists?
# => false
# Persist the member.
band.members.map(&:save!)
band.members.any?
# => true
band.members.exists?
# => true

자식 항목을 별도의 collection에 저장된 부모 항목과 연관 관계로 설정하려면 belongs_to 매크로를 사용하세요. (부모 항목과 연관 관계의 경우) 부모 항목의 _id 는 자식 항목에 저장됩니다.

기본값으로 모델에 belongs_to 연관 관계가 정의되어 있다면, 모델 인스턴스가 저장되기 위해서는 값을 제공해야 합니다. 부모를 명시하지 않고도 인스턴스를 저장할 수 있도록 하려면 optional: true` 설정 옵션을 사용합니다:

class Band
include Mongoid::Document
has_one :studio
end
class Studio
include Mongoid::Document
belongs_to :band, optional: true
end
studio = Studio.create!
# => #<Studio _id: 600118184896684987aa884f, band_id: nil>

전역적 설정하다 해당 상위 항목이 필요하지 않도록 belongs_to 연결의 기본값 동작을 변경하려면 belongs_to_required_by_default 구성 옵션false 로 설정합니다.

has_onehas_many 연관 관계는 해당하는 belongs_to 연관 관계가 자식 모델에 정의되어 있어야 하지만, belongs_to 역시 해당하는 has_one 또는 has_many 매크로 없이도 사용될 수 있습니다. 이 경우 부모는 자식에 접근할 수 없지만 자식은 부모에 접근할 수 있습니다.

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

부모 항목이 연관 관계를 정의하지 않은 경우, 명확성을 위해 inverse_of: nil 옵션을 추가할 수 있습니다.

class Band
include Mongoid::Document
end
class Studio
include Mongoid::Document
belongs_to :band, inverse_of: nil
end

has_and_belongs_to_many 매크로를 사용하여 다대다 연관 관계를 선언합니다:

class Band
include Mongoid::Document
has_and_belongs_to_many :tags
end
class Tag
include Mongoid::Document
has_and_belongs_to_many :bands
end

모델 인스턴스는 연관 관계가 있는 모델들의 ID 목록을 저장합니다(ID가 있는 경우):

band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011d554896684b8b910a2a, tag_ids: [BSON::ObjectId('60011d554896684b8b910a29')]>
band.tags
# => [#<Tag _id: 60011d554896684b8b910a29, band_ids: [BSON::ObjectId('60011d554896684b8b910a2a')]>]

inverse_of: nil 옵션을 사용하여 한 쪽 문서에서만 ID를 저장하는 has_and_belongs_to_many 연관 관계를 만들 수 있습니다.

class Band
include Mongoid::Document
has_and_belongs_to_many :tags, inverse_of: nil
end
class Tag
include Mongoid::Document
end
band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011dbc4896684bbbaa9255, tag_ids: [BSON::ObjectId('60011dbc4896684bbbaa9254')]>
band.tags
# => [#<Tag _id: 60011dbc4896684bbbaa9254, >]

한 쪽 방향의 has_and_belongs_to_many 연관 관계는 자연스럽게, 이것이 정의된 모델에서만 사용할 수 있습니다.

참고

A와 B라는 두 개의 모델이 있고 A가 B와 has_and_belongs_to_many 관계를 가질 때, A 유형의 문서에 B 유형의 문서를 HABTM 연관 관계에 추가하면, Mongoid는 A 유형의 문서에 대한 updated_at 필드를 업데이트하지 않지만, B 유형의 문서에 대한 updated_at 필드를 업데이트합니다.

대부분의 경우, 참조된 연관 관계(일반적으로 데이터나 조건 또는 여러 collection에 관련된 경우)를 효율적으로 쿼리하는 것은 집계 파이프라인을 사용하여 수행됩니다. 집계 파이프라인 쿼리 구성을 위한 Mongoid 도우미는 집계 파이프라인 섹션에 설명되어 있습니다.

간단한 쿼리의 경우, 집계 파이프라인의 사용하는 대신에 연관 관계를 직접 쿼리할 수 있습니다. 연관 관계를 직접 쿼리할 때, 모든 조건은 해당 연관 관계의 collection에서만 적용되어야 합니다(이는 일반적으로 해당 연관 관계와 그 안에 포함된 모든 연관 관계를 의미합니다).

예를 들어 다음과 같은 모델이 있습니다.

class Band
include Mongoid::Document
has_many :tours
has_many :awards
field :name, type: String
end
class Tour
include Mongoid::Document
belongs_to :band
field :year, type: Integer
end
class Award
include Mongoid::Document
belongs_to :band
field :name, type: String
end

2000년 이후 투어를 진행한 모든 밴드를 다음과 같이 조회할 수 있습니다:

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
bands = Band.find(band_ids)

Tour 에 대한 조건은 매우 복잡할 수 있지만, 모두 같은 Tour 문서 (또는 Tour 에 포함된 문서) 에 적용되어야 합니다.

2000년 이후 투어를 진행한 밴드에 관련된 상을 찾아보세요:

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
awards = Award.where(band_id: {'$in' => band_ids})

MongoDB의 문서 모델 덕분에 Mongoid는 다양한 유형의 문서를 동일한 컬렉션에 계층적으로 저장할 수 있는 임베디드 연결도 제공합니다. 임베디드 연결은 embeds_one, embeds_many, embedded_in 매크로와 재귀적 임베딩을 위한 recursively_embeds_one, recursively_embeds_many를 사용하여 정의됩니다.

부모 문서에 자식 항목이 포함되는 일대일 연관 관계는 Mongoid의 embeds_oneembedded_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

embeds_one 매크로를 사용하여 포함된 문서들은 부모 문서의 데이터베이스 collection 안에서 해시로 저장됩니다.

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

:store_as 옵션을 제공함으로써, Mongoid에게 이름과 다른 속성에 내장된 문서를 저장하도록 지시할 수도 있습니다.

class Band
include Mongoid::Document
embeds_one :label, store_as: "lab"
end

부모 문서에 포함된 자식들을 가지는 일대다 관계는 Mongoid의 embeds_manyembedded_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 매크로를 사용하여 포함된 문서는 부모 문서의 데이터베이스 collection 안에서 해시 배열로 저장됩니다.

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

:store_as 옵션을 제공함으로써, Mongoid에게 이름과 다른 속성에 내장된 문서를 저장하도록 지시할 수도 있습니다.

class Band
include Mongoid::Document
embeds_many :albums, store_as: "albs"
end

문서는 recursively_embeds_one 또는 recursively_embeds_many을 사용하여 재귀적으로 스스로를 임베드할 수 있으며, parent_child_ 메서드를 통해 부모 및 자식에 대한 접근자를 제공합니다.

class Tag
include Mongoid::Document
field :name, type: String
recursively_embeds_many
end
root = Tag.new(name: "programming")
child_one = root.child_tags.build
child_two = root.child_tags.build
root.child_tags # [ child_one, child_two ]
child_one.parent_tag # [ root ]
child_two.parent_tag # [ root ]
class Node
include Mongoid::Document
recursively_embeds_one
end
root = Node.new
child = Node.new
root.child_node = child
root.child_node # child
child.parent_node # root

참조와 포함에 대한 완전한 논의는 이 튜토리얼의 범위를 벗어나지만, 하나를 선택하는데 있어 고려해야 할 몇 가지 고급 고려 사항들이 있습니다.

연관 관계가 포함될 때, 부모와 자식 문서는 같은 collection에 저장됩니다. 이는 둘 다 사용되거나 필요할 때 효율적인 지속성과 검색을 허용합니다. 예를 들어, 웹사이트의 내비게이션 바가 문서 자체에 저장된 사용자의 속성을 보여주는 경우, 포함된 연관 관계를 사용하는 것이 종종 좋은 아이디어입니다.

포함된 연관 관계를 사용하면 MongoDB 도구들, 예를 들어 강력한 방식으로 이러한 문서들을 쿼리하는 집계 파이프라인 을 사용할 수 있습니다.

내장된 문서는 부모 최상위 문서의 일부로 저장되기 때문에, 내장된 문서를 단독으로 지속하거나 직접 조회하는 것은 불가능합니다. 그러나, MongoDB의 프로젝션 연산을 이용하면 내장된 문서들을 여전히 효율적으로 쿼리하고 조회할 수 있습니다:

class Band
include Mongoid::Document
field :started_on, type: Date
embeds_one :label
end
class Label
include Mongoid::Document
field :name, type: String
embedded_in :band
end
# Retrieve labels for bands started in the last year.
#
# Sends a find query like this:
# {"find"=>"bands",
# "filter"=>{"started_on"=>{"$gt"=>2018-07-01 00:00:00 UTC}},
# "projection"=>{"_id"=>1, "label"=>1}}
Band.where(started_on: {'$gt' => Time.now - 1.year}).only(:label).map(&:label).compact.uniq

참조된 연관 관계에 오래된 값을 설정하는 것은 때때로 데이터베이스에 nil 값이 지속되는 결과를 초래할 수 있습니다. 다음의 경우를 예로 들어 보겠습니다:

class Post
include Mongoid::Document
has_one :comment, inverse_of: :post
end
class Comment
include Mongoid::Document
belongs_to :post, inverse_of: :comment, optional: true
end
post.comment = comment1
post.reload

이 시점에서 post.commentcomment1 로 설정되지만, 재로드가 발생했기 때문에 post.commentcomment1 와 동일한 객체를 참조하지 않습니다. 즉, 한 객체를 업데이트해도 다른 객체가 암시적으로 업데이트되지 않습니다. 이는 다음 작업을 위해 중요합니다:

post.comment = comment2
post.reload

이제 post.commentcomment2로 설정되고, 이전 댓글의 post_idnil으로 설정됩니다. 그러나 post.comment 에 할당된 값은 comment1 와 동일한 객체를 참조하지 않았으므로, 이전 값 post.commentnil post_id 를 갖도록 업데이트 되었지만, comment1 에는 여전히 설정된 post_id 를 가지고 있습니다.

post.comment = comment1
post.reload

마지막으로, 이 할당은 comment1post_id 을 설정하려고 시도하는데, 이 시점에서 nil 이어야 하지만 이전 post_id로 설정되어 있습니다. 이 작업을 수행하는 동안 comment2에서 post_id 가 제거되고, 새로운 post_idcomment1에 설정됩니다. 하지만 post_id 값은 이미 comment1 로 설정되었으므로, 아무것도 지속되지 않고, 결과적으로 두 댓글 모두 nil post_id 를 가지게 됩니다. 이 점에서 post.comment 를 실행하면 nil이 반환됩니다.

최상위 문서를 쿼리할 때, 포함된 연관 관계의 문서에 점 표기법을 사용하여 조건을 명시할 수 있습니다. 예를 들어 다음 모델이 있다고 가정해 보겠습니다:

class Band
include Mongoid::Document
embeds_many :tours
embeds_many :awards
field :name, type: String
end
class Tour
include Mongoid::Document
embedded_in :band
field :year, type: Integer
end
class Award
include Mongoid::Document
embedded_in :band
field :name, type: String
end

투어 속성을 기준으로 밴드를 조회하려면 다음과 같이 점 표기법을 사용합니다:

# Get all bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000})

최상위 문서를 검색하지 않고 포함된 연관 관계의 문서만 조회하려면 pluck 프로젝션 메소드를 사용하세요.

# Get awards for bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards)

Mongoid 쿼리 메서드는 애플리케이션에 이미 로드된 문서의 포함된 연관 관계에 사용할 수 있습니다. 이 메커니즘을 "내장된 매칭"이라고 하며, 이는 전적으로 Mongoid 내에서 구현되며 쿼리는 서버로 전송되지 않습니다.

지원되는 연산자는 다음과 같습니다:

예를 들어 방금 제공된 모델 정의를 사용하여 로드된 밴드에서 투어를 쿼리할 수 있습니다.

band = Band.where(name: 'Astral Projection').first
tours = band.tours.where(year: {'$gte' => 2000})

Mongoid의 임베디드 매칭은 최신 MongoDB 서버 버전에서 네이티브 쿼리와 동일한 기능 및 의미 체계를 지원하는 것을 목표로 합니다. 다음과 같은 알려진 제한 사항에 유의하세요.

  • 텍스트 검색, 지리 공간적 쿼리 연산자, JavaScript 코드를 실행하는 연산자($where), 다른 서버 기능을 통해 구현되는 연산자(예: $expr$jsonSchema)에는 임베디드 매칭이 구현되지 않습니다.

  • Mongoid DSL은 Range 인수를 $gte$lte 조건이 있는 해시로 확장합니다. 경우에 따라 이는 가짜 쿼리를 생성합니다. 포함된 매처는 이러한 경우 InvalidQuery 예외를 발생시킵니다. 영향을 받는 것으로 알려진 연산자는 $elemMatch, $eq, $gt, $gte, $lt, $lte$ne 입니다.

  • $regex 를 사용하여 포함된 매칭을 수행할 때, 현재로서는 정규 표현식 객체를 패턴으로 지정하고 옵션을 제공하는 것이 불가능합니다. 것은 현재 불가능합니다.

  • MongoDB Server 4.0 및 이전 서버는 $type 인수의 유효성을 엄격하게 검사하지 않습니다(예: 0 과 같은 유효하지 않은 인수 허용). 이는 클라이언트 사이드에서 더 엄격하게 검증됩니다.

기본적으로 Mongoid는 각 내장된 문서에 _id 필드를 추가합니다. 이를 통해 내장된 문서를 쉽게 참조하고 작업할 수 있습니다.

저장 공간을 절약하기 위해 이러한 _id 필드는 생략될 수 있습니다. 이렇게 하려면 하위 문서에서 _id 필드 정의를 재정의하고 다음과 같이 기본값을 제거합니다.

class Order
include Mongoid::Document
embeds_many :line_items
end
class LineItem
include Mongoid::Document
embedded_in :order
field :_id, type: Object
end

현재 버전의 Mongoid에서는 필드 정의가 필요하지만 기본값을 지정하지 않으면 데이터베이스에 값이 저장되지 않습니다. Mongoid의 향후 버전에서는 이전에 정의된 필드를 제거할 수 있습니다.

참고

_id 필드를 제거하면 내장된 문서들이 쿼리, 업데이트, 삭제 중에 그들의 내용 속성 값에 의해 식별되어야 합니다.

Mongoid는 embeds_many 연관 관계에서 자식을 삭제하기 위해 세 가지 방법을 제공합니다: clear, destroy_all, delete_all.

clear 메서드는 $unset 연산자 를 사용하여 호스팅하다 문서 에서 전체 연결을 제거 합니다. 제거되는 문서에 대해 삭제 콜백을 실행 하지 않으며, 이와 관련하여 delete_all 처럼 작동합니다.

band = Band.find(...)
band.tours.clear

저장되지 않은 호스트 문서의 연관 관계에 clear 이 호출되는 경우에도, 호스트 문서의 _id에 따라 데이터베이스에서 연관 관계을 제거하려고 시도합니다:

band = Band.find(...)
band.tours << Tour.new(...)
unsaved_band = Band.new(id: band.id, tours: [Tour.new])
# Removes all tours from the persisted band due to _id match.
unsaved_band.tours.clear
band.tours
# => []

delete_all 메서드는 $pullAll 연산자 를 사용하여 연관 관계에 있는 문서를 제거합니다. 와 달리 cleardelete_all 다음과 같습니다.

  • 연결이 아직 로드되지 않은 경우 연결을 로드합니다.

  • 애플리케이션에 존재하는 문서만 제거합니다.

delete_all 제거되는 문서에 대해 삭제 콜백을 실행하지 않습니다.

예시:

band = Band.find(...)
band.tours.delete_all

delete_all 메서드는 삭제 콜백을 실행 하는 동안 $pullAll 연산자 를 사용하여 연관 관계에 있는 문서를 제거합니다. delete_all 와 마찬가지로, destroy_all 는 아직 로드되지 않은 경우 전체 연관 관계를 로드하고 애플리케이션 에 존재하는 문서만 제거합니다.

band = Band.find(...)
band.tours.destroy_all

임베디드 연결을 사용하면 사용자가 문서 대신 Hash를 연결에 할당할 수 있습니다. 할당 시 이 해시는 할당되는 연결 클래스의 문서에 강제로 적용됩니다. 다음 예시를 살펴보겠습니다.

class Band
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end
band = Band.create!
band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ]
p band.albums
# => [ #<Album _id: 633c71e93282a4357bb608e5, name: "Narrow Stairs">, #<Album _id: 633c71e93282a4357bb608e6, name: "Transatlanticism"> ]

이 방식은 embeds_one, embeds_many, embedded_in 연결에 대해 작동합니다. 참조된 연결에는 해시를 할당할 수 없다는 점에 유의하세요.

모든 연관 관계는 확장을 가질 수 있으며, 이는 연관 관계에 애플리케이션 특정 기능을 추가하는 방법을 제공합니다. 이들은 연관 관계 정의에 블록을 제공함으로써 정의됩니다.

class Person
include Mongoid::Document
embeds_many :addresses do
def find_by_country(country)
where(country: country).first
end
def chinese
_target.select { |address| address.country == "China" }
end
end
end
person.addresses.find_by_country("Mongolia") # returns address
person.addresses.chinese # returns [ address ]

연관 관계의 이름은 원하는 대로 지정할 수 있지만, 클래스가 Mongoid에 의해 이름에서 추론될 수 없고 반대편도 마찬가지인 경우, Mongoid가 연결 방법을 알 수 있도록 추가 옵션을 매크로에 제공하고 싶을 것입니다.

class Car
include Mongoid::Document
embeds_one :engine, class_name: "Motor", inverse_of: :machine
end
class Motor
include Mongoid::Document
embedded_in :machine, class_name: "Car", inverse_of: :engine
end

연관 관계를 찾을 때 사용되는 필드는 명시적으로 지정될 수 있습니다. 기본값은 "부모" 연관 관계에서는 id 를 사용하고 "자식" 연관 관계에서는 #{association_name}_id 를 사용합니다(예시: has_many/belongs_to):

class Company
include Mongoid::Document
has_many :emails
end
class Email
include Mongoid::Document
belongs_to :company
end
company = Company.find(id)
# looks up emails where emails.company_id == company.id
company.emails

"부모" 연관 관계에서 필드 이름을 변경하려면 다른 primary_key 를 지정하고 "자식" 연관 관계에서 필드 이름을 변경하려면 foreign_key 를 지정합니다:

class Company
include Mongoid::Document
field :c, type: String
has_many :emails, foreign_key: 'c_ref', primary_key: 'c'
end
class Email
include Mongoid::Document
# This definition of c_ref is automatically generated by Mongoid:
# field :c_ref, type: Object
# But the type can also be specified:
field :c_ref, type: String
belongs_to :company, foreign_key: 'c_ref', primary_key: 'c'
end
company = Company.find(id)
# looks up emails where emails.c_ref == company.c
company.emails

has_and_belongs_to_many 연관 관계에서는 데이터가 연관 관계의 양쪽에 저장되므로, 연관 관계를 정의할 때 4개의 필드가 구성될 수 있습니다:

  • :primary_key 원격 모델에서 원격 모델을 찾는 데 사용되는 값이 포함된 필드입니다.

  • :foreign_key 로컬 모델에서 :primary_key 값이 저장되는 필드입니다.

  • :inverse_primary_key 로컬 모델 문서를 찾기 위해 원격 모델이 사용하는 필드입니다.

  • :inverse_foreign_key :inverse_primary_key에 있는 값이 저장되는 원격 모델의 필드입니다.

이를 명확히 설명하는 예시는 다음과 같습니다:

class Company
include Mongoid::Document
field :c_id, type: Integer
field :e_ids, type: Array
has_and_belongs_to_many :employees,
primary_key: :e_id, foreign_key: :e_ids,
inverse_primary_key: :c_id, inverse_foreign_key: :c_ids
end
class Employee
include Mongoid::Document
field :e_id, type: Integer
field :c_ids, type: Array
has_and_belongs_to_many :companies,
primary_key: :c_id, foreign_key: :c_ids,
inverse_primary_key: :e_id, inverse_foreign_key: :e_ids
end
company = Company.create!(c_id: 123)
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: nil>
employee = Employee.create!(e_id: 456)
# => #<Employee _id: 5c565ee8026d7c461d8a9d4f, e_id: 456, c_ids: nil>
company.employees << employee
company
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: [456]>
employee
# => #<Employee _id: 5c5883ce026d7c4b9e244c0c, e_id: 456, c_ids: [123]>

#{association_name}_id 기본값 필드와 마찬가지로 Mongoid는 사용자 정의 외래 키 c_ref 를 위한 필드를 모델에 자동으로 추가합니다. 그러나 Mongoid는 해당 필드에 어떤 유형의 데이터가 허용되어야 하는지 알 수 없기 때문에, 필드는 객체 유형으로 생성됩니다 적절한 유형으로 필드를 명시적으로 정의하는 것이 좋습니다.

:scope 매개변수를 사용하여 연관 관계에 특정 범위를 설정할 수 있습니다. 범위는 연관 관계의 일부로 간주되는 객체들을 제한하는 추가 필터입니다 - 범위가 있는 연관 관계는 범위 조건을 만족하는 문서만 반환합니다. 범위는 다음 중 하나일 수 있습니다:

class Trainer
has_many :pets, scope: -> { where(species: 'dog') }
has_many :toys, scope: :rubber
end
class Pet
belongs_to :trainer
end
class Toy
scope :rubber, where(material: 'rubber')
belongs_to :trainer
end

참고

연관 관계의 범위를 만족하지 않는 문서를 해당 연관 관계에 추가하는 것이 가능합니다 이 경우, 해당 문서들은 메모리에 연관 관계되어 나타나고 데이터베이스에 저장되지만, 향후 연관 관계를 쿼리할 때는 나타나지 않을 것입니다. 예시:

trainer = Trainer.create!
dog = Pet.create!(trainer: trainer, species: 'dog')
cat = Pet.create!(trainer: trainer, species: 'cat')
trainer.pets #=> [dog, cat]
trainer.reload.pets #=> [dog]

참고

Mongoid의 범위 지정 연관 구문은 ActiveRecord와 다릅니다. Mongoid는 다른 연관 옵션과의 일관성을 위해 :scope 키워드 인수를 사용하는 반면, ActiveRecord에서는 범위가 위치 인수입니다.

기본값으로 Mongoid는 validates_associated를 통해 메모리에 로드된 모든 연관 관계의 자식들에 대해 유효성을 검사한다는 점에 유의해야 합니다. 이는 다음과 같은 연관 관계에 적용됩니다:

  • embeds_many

  • embeds_one

  • has_many

  • has_one

  • has_and_belongs_to_many

이러한 동작을 원하지 않는 경우, 연관 관계를 정의할 때 이 기능을 끌 수 있습니다.

class Person
include Mongoid::Document
embeds_many :addresses, validate: false
has_many :posts, validate: false
end

일대일 및 일대다 연관 관계는 다형성을 지원합니다. 즉, 단일 연관 관계가 서로 다른 클래스의 객체들을 포함할 수 있습니다. 예를 들어, 다음과 같이 부서와 팀에 매니저가 있는 조직을 모델링할 수 있습니다:

class Department
include Mongoid::Document
has_one :manager, as: :unit
end
class Team
include Mongoid::Document
has_one :manager, as: :unit
end
class Manager
include Mongoid::Document
belongs_to :unit, polymorphic: true
end
dept = Department.create!
team = Team.create!
alice = Manager.create!(unit: dept)
alice.unit == dept
# => true
dept.manager == alice
# => true

다른 예로 제품 및 번들에 대한 가격 이력을 추적한다고 가정해 보겠습니다. 이는 임베디드 일대다 다형성 연관을 통해 달성할 수 있습니다.

class Product
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :bundles
embeds_many :prices, as: :item
end
class Bundle
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :products
embeds_many :prices, as: :item
end
class Price
include Mongoid::Document
embedded_in :item, polymorphic: true
end
pants = Product.create!(name: 'Pants',
prices: [Price.new, Price.new])
costume = Bundle.create!(name: 'Costume', products: [pants],
prices: [Price.new, Price.new])

다형성 연관을 정의하려면 자식 연관 관계에 polymorphic: true 옵션을 지정하고 부모 연관 관계에 as: :association_name 옵션을 추가합니다.

Mongoid는 현재 자식에서 부모로의 한 방향으로만 다형성을 지원합니다. 예를 들어, 다형성을 사용하여 번들이 다른 번들이나 제품을 포함할 수 있다고 지정할 수는 없습니다:

class Bundle
include Mongoid::Document
# Does not work:
has_many :items, polymorphic: true
end

has_and_belongs_to_many 다형성을 지원하지 않는 연관 관계들입니다.

부모에 대한 지속성 작업을 호출할 때 내장된 문서 콜백을 실행하려면 연관 관계에 cascade 콜백 옵션을 제공해야 합니다.

class Band
include Mongoid::Document
embeds_many :albums, cascade_callbacks: true
embeds_one :label, cascade_callbacks: true
end
band.save # Fires all save callbacks on the band, albums, and label.

참조된 연관 관계에 dependent 옵션을 제공하여 연관 관계의 한 쪽이 삭제되거나 삭제를 시도할 때 Mongoid가 어떻게 처리해야 하는지 지시할 수 있습니다. 옵션은 다음과 같습니다:

  • :delete_all: 모델 콜백을 실행하지 않고 자식 문서를 삭제합니다.

  • :destroy: 자식 문서를 삭제하고 모든 모델 콜백을 실행합니다.

  • :nullify: 자식 문서의 외래 키 필드를 nil로 설정합니다. 자식은 일반적으로 부모를 통해서만 참조되는 경우 고아가 될 수 있습니다.

  • :restrict_with_exception: raise 자식 항목이 비어 있지 않으면 오류가 발생합니다.

  • :restrict_with_error: 자식이 비어 있지 않으면 연산을 취소하고 false를 반환합니다.

:dependent 옵션이 제공되지 않으면, 부모 문서를 삭제해도 자식 문서는 수정되지 않습니다(즉, 자식 문서는 여전히 삭제된 부모 문서를 외래 키 필드를 통해 참조합니다). 자식은 일반적으로 부모를 통해서만 참조되는 경우 고아가 될 수 있습니다.

class Band
include Mongoid::Document
has_many :albums, dependent: :delete_all
belongs_to :label, dependent: :nullify
end
class Album
include Mongoid::Document
belongs_to :band
end
class Label
include Mongoid::Document
has_many :bands, dependent: :restrict_with_exception
end
label = Label.first
label.bands.push(Band.first)
label.delete # Raises an error since bands is not empty.
Band.first.destroy # Will delete all associated albums.

Mongoid와 ActiveRecord의 한 가지 주요 차이점은, 성능상의 이유로 Mongoid는 부모가 저장될 때 참조된(즉, 포함되지 않은) 연관 관계의 문서를 자동으로 저장하지 않는다는 것입니다.

자동 저장을 사용하지 않는 경우, 연관 관계를 통해 존재하지 않는 문서에 매달린 참조를 만들 수 있습니다:

class Band
include Mongoid::Document
has_many :albums
end
class Album
include Mongoid::Document
belongs_to :band
end
band = Band.new
album = Album.create!(band: band)
# The band is not persisted at this point.
album.reload
album.band_id
# => BSON::ObjectId('6257699753aefe153121a3d5')
# Band does not exist.
album.band
# => nil

부모가 저장될 때 참조된 연관 관계가 자동으로 저장되도록 하려면, 연관 관계에 :autosave 옵션을 추가하세요.

class Band
include Mongoid::Document
has_many :albums
end
class Album
include Mongoid::Document
belongs_to :band, autosave: true
end
band = Band.new
album = Album.create!(band: band)
# The band is persisted at this point.
album.reload
album.band_id
# => BSON::ObjectId('62576b4b53aefe178b65b8e3')
album.band
# => #<Band _id: 62576b4b53aefe178b65b8e3, >

accepts_nested_attributes_for 를 사용하면 자동 저장 기능이 연관 관계에 자동으로 추가되므로, 양식 제출을 처리할 때 어떤 연관 관계가 수정되었는지 애플리케이션이 추적할 필요가 없습니다.

포함된 연관 관계는 부모 문서의 일부로 저장되므로 항상 자동 저장됩니다.

일부 연관 관계 작업은 자동 저장이 활성화되어 있든 아니든 관계없이 작업의 일부로 부모와 자식 문서를 항상 저장합니다. 이러한 작업의 비완전한 목록은 다음과 같습니다:

  • 연관 관계에 대한 할당:

    # Saves the band and the album.
    band.albums = [Album.new]
  • push, <<:

    band.albums << Album.new
    band.albums.push(Album.new)

모든 연관 관계에는 연관 관계가 비어 있는지 확인하기 위한 name?has_name? 과 같은 존재 조건문이 있습니다.

class Band
include Mongoid::Document
embeds_one :label
embeds_many :albums
end
band.label?
band.has_label?
band.albums?
band.has_albums?

일대일 연관 관계 (embeds_one, has_one) 는 자동 빌드 옵션이 있어, 연관 관계에 액세스하고 해당 문서가 nil 일 때 새 문서를 인스턴스화하도록 Mongoid에 지시합니다.

class Band
include Mongoid::Document
embeds_one :label, autobuild: true
has_one :producer, autobuild: true
end
band = Band.new
band.label # Returns a new empty label.
band.producer # Returns a new empty producer.

모든 belongs_to 연관 관계은 선택적인 :touch 옵션을 가질 수 있으며, 이는 자식 문서가 업데이트될 때마다 부모 문서가 터치되도록 합니다.

class Band
include Mongoid::Document
field :name
belongs_to :label, touch: true
end
band = Band.first
band.name = "The Rolling Stones"
band.save! # Calls touch on the parent label.
band.touch # Calls touch on the parent label.

:touch 또한 updated_at 외에 부모 연관 관계에서 터치될 필드를 지정하는 문자열 또는 심볼 인자를 받을 수도 있습니다:

class Label
include Mongoid::Document
include Mongoid::Timestamps
field :bands_updated_at, type: Time
has_many :bands
end
class Band
include Mongoid::Document
belongs_to :label, touch: :bands_updated_at
end
label = Label.create!
band = Band.create!(label: label)
band.touch # Updates updated_at and bands_updated_at on the label.

내장된 문서가 터치될 때, 그 부모들은 작곡 루트를 통해 재귀적으로 터치됩니다(내장된 문서가 저장될 때 모든 부모들이 반드시 저장되기 때문임). 따라서 :touch 속성은 embedded_in 연관 관계에서는 필요하지 않습니다.

Mongoid는 현재 embedded_in 연관 관계에서 건드릴 추가 필드 를 지정하는 것을 지원 하지 않습니다. .

:touch 내장된 문서 의 터치에 따라 항상 구성 계층이 업데이트되므로 embedded_in 연관 관계에서 false 로 설정하다 해서는 안 됩니다. 현재는 시행되고 있지 않지만 향후 시행될 예정입니다..

ActiveRecord와 마찬가지로 :counter_cache 옵션을 연관 관계에 사용하면 속해 있는 객체 수를 더 효율적으로 찾을 수 있습니다. 또한 ActiveRecord와 유사하게, 연관된 모델에 추가 속성이 있을 것이라는 점을 고려해야 합니다. 이는 Mongoid에서 연관된 모델에 Mongoid::Attributes::Dynamic 을 포함해야 함을 의미합니다. 예시:

class Order
include Mongoid::Document
belongs_to :customer, counter_cache: true
end
class Customer
include Mongoid::Document
include Mongoid::Attributes::Dynamic
has_many :orders
end

연관 관계는 대상 객체에 대한 투명한 프록시를 사용합니다. 이는 일부 상황에서 놀라운 동작을 일으킬 수 있습니다.

연관 관계 대상의 메소드에 액세스할 때 메소드 가시성이 손실될 수 있으며, 이는 연관 관계에 따라 다릅니다:

class Order
include Mongoid::Document
belongs_to :customer
private
def internal_status
'new'
end
end
class Customer
include Mongoid::Document
has_many :orders
private
def internal_id
42
end
end
order = Order.new
customer = Customer.create!(orders: [order])
# has_many does not permit calling private methods on the target
customer.orders.first.internal_status
# NoMethodError (private method `internal_status' called for #<Order:0x000055af2ec46c50>)
# belongs_to permits calling private methods on the target
order.customer.internal_id
# => 42

Mongoid의 모든 연관 관계에는 해당 연관 관계에 대한 정보를 담고 있는 메타데이터가 포함되어 있으며, 이는 Mongoid를 확장하려는 제3자 개발자에게 유용한 도구입니다.

연관 관계의 메타데이터에는 몇 가지 방법으로 액세스할 수 있습니다.

# Get the metadata for a named association from the class or document.
Model.reflect_on_association(:association_name)
model.reflect_on_association(:association_name)
# Get the metadata with a specific association itself on a specific
# document.
model.associations[:association_name]

모든 연관 관계에는 프록시된 문서 또는 문서들인 _target, 연관 관계가 속한 문서인 _base , 연관 관계에 대한 정보를 제공하는 _association 가 포함되어 있습니다.

class Person
include Mongoid::Document
embeds_many :addresses
end
person.addresses = [ address ]
person.addresses._target # returns [ address ]
person.addresses._base # returns person
person.addresses._association # returns the association metadata

연관 관계 객체 자체는 알고 있는 것보다 더 많은 정보를 포함하고 있으며, Mongoid 확장 기능을 개발하는 개발자들에게 유용합니다.

메서드
설명
Association#as
부모의 이름을 다형성 자식에게 반환합니다.
Association#as?
as 옵션이 존재하는지 여부를 반환합니다.
Association#autobuilding?
연관이 자동 빌드되는지 여부를 반환합니다.
Association#autosaving?
연관 관계에 자동 저장되는지 여부를 반환합니다.
Association#cascading_callbacks?
연관 관계에 부모로부터 계단식으로 내려오는 콜백이 있는지 여부를 반환합니다.
Association#class_name
프록시된 문서의 클래스 이름을 반환합니다.
Association#cyclic?
연관 관계가 순환 연결인지 여부를 반환합니다.
Association#dependent
연관 관계의 종속 옵션을 반환합니다.
Association#destructive?
연관 관계에 종속 삭제 또는 삭제가 있는 경우 true를 반환합니다.
Association#embedded?
연관이 다른 문서에 포함되어 있는지 여부를 반환합니다.
Association#forced_nil_inverse?
연결에 nil 역이 정의되어 있는지 여부를 반환합니다.
Association#foreign_key
외래 키 필드의 이름을 반환합니다.
Association#foreign_key_check
외래 키 필드 더티 검사 메소드의 이름을 반환합니다.
Association#foreign_key_setter
외래 키 필드 설정자의 이름을 반환합니다.
Association#indexed?
외래 키가 자동 인덱싱되는지 여부를 반환합니다.
Association#inverses
모든 역방향 연관 관계의 이름을 반환합니다.
Association#inverse
단일 역방향 연관 관계의 이름을 반환합니다.
Association#inverse_class_name
역방향에서 연관 관계의 클래스 이름을 반환합니다.
Association#inverse_foreign_key
역방향에서 외래 키 필드의 이름을 반환합니다.
Association#inverse_klass
반대쪽 연관 클래스를 반환합니다.
Association#inverse_association
역방향에서 연관 관계의 메타데이터를 반환합니다.
Association#inverse_of
명시적으로 정의된 역방향 연관 관계의 이름을 반환합니다.
Association#inverse_setter
역방향을 설정하는 데 사용된 메소드의 이름을 반환합니다.
Association#inverse_type
역방향에서 다형성 유형 필드의 이름을 반환합니다.
Association#inverse_type_setter
역방향에서 다형성 유형 필드 설정자의 이름을 반환합니다.
Association#key
속성 해시에서 연관 관계를 가져오는 데 사용할 필드의 이름을 반환합니다.
Association#klass
연관 관계에서 프록시된 문서들의 클래스를 반환합니다.
Association#name
연관 관계 이름을 반환합니다.
Association#options
ActiveRecord와의 API 호환성을 위해 자체를 반환합니다.
Association#order
연관 관계에 대한 사용자 정의 정렬 옵션을 반환합니다.
Association#polymorphic?
연관 관계가 다형성인지 여부를 반환합니다.
Association#setter
연관 관계를 설정할 필드의 이름을 반환합니다.
Association#store_as
포함된 연관 관계를 저장할 속성의 이름을 반환합니다.
Association#touchable?
연관 관계에 터치 옵션이 있는지 여부를 반환합니다.
Association#type
다형성 유형을 가져오는 필드의 이름을 반환합니다.
Association#type_setter
다형성 유형을 설정하는 필드의 이름을 반환합니다.
Association#validate?
연관 관계에 연결된 유효성 검사가 있는지 여부를 반환합니다.

돌아가기

상속