연관 관계
이 페이지의 내용
참조된 연관 관계
Mongoid는 ActiveRecord 사용자에게 익숙한 has_one
, has_many
, belongs_to
및 has_and_belongs_to_many
연관 관계를 지원합니다.
Has One
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
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?
연관 관계에 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?
연관 관계의 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
Belongs To
자식 항목을 별도의 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_one
및 has_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
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
를 사용하여 정의됩니다.
Embeds One
부모 문서에 자식 항목이 포함되는 일대일 연관 관계는 Mongoid의 embeds_one
및 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
스토리지
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
Embeds Many
부모 문서에 포함된 자식들을 가지는 일대다 관계는 Mongoid의 embeds_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
매크로를 사용하여 포함된 문서는 부모 문서의 데이터베이스 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.comment
는 comment1
로 설정되지만, 재로드가 발생했기 때문에 post.comment
는 comment1
와 동일한 객체를 참조하지 않습니다. 즉, 한 객체를 업데이트해도 다른 객체가 암시적으로 업데이트되지 않습니다. 이는 다음 작업을 위해 중요합니다:
post.comment = comment2 post.reload
이제 post.comment
이 comment2
로 설정되고, 이전 댓글의 post_id
가 nil
으로 설정됩니다. 그러나 post.comment
에 할당된 값은 comment1
와 동일한 객체를 참조하지 않았으므로, 이전 값 post.comment
는 nil
post_id
를 갖도록 업데이트 되었지만, comment1
에는 여전히 설정된 post_id
를 가지고 있습니다.
post.comment = comment1 post.reload
마지막으로, 이 할당은 comment1
에 post_id
을 설정하려고 시도하는데, 이 시점에서 nil
이어야 하지만 이전 post_id
로 설정되어 있습니다. 이 작업을 수행하는 동안 comment2
에서 post_id
가 제거되고, 새로운 post_id
가 comment1
에 설정됩니다. 하지만 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 내에서 구현되며 쿼리는 서버로 전송되지 않습니다.
지원되는 연산자는 다음과 같습니다:
$regex (
$options
필드 는$regex
인수가 string 인 경우에만 지원됨)
예를 들어 방금 제공된 모델 정의를 사용하여 로드된 밴드에서 투어를 쿼리할 수 있습니다.
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 과 같은 유효하지 않은 인수 허용). 이는 클라이언트 사이드에서 더 엄격하게 검증됩니다.
필드 _id
생략
기본적으로 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
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
delete_all
메서드는 $pullAll 연산자 를 사용하여 연관 관계에 있는 문서를 제거합니다. 와 달리 clear
는delete_all
다음과 같습니다.
연결이 아직 로드되지 않은 경우 연결을 로드합니다.
애플리케이션에 존재하는 문서만 제거합니다.
delete_all
제거되는 문서에 대해 삭제 콜백을 실행하지 않습니다.
예시:
band = Band.find(...) band.tours.delete_all
destroy_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
매개변수를 사용하여 연관 관계에 특정 범위를 설정할 수 있습니다. 범위는 연관 관계의 일부로 간주되는 객체들을 제한하는 추가 필터입니다 - 범위가 있는 연관 관계는 범위 조건을 만족하는 문서만 반환합니다. 범위는 다음 중 하나일 수 있습니다:
아리티가 0인
Proc
또는연관된 모델에서 명명된 범위를 참조하는
Symbol
.
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? | 연관 관계에 연결된 유효성 검사가 있는지 여부를 반환합니다. |