레일에서 동일한 모델로 다 대다 관계를 만들려면 어떻게해야합니까?
예를 들어 각 게시물은 여러 게시물에 연결됩니다.
답변
여러 종류의 다 대다 관계가 있습니다. 다음과 같은 질문을해야합니다.
- 협회와 함께 추가 정보를 저장하고 싶습니까? (조인 테이블의 추가 필드)
- 연관이 암시 적으로 양방향이어야합니까? (포스트 A가 포스트 B에 연결된 경우 포스트 B도 포스트 A에 연결됩니다.)
네 가지 가능성이 있습니다. 아래에서 이것들을 살펴 보겠습니다.
참고 : 주제에 대한 Rails 문서 . “다 대다”라는 섹션이 있으며 물론 클래스 메서드 자체에 대한 문서도 있습니다.
가장 간단한 시나리오, 단방향, 추가 필드 없음
이것은 코드에서 가장 간결합니다.
게시물에 대한 다음 기본 스키마부터 시작하겠습니다.
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
다 대다 관계의 경우 조인 테이블이 필요합니다. 이에 대한 스키마는 다음과 같습니다.
create_table "post_connections", :force => true, :id => false do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
end
기본적으로 Rails는이 테이블을 우리가 조인하는 두 테이블 이름의 조합이라고 부릅니다. 그러나 그것은 posts_posts
이 상황에서 와 같이 밝혀 질 것이기 때문에 post_connections
대신 받아들이기로 결정했습니다 .
여기서 매우 중요한 것은 :id => false
기본 id
열 을 생략하는 것 입니다. Rails는 조인 테이블을 제외한 모든 곳에서 해당 열을 원합니다 has_and_belongs_to_many
. 큰 소리로 불평 할 것입니다.
마지막으로 post_id
충돌을 방지하기 위해 열 이름도 표준이 아닙니다 (아님 ).
이제 모델에서 이러한 두 가지 비표준 사항에 대해 Rails에 알려 주기만하면됩니다. 다음과 같이 표시됩니다.
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
end
그리고 그것은 간단하게 작동합니다! 다음은 irb 세션 실행 예입니다 script/console
.
>> a = Post.create :name => 'First post!'
=> #<Post id: 1, name: "First post!">
>> b = Post.create :name => 'Second post?'
=> #<Post id: 2, name: "Second post?">
>> c = Post.create :name => 'Definitely the third post.'
=> #<Post id: 3, name: "Definitely the third post.">
>> a.posts = [b, c]
=> [#<Post id: 2, name: "Second post?">, #<Post id: 3, name: "Definitely the third post.">]
>> b.posts
=> []
>> b.posts = [a]
=> [#<Post id: 1, name: "First post!">]
posts
연결에 할당하면 post_connections
적절하게 테이블에 레코드가 생성됨을 알 수 있습니다 .
참고할 사항 :
- 위의 irb 세션에서 연관이 단방향임을 알 수 있습니다. 이후
a.posts = [b, c]
의 출력b.posts
에는 첫 번째 게시물이 포함되지 않기 때문 입니다. - 눈치 채 셨을 수도있는 또 다른 점은 모델이 없다는 것입니다
PostConnection
. 일반적으로has_and_belongs_to_many
연결에 모델을 사용하지 않습니다 . 따라서 추가 필드에 액세스 할 수 없습니다.
추가 필드가있는 단방향
바로 지금 … 장어가 얼마나 맛있는 지에 대한 게시물을 오늘 귀하의 사이트에 게시 한 일반 사용자가 있습니다. 이 완전히 낯선 사람이 귀하의 사이트를 방문하고 가입하고 일반 사용자의 무능함을 꾸짖는 게시물을 작성합니다. 결국 장어는 멸종 위기에 처한 종입니다!
그래서 당신은 당신의 데이터베이스에서 포스트 B가 포스트 A에 대한 꾸짖는 소리라는 것을 분명히하고 싶을 것입니다. 그렇게하기 위해, 당신 category
은 연관에 필드를 추가하고 싶습니다 .
우리에게 필요한 것은 더 이상 없다 has_and_belongs_to_many
, 그러나의 조합 has_many
, belongs_to
, has_many ..., :through => ...
과에 대한 별도의 모델은 테이블을 가입 할 수 있습니다. 이 추가 모델은 협회 자체에 추가 정보를 추가 할 수있는 권한을 제공합니다.
다음은 위와 매우 유사한 또 다른 스키마입니다.
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
create_table "post_connections", :force => true do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
t.string "category"
end
공지 사항이 상황에서, 어떻게, post_connections
수행 이 id
열을. ( 매개 변수 가 없습니다 :id => false
.) 테이블에 액세스하기위한 일반 ActiveRecord 모델이 있기 때문에 이것은 필수입니다.
PostConnection
아주 간단하기 때문에 모델 부터 시작하겠습니다 .
class PostConnection < ActiveRecord::Base
belongs_to :post_a, :class_name => :Post
belongs_to :post_b, :class_name => :Post
end
여기에 무슨있는 유일한 방법은 :class_name
레일에서 추론 할 수 없기 때문에, 필요하다, post_a
또는 post_b
여기에 포스트 나왔습니다 거래 우리 것을. 우리는 그것을 명시 적으로 말해야합니다.
이제 Post
모델 :
class Post < ActiveRecord::Base
has_many :post_connections, :foreign_key => :post_a_id
has_many :posts, :through => :post_connections, :source => :post_b
end
첫 번째 has_many
연결을 통해 모델 post_connections
에 posts.id = post_connections.post_a_id
.
두 번째 협회, 우리는 우리가 다른 게시물에 도달 할 수있는 레일을 말하고있다, 이것에 연결된 것들, 우리가 처음으로 협회를 통해 post_connections
,에 의해 다음 post_b
의 협회 PostConnection
.
이 단지 한 가지 더 없는, 그리고 그것은 우리가 그 레일을 말할 필요가있다 PostConnection
이 속한 게시물에 따라 달라집니다. 하나 또는 둘 모두의 경우 post_a_id
와 post_b_id
있었다 NULL
, 다음 연결이 많은 우리에게, 그것은 것하지 않을 것이라고? Post
모델 에서 수행하는 방법은 다음과 같습니다 .
class Post < ActiveRecord::Base
has_many(:post_connections, :foreign_key => :post_a_id, :dependent => :destroy)
has_many(:reverse_post_connections, :class_name => :PostConnection,
:foreign_key => :post_b_id, :dependent => :destroy)
has_many :posts, :through => :post_connections, :source => :post_b
end
구문의 약간의 변경 외에도 두 가지 실제 사항이 다릅니다.
- 는
has_many :post_connections
추가가:dependent
매개 변수를. 값으로:destroy
Rails에이 게시물이 사라지면 계속 진행하여 이러한 객체를 파괴 할 수 있음을 알려줍니다. 여기서 사용할 수있는 대체 값:delete_all
은 더 빠르지 만이를 사용하는 경우 파괴 후크를 호출하지 않습니다. - 우리는 .NET을 통해 우리를 연결
has_many
한 역 연결에 대한 연결도 추가했습니다post_b_id
. 이런 식으로 Rails는 그것들도 깔끔하게 파괴 할 수 있습니다.:class_name
모델의 클래스 이름을 더 이상에서 유추 할 수 없기 때문에 여기서 지정해야 합니다:reverse_post_connections
.
이를 통해 script/console
다음을 통해 또 다른 irb 세션을 제공합니다 .
>> a = Post.create :name => 'Eels are delicious!'
=> #<Post id: 16, name: "Eels are delicious!">
>> b = Post.create :name => 'You insensitive cloth!'
=> #<Post id: 17, name: "You insensitive cloth!">
>> b.posts = [a]
=> [#<Post id: 16, name: "Eels are delicious!">]
>> b.post_connections
=> [#<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>]
>> connection = b.post_connections[0]
=> #<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>
>> connection.category = "scolding"
=> "scolding"
>> connection.save!
=> true
연관을 생성 한 다음 카테고리를 별도로 설정하는 대신 PostConnection을 생성하여이를 수행 할 수도 있습니다.
>> b.posts = []
=> []
>> PostConnection.create(
?> :post_a => b, :post_b => a,
?> :category => "scolding"
>> )
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> b.posts(true) # 'true' means force a reload
=> [#<Post id: 16, name: "Eels are delicious!">]
그리고 우리는 또한 조작 할 수 post_connections
및 reverse_post_connections
협회; posts
협회에 깔끔하게 반영됩니다 .
>> a.reverse_post_connections
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> a.reverse_post_connections = []
=> []
>> b.posts(true) # 'true' means force a reload
=> []
양방향 루프 연결
일반 has_and_belongs_to_many
연관에서 연관은 관련된 두 모델 에서 정의됩니다 . 그리고 연관성은 양방향입니다.
하지만이 경우에는 Post 모델이 하나뿐입니다. 그리고 연관은 한 번만 지정됩니다. 이것이 바로이 특정 경우에 연관이 단방향 인 이유입니다.
has_many
조인 테이블의 모델 과 함께 대체 방법에 대해서도 마찬가지입니다 .
이것은 단순히 irb에서 연결에 액세스하고 Rails가 로그 파일에서 생성하는 SQL을 볼 때 가장 잘 나타납니다. 다음과 같은 것을 찾을 수 있습니다.
SELECT * FROM "posts"
INNER JOIN "post_connections" ON "posts".id = "post_connections".post_b_id
WHERE ("post_connections".post_a_id = 1 )
연결을 양방향으로 만들려면 Rails OR
를 위의 조건 post_a_id
과 post_b_id
반대로 만드는 방법을 찾아야하므로 양방향으로 볼 수 있습니다.
불행히도 내가 아는 유일한 방법은 다소 해키입니다. 당신은 수동으로 SQL가 옵션을 사용하여 지정해야합니다 has_and_belongs_to_many
같은 :finder_sql
, :delete_sql
그것은 꽤 아니다 등. (여기에서도 제안을받을 수 있습니다. 누구세요?)
답변
Shteef가 제기 한 질문에 답하려면 :
양방향 루프 연결
사용자 간의 팔로어-팔로어 관계 는 양방향 루프 연관의 좋은 예입니다. 사용자는 많은 수 있습니다 :
- 추종자로서의 추종자
- 추종자로서의 추종자.
user.rb 의 코드는 다음과 같습니다 .
class User < ActiveRecord::Base
# follower_follows "names" the Follow join table for accessing through the follower association
has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow"
# source: :follower matches with the belong_to :follower identification in the Follow model
has_many :followers, through: :follower_follows, source: :follower
# followee_follows "names" the Follow join table for accessing through the followee association
has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"
# source: :followee matches with the belong_to :followee identification in the Follow model
has_many :followees, through: :followee_follows, source: :followee
end
follow.rb 의 코드는 다음과 같습니다 .
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: "follower_id", class_name: "User"
belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end
주목해야 할 가장 중요한 것은 아마도 용어 :follower_follows
와 :followee_follows
user.rb 일 것입니다. 예를 들어 러닝 오브 더 밀 (비 루프) 연결을 사용하려면 팀에 : players
~ :contracts
. 이는 대한 다르지 않다 플레이어 많은있을 수 있습니다 :teams
통해이 :contracts
아니라 (예의 과정을 통해 플레이어 의 경력). 그러나이 경우 명명 된 모델이 하나만 존재하는 경우 (예 : User ) through : 관계의 이름을 동일하게 지정하면 (예 : through: :follow
위의 게시물 예제에서 수행 한 것과 같이 through: :post_connections
) ( 또는 액세스 포인트) 조인 테이블. :follower_follows
과:followee_follows
이러한 이름 충돌을 피하기 위해 만들어졌습니다. 이제, 사용자가 많은 수 :followers
를 통해 :follower_follows
많은 :followees
통해를 :followee_follows
.
사용자 의 : followees ( @user.followees
데이터베이스 호출 시) 를 결정하기 위해 Rails는 이제 class_name : “팔로우”의 각 인스턴스를 살펴볼 수 있습니다. 여기서 해당 사용자는 해당 사용자 의 : followee_follows를 foreign_key: :follower_id
통해 (예 🙂 팔로어입니다. 사용자 의 : followers ( 데이터베이스 호출 시) 를 결정하기 위해 Rails는 이제 class_name : “팔로우”의 각 인스턴스를 살펴볼 수 있습니다. 여기서 해당 사용자 는 다음을 통해 해당 사용자 의 : follower_follows입니다.@user.followers
foreign_key: :followee_id
답변
Rails에서 친구 관계를 만드는 방법을 알아보기 위해 여기에 온 사람이 있다면, 제가 마침내 사용하기로 결정한 ‘커뮤니티 엔진’이 한 일을 복사 해 보려고합니다.
다음을 참조 할 수 있습니다.
https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb
과
https://github.com/bborn/communityengine/blob/master/app/models/user.rb
자세한 내용은.
TL; DR
# user.rb
has_many :friendships, :foreign_key => "user_id", :dependent => :destroy
has_many :occurances_as_friend, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
..
# friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
답변
@ Stéphan Kochen에서 영감을 얻어 양방향 연결에 사용할 수 있습니다.
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
has_and_belongs_to_many(:reversed_posts,
:class_name => Post,
:join_table => "post_connections",
:foreign_key => "post_b_id",
:association_foreign_key => "post_a_id")
end
그러면 post.posts
&& post.reversed_posts
둘 다 작동해야합니다. 적어도 나를 위해 일했습니다.
답변
양방향의 경우 belongs_to_and_has_many
이미 게시 된 훌륭한 답변을 참조한 다음 다른 이름으로 다른 연결을 생성하면 외래 키가 반전되고 class_name
올바른 모델을 다시 가리 키도록 설정 했는지 확인합니다 . 건배.
답변
다음과 같은 우수한 답변을 얻는 데 문제가있는 사람이있는 경우 :
(개체는 #inspect를 지원하지 않습니다)
=>
또는
NoMethodError : : Mission : Symbol에 대한 정의되지 않은 메서드 ‘split’
그런 다음 해결책은 물론 클래스 이름 을 대체 :PostConnection
하여 로 바꾸는 것 입니다 "PostConnection"
.