回答:
多対多の関係にはいくつかの種類があります。次の質問を自問する必要があります。
これには、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_follows
user.rbにあります。ミルの実行(非ループ)の関連付けを例として使用する場合、チームには:players
からまでの数が多い場合があります:contracts
。これは、(そのようなPlayerのキャリアの過程で)多くのスルーを持っている可能性のあるPlayerでも同じです。ただし、この場合、名前付きモデルが1つしか存在しない場合(つまり、User)、through:関係に同じ名前を付けると(たとえば、上記の投稿の例で行ったように)、(またはアクセスポイント)への結合テーブル。そして:teams
:contracts
through: :follow
through: :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
。