[ruby-on-rails] 레일에서 동일한 모델과 다 대다 관계?

레일에서 동일한 모델로 다 대다 관계를 만들려면 어떻게해야합니까?

예를 들어 각 게시물은 여러 게시물에 연결됩니다.



답변

여러 종류의 다 대다 관계가 있습니다. 다음과 같은 질문을해야합니다.

  • 협회와 함께 추가 정보를 저장하고 싶습니까? (조인 테이블의 추가 필드)
  • 연관이 암시 적으로 양방향이어야합니까? (포스트 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_connectionsposts.id = post_connections.post_a_id.

두 번째 협회, 우리는 우리가 다른 게시물에 도달 할 수있는 레일을 말하고있다, 이것에 연결된 것들, 우리가 처음으로 협회를 통해 post_connections,에 의해 다음 post_b의 협회 PostConnection.

이 단지 한 가지 더 없는, 그리고 그것은 우리가 그 레일을 말할 필요가있다 PostConnection이 속한 게시물에 따라 달라집니다. 하나 또는 둘 모두의 경우 post_a_idpost_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매개 변수를. 값으로 :destroyRails에이 게시물이 사라지면 계속 진행하여 이러한 객체를 파괴 할 수 있음을 알려줍니다. 여기서 사용할 수있는 대체 값 :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_connectionsreverse_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_idpost_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_followsuser.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.followersforeign_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".


답변