回答:
多対多の関係にはいくつかの種類があります。次の質問を自問する必要があります。
これには、4つの異なる可能性があります。以下でこれらについて説明します。
参考までに、この件に関する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はこのテーブルを、結合する2つのテーブルの名前の組み合わせと呼びます。しかし、それは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必要に応じてテーブルにレコードが作成されます。
注意すべき点:
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。これは、Railsがここから投稿を処理していること、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ます。
2番目の関連付けでは、最初の関連付けpost_connections、続いてのpost_b関連付けを介して、他の投稿(この投稿に接続されている投稿)に到達できることをRailsに伝えていますPostConnection。
不足していることがもう1つありPostConnectionます。それは、a が属する投稿に依存していることをRailsに通知する必要があるということです。一方または両方の場合に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
構文のわずかな変更に加えて、ここでは実際の2つの点が異なります。
has_many :post_connections余分ている:dependentパラメータを。値:destroyを使用して、この投稿が消えると、先に進み、これらのオブジェクトを破棄できることをRailsに伝えます。ここで使用できる代替値はです:delete_all。これはより高速ですが、使用している場合は破壊フックを呼び出しません。has_many関連付けも追加しました。これは、を介してリンクされたものです。このようにして、Railsはそれらもきちんと破壊できます。モデルのクラス名はから推測できないため、ここで指定する必要があることに注意してください。post_b_id:class_name:reverse_post_connectionsこれで、次のirbセッションが始まりますscript/console。
>> 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モデルは1つしかありません。また、関連付けは一度だけ指定されます。これこそが、この特定のケースで、関連付けが単方向である理由です。
同じことが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 )
関連が双方向にするために、我々は、makeの方法を見つける必要があるだろうレールORと、上記の条件をpost_a_idし、post_b_id逆に、それは両方の方向になります。
残念ながら、私が知っているこれを行う唯一の方法はかなりハックです。手動でのオプションを使用して、SQLを指定する必要がありますhas_and_belongs_to_manyような:finder_sql、:delete_sqlそれはかなりではないなど、。(私もここで提案を受け入れます。誰か?)
シュティーフが提起した質問に答えるには:
ユーザー間のフォロワーとフォロワーの関係は、双方向ループ関連の良い例です。ユーザーは多くを持つことができます。
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
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。これは、(そのようなPlayerのキャリアの過程で)多くのスルーを持っている可能性のあるPlayerでも同じです。ただし、この場合、名前付きモデルが1つしか存在しない場合(つまり、User)、through:関係に同じ名前を付けると(たとえば、上記の投稿の例で行ったように)、(またはアクセスポイント)への結合テーブル。そして:teams:contractsthrough: :followthrough: :post_connections:follower_follows:followee_followsこのような名前の衝突を回避するために作成されました。今、ユーザーは多くを持つことができ:followersて:follower_follows、多く:followeesて:followee_follows。
判断するにはユーザーさん:フォロイー(時に@user.followeesデータベースへのコール)、Railsは今CLASS_NAMEの各インスタンスを見てもよい。そのようなユーザーがフォロワー(すなわちある『フォロー』foreign_key: :follower_idなど:を通じて)ユーザーさん:followee_followsを。判断するにはユーザーさん:フォロワー(時に@user.followersデータベースへのコール)、Railsは今CLASS_NAMEの各インスタンスを見てもよい:例えば、 『フォロー』ユーザーがフォロイー(すなわちあるforeign_key: :followee_idように:を介して)ユーザーさん:follower_followsを。
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éphanKochenに触発され、これは双方向の関連付けで機能する可能性があります
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"、もちろんクラス名を置き換えます。
:foreign_key、has_many :throughは必要ありません。の非常に便利な:dependentパラメーターの使用方法についての説明を追加しましたhas_many。