在rails中与相同模型的多对多关系?

时间:2022-10-04 15:46:49

How can I make a many-to-many relationship with the same model in rails?

如何在rails中与相同的模型建立多对多关系?

For example, each post is connected to many posts.

例如,每个post都连接到多个post。

6 个解决方案

#1


251  

There are several kinds of many-to-many relationships; you have to ask yourself the following questions:

有几种多对多关系;你必须问自己以下问题:

  • Do I want to store additional information with the association? (Additional fields in the join table.)
  • 我想要在协会中存储更多的信息吗?(连接表中的其他字段)。
  • Do the associations need to be implicitly bi-directional? (If post A is connected to post B, then post B is also connected to post A.)
  • 关联是否需要隐式双向?(如果post A连接到post B,那么post B也连接到post A)

That leaves four different possibilities. I'll walk over these below.

这就留下了四种不同的可能性。我将在下面经过这些。

For reference: the Rails documentation on the subject. There's a section called “Many-to-many”, and of course the documentation on the class methods themselves.

参考:关于主题的Rails文档。有一个部分叫做“多对多”,当然还有关于类方法本身的文档。

Simplest scenario, uni-directional, no additional fields

This is the most compact in code.

这是代码中最紧凑的。

I'll start out with this basic schema for your posts:

我将从你的文章的基本模式开始:

create_table "posts", :force => true do |t|
  t.string  "name", :null => false
end

For any many-to-many relationship, you need a join table. Here's the schema for that:

对于任何多对多关系,都需要一个连接表。这里的模式是这样的:

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

By default, Rails will call this table a combination of the names of the two tables we're joining. But that would turn out as posts_posts in this situation, so I decided to take post_connections instead.

默认情况下,Rails将把这个表称为我们正在连接的两个表的名称的组合。但是在这种情况下,这将会变成posts_posts,因此我决定使用post_connections。

Very important here is :id => false, to omit the default id column. Rails wants that column everywhere except on join tables for has_and_belongs_to_many. It will complain loudly.

这里非常重要的一点是:id => false,以省略默认id列。Rails希望该列在所有地方,除了has_and_belongs_to_many的联接表上。它会大声抱怨。

Finally, notice that the column names are non-standard as well (not post_id), to prevent conflict.

最后,注意列名也是非标准的(不是post_id),以防止冲突。

Now in your model, you simply need to tell Rails about these couple of non-standard things. It will look as follows:

现在,在您的模型中,您只需要告诉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

And that should simply work! Here's an example irb session run through script/console:

这应该很简单!下面是一个通过脚本/控制台运行的irb会话示例:

>> 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!">]

You'll find that assigning to the posts association will create records in the post_connections table as appropriate.

您将发现分配给posts关联将在post_connections表中适当地创建记录。

Some things to note:

一些注意事项:

  • You can see in the above irb session that the association is uni-directional, because after a.posts = [b, c], the output of b.posts does not include the first post.
  • 您可以在上面的irb会话中看到关联是单向的,因为在a之后。posts = [b, c], b的输出。贴子不包括第一个贴子。
  • Another thing you may have noticed is that there is no model PostConnection. You normally don't use models for a has_and_belongs_to_many association. For this reason, you won't be able to access any additional fields.
  • 您可能注意到的另一件事是没有模型后连接。您通常不使用has_and_belongs_to_many关联的模型。由于这个原因,您将无法访问任何其他字段。

Uni-directional, with additional fields

Right, now... You've got a regular user who has today made a post on your site about how eels are delicious. This total stranger comes around to your site, signs up, and writes a scolding post on regular user's ineptitude. After all, eels are an endangered species!

对,现在……你有一个老用户,他今天在你的网站上发了一篇关于鳗鱼如何美味的帖子。这个完全陌生的人来到你的网站,签了名,并写了一篇关于普通用户不称职的斥责文章。毕竟,鳗鱼是濒危物种!

So you'd like to make clear in your database that post B is a scolding rant on post A. To do that, you want to add a category field to the association.

所以你要在你的数据库中明确,post B是post a上的责骂,要做到这一点,你需要向关联添加一个category字段。

What we need is no longer a has_and_belongs_to_many, but a combination of has_many, belongs_to, has_many ..., :through => ... and an extra model for the join table. This extra model is what gives us the power to add additional information to the association itself.

我们需要的不再是has_and_belongs_to_many,而是has_many、belongs_to、has_many…,:通过= >……以及连接表的额外模型。这个额外的模型使我们能够向关联本身添加额外的信息。

Here's another schema, very similar to the above:

这是另一种模式,与上面非常相似:

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

Notice how, in this situation, post_connections does have an id column. (There's no :id => false parameter.) This is required, because there'll be a regular ActiveRecord model for accessing the table.

注意,在这种情况下,post_connections有一个id列。(没有:id =>假参数)这是必需的,因为有一个常规的ActiveRecord模型用于访问表。

I'll start with the PostConnection model, because it's dead simple:

我将从后连接模型开始,因为它非常简单:

class PostConnection < ActiveRecord::Base
  belongs_to :post_a, :class_name => :Post
  belongs_to :post_b, :class_name => :Post
end

The only thing going on here is :class_name, which is necessary, because Rails cannot infer from post_a or post_b that we're dealing with a Post here. We have to tell it explicitly.

这里唯一要做的事情是:class_name,这是必需的,因为Rails不能从post_a或post_b推断出我们在这里处理的是Post。我们必须明确地告诉它。

Now the Post model:

现在的模型:

class Post < ActiveRecord::Base
  has_many :post_connections, :foreign_key => :post_a_id
  has_many :posts, :through => :post_connections, :source => :post_b
end

With the first has_many association, we tell the model to join post_connections on posts.id = post_connections.post_a_id.

在第一个has_many关联中,我们告诉模型在posts上连接post_connections。id = post_connections.post_a_id。

With the second association, we are telling Rails that we can reach the other posts, the ones connected to this one, through our first association post_connections, followed by the post_b association of PostConnection.

通过第二个关联,我们告诉Rails我们可以通过我们的第一个关联post_connections,然后是post_b关联的PostConnection,到达与这个关联的其他post。

There's just one more thing missing, and that is that we need to tell Rails that a PostConnection is dependent on the posts it belongs to. If one or both of post_a_id and post_b_id were NULL, then that connection wouldn't tell us much, would it? Here's how we do that in our Post model:

还有一点需要注意,那就是我们需要告诉Rails PostConnection依赖于它所属的post。如果post_a_id和post_b_id中的一个或两个都为NULL,那么这个连接就不会告诉我们很多信息,不是吗?以下是我们在邮政模式下的做法:

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

Besides the slight change in syntax, two real things are different here:

除了语法上的细微变化外,这里有两个事实是不同的:

  • The has_many :post_connections has an extra :dependent parameter. With the value :destroy, we tell Rails that, once this post disappears, it can go ahead and destroy these objects. An alternative value you can use here is :delete_all, which is faster, but will not call any destroy hooks if you are using those.
  • has_many:post_connections有一个额外的:从属参数。使用这个值:destroy,我们告诉Rails,一旦这个post消失,它就可以继续破坏这些对象。您可以在这里使用的另一个值是:delete_all,它更快,但如果您使用的是销毁钩子,则不会调用任何销毁钩子。
  • We've added a has_many association for the reverse connections as well, the ones that have linked us through post_b_id. This way, Rails can neatly destroy those as well. Note that we have to specify :class_name here, because the model's class name can no longer be inferred from :reverse_post_connections.
  • 我们还为反向连接添加了has_many关联,这些关联通过post_b_id链接到我们。通过这种方式,Rails也可以巧妙地摧毁它们。注意,我们必须在这里指定:class_name,因为不能再从:reverse_post_connections推断模型的类名。

With this in place, I bring you another irb session through 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

Instead of creating the association and then setting the category separately, you can also just create a PostConnection and be done with it:

与其创建关联,然后分别设置类别,你还可以创建一个后连接,然后使用它:

>> 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!">]

And we can also manipulate the post_connections and reverse_post_connections associations; it will neatly reflect in the posts association:

我们还可以操作post_connections和reverse_post_connections关联;它将巧妙地反映在邮务协会:

>> 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
=> []

Bi-directional looped associations

In normal has_and_belongs_to_many associations, the association is defined in both models involved. And the association is bi-directional.

在普通的has_and_belongs_to_many关联中,关联在涉及的两个模型中都有定义。这种联系是双向的。

But there is just one Post model in this case. And the association is only specified once. That's exactly why in this specific case, associations are uni-directional.

但这里只有一个Post模型。关联只指定一次。这就是为什么在这个特定的例子中,关联是单向的。

The same is true for the alternative method with has_many and a model for the join table.

对于具有has_many的替代方法和连接表的模型也是如此。

This is best seen when simply accessing the associations from irb, and looking at the SQL that Rails generates in the log file. You'll find something like the following:

这在简单地从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 )

To make the association bi-directional, we'd have to find a way to make Rails OR the above conditions with post_a_id and post_b_id reversed, so it will look in both directions.

为了使关联具有双向性,我们必须找到一种方法使Rails或上面的条件与post_a_id和post_b_id颠倒,这样它就可以在两个方向上查找。

Unfortunately, the only way to do this that I know of is rather hacky. You'll have to manually specify your SQL using options to has_and_belongs_to_many such as :finder_sql, :delete_sql, etc. It's not pretty. (I'm open to suggestions here too. Anyone?)

不幸的是,我所知道的做这件事的唯一方法是相当陈腐的。必须使用has_and_belongs_to_many(如:finder_sql、:delete_sql等)选项手动指定SQL。(我也对这里的建议持开放态度。有人知道吗?)

#2


15  

To answer the question posed by Shteef:

回答Shteef提出的问题:

Bi-directional looped associations

The follower-followee relationship among Users is a good example of a Bi-directional looped association. A User can have many:

用户之间的follow -followee关系是双向循环关联的一个很好的例子。用户可以有很多:

  • followers in its capacity as followee
  • 追随者以其能力作为追随。
  • followees in its capacity as follower.
  • 以追随者的身份追随。

Here's how the code for user.rb might look:

下面是用户的代码。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

Here's how the code for 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

The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career). But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow, or, like was done above in the posts example, through: :post_connections) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision. Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows.

需要注意的最重要的事情可能是术语:follower_follow和:user.rb中的followee_follow。以一个非循环联盟的运行为例,一个团队可能有很多:通过:合同的球员。这对一个球员来说也没什么不同,他可能有很多:团队通过:合同(在这个球员的职业生涯中)。但是在这种情况下,如果只有一个命名的模型存在(比如一个用户),那么对整个:关系进行相同的命名(例如,通过::follow,或者像上面的post示例中那样,通过::post_connections)将导致连接表的不同用例(或访问点)的命名冲突。如下所示:为了避免这种命名冲突,创建了followee_follow。现在,用户可以有许多:follower:follower_follow和many:followees through:followee_follow。

To determine a User’s :followees (upon an @user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows. To determine a User’s :followers (upon an @user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.

要确定用户的:followees(对@user)。后续调用到数据库),Rails现在可以查看class_name:“Follow”的每个实例,其中这个用户是追随者(例如,foreign_key: follower_id),通过:这个用户的:followee_follow。确定用户的:追随者(在@user上)。追随者调用数据库),Rails现在可以查看class_name:“Follow”的每个实例,其中此类用户是followee(即foreign_key::followee_id)通过:此类用户的:follower_follow。

#3


6  

If anyone came here to try to find out how to create friend relationships in Rails, then I would refer them to what I finally decided to use, which is to copy what 'Community Engine' did.

如果有人来这里试图了解如何在Rails中创建朋友关系,那么我将向他们介绍我最终决定使用的内容,即复制“社区引擎”所做的工作。

You can refer to:

您可以参考:

https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb

https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb

and

https://github.com/bborn/communityengine/blob/master/app/models/user.rb

https://github.com/bborn/communityengine/blob/master/app/models/user.rb

for more information.

为更多的信息。

TL;DR

博士TL;

# 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"

#4


1  

For bi-directional belongs_to_and_has_many, refer to the great answer already posted, and then create another association with a different name, the foreign keys reversed and ensure that you have class_name set to point back to the correct model. Cheers.

对于双向belongs_to_and_has_many,请参考已经发布的伟大答案,然后创建另一个具有不同名称的关联,并反转外键,确保class_name设置为指向正确的模型。欢呼。

#5


0  

If anyone had issues getting the excellent answer to work, such as:

如果有人在工作中遇到问题,比如:

(Object doesn't support #inspect)
=>

(对象不支持#inspect) =>

or

NoMethodError: undefined method `split' for :Mission:Symbol

NoMethodError:未定义的方法“分割”:任务:符号。

Then the solution is to replace :PostConnection with "PostConnection", substituting your classname of course.

然后解决方案是用“PostConnection”替换:PostConnection,当然要替换您的类名。

#6


0  

Inspired by @Stéphan Kochen, this could work for bi-directional associations

受到@Stephan 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

then post.posts && post.reversed_posts should both works, at least worked for me.

然后文章。帖子& &。反向post应该可以同时工作,至少对我有用。

#1


251  

There are several kinds of many-to-many relationships; you have to ask yourself the following questions:

有几种多对多关系;你必须问自己以下问题:

  • Do I want to store additional information with the association? (Additional fields in the join table.)
  • 我想要在协会中存储更多的信息吗?(连接表中的其他字段)。
  • Do the associations need to be implicitly bi-directional? (If post A is connected to post B, then post B is also connected to post A.)
  • 关联是否需要隐式双向?(如果post A连接到post B,那么post B也连接到post A)

That leaves four different possibilities. I'll walk over these below.

这就留下了四种不同的可能性。我将在下面经过这些。

For reference: the Rails documentation on the subject. There's a section called “Many-to-many”, and of course the documentation on the class methods themselves.

参考:关于主题的Rails文档。有一个部分叫做“多对多”,当然还有关于类方法本身的文档。

Simplest scenario, uni-directional, no additional fields

This is the most compact in code.

这是代码中最紧凑的。

I'll start out with this basic schema for your posts:

我将从你的文章的基本模式开始:

create_table "posts", :force => true do |t|
  t.string  "name", :null => false
end

For any many-to-many relationship, you need a join table. Here's the schema for that:

对于任何多对多关系,都需要一个连接表。这里的模式是这样的:

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

By default, Rails will call this table a combination of the names of the two tables we're joining. But that would turn out as posts_posts in this situation, so I decided to take post_connections instead.

默认情况下,Rails将把这个表称为我们正在连接的两个表的名称的组合。但是在这种情况下,这将会变成posts_posts,因此我决定使用post_connections。

Very important here is :id => false, to omit the default id column. Rails wants that column everywhere except on join tables for has_and_belongs_to_many. It will complain loudly.

这里非常重要的一点是:id => false,以省略默认id列。Rails希望该列在所有地方,除了has_and_belongs_to_many的联接表上。它会大声抱怨。

Finally, notice that the column names are non-standard as well (not post_id), to prevent conflict.

最后,注意列名也是非标准的(不是post_id),以防止冲突。

Now in your model, you simply need to tell Rails about these couple of non-standard things. It will look as follows:

现在,在您的模型中,您只需要告诉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

And that should simply work! Here's an example irb session run through script/console:

这应该很简单!下面是一个通过脚本/控制台运行的irb会话示例:

>> 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!">]

You'll find that assigning to the posts association will create records in the post_connections table as appropriate.

您将发现分配给posts关联将在post_connections表中适当地创建记录。

Some things to note:

一些注意事项:

  • You can see in the above irb session that the association is uni-directional, because after a.posts = [b, c], the output of b.posts does not include the first post.
  • 您可以在上面的irb会话中看到关联是单向的,因为在a之后。posts = [b, c], b的输出。贴子不包括第一个贴子。
  • Another thing you may have noticed is that there is no model PostConnection. You normally don't use models for a has_and_belongs_to_many association. For this reason, you won't be able to access any additional fields.
  • 您可能注意到的另一件事是没有模型后连接。您通常不使用has_and_belongs_to_many关联的模型。由于这个原因,您将无法访问任何其他字段。

Uni-directional, with additional fields

Right, now... You've got a regular user who has today made a post on your site about how eels are delicious. This total stranger comes around to your site, signs up, and writes a scolding post on regular user's ineptitude. After all, eels are an endangered species!

对,现在……你有一个老用户,他今天在你的网站上发了一篇关于鳗鱼如何美味的帖子。这个完全陌生的人来到你的网站,签了名,并写了一篇关于普通用户不称职的斥责文章。毕竟,鳗鱼是濒危物种!

So you'd like to make clear in your database that post B is a scolding rant on post A. To do that, you want to add a category field to the association.

所以你要在你的数据库中明确,post B是post a上的责骂,要做到这一点,你需要向关联添加一个category字段。

What we need is no longer a has_and_belongs_to_many, but a combination of has_many, belongs_to, has_many ..., :through => ... and an extra model for the join table. This extra model is what gives us the power to add additional information to the association itself.

我们需要的不再是has_and_belongs_to_many,而是has_many、belongs_to、has_many…,:通过= >……以及连接表的额外模型。这个额外的模型使我们能够向关联本身添加额外的信息。

Here's another schema, very similar to the above:

这是另一种模式,与上面非常相似:

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

Notice how, in this situation, post_connections does have an id column. (There's no :id => false parameter.) This is required, because there'll be a regular ActiveRecord model for accessing the table.

注意,在这种情况下,post_connections有一个id列。(没有:id =>假参数)这是必需的,因为有一个常规的ActiveRecord模型用于访问表。

I'll start with the PostConnection model, because it's dead simple:

我将从后连接模型开始,因为它非常简单:

class PostConnection < ActiveRecord::Base
  belongs_to :post_a, :class_name => :Post
  belongs_to :post_b, :class_name => :Post
end

The only thing going on here is :class_name, which is necessary, because Rails cannot infer from post_a or post_b that we're dealing with a Post here. We have to tell it explicitly.

这里唯一要做的事情是:class_name,这是必需的,因为Rails不能从post_a或post_b推断出我们在这里处理的是Post。我们必须明确地告诉它。

Now the Post model:

现在的模型:

class Post < ActiveRecord::Base
  has_many :post_connections, :foreign_key => :post_a_id
  has_many :posts, :through => :post_connections, :source => :post_b
end

With the first has_many association, we tell the model to join post_connections on posts.id = post_connections.post_a_id.

在第一个has_many关联中,我们告诉模型在posts上连接post_connections。id = post_connections.post_a_id。

With the second association, we are telling Rails that we can reach the other posts, the ones connected to this one, through our first association post_connections, followed by the post_b association of PostConnection.

通过第二个关联,我们告诉Rails我们可以通过我们的第一个关联post_connections,然后是post_b关联的PostConnection,到达与这个关联的其他post。

There's just one more thing missing, and that is that we need to tell Rails that a PostConnection is dependent on the posts it belongs to. If one or both of post_a_id and post_b_id were NULL, then that connection wouldn't tell us much, would it? Here's how we do that in our Post model:

还有一点需要注意,那就是我们需要告诉Rails PostConnection依赖于它所属的post。如果post_a_id和post_b_id中的一个或两个都为NULL,那么这个连接就不会告诉我们很多信息,不是吗?以下是我们在邮政模式下的做法:

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

Besides the slight change in syntax, two real things are different here:

除了语法上的细微变化外,这里有两个事实是不同的:

  • The has_many :post_connections has an extra :dependent parameter. With the value :destroy, we tell Rails that, once this post disappears, it can go ahead and destroy these objects. An alternative value you can use here is :delete_all, which is faster, but will not call any destroy hooks if you are using those.
  • has_many:post_connections有一个额外的:从属参数。使用这个值:destroy,我们告诉Rails,一旦这个post消失,它就可以继续破坏这些对象。您可以在这里使用的另一个值是:delete_all,它更快,但如果您使用的是销毁钩子,则不会调用任何销毁钩子。
  • We've added a has_many association for the reverse connections as well, the ones that have linked us through post_b_id. This way, Rails can neatly destroy those as well. Note that we have to specify :class_name here, because the model's class name can no longer be inferred from :reverse_post_connections.
  • 我们还为反向连接添加了has_many关联,这些关联通过post_b_id链接到我们。通过这种方式,Rails也可以巧妙地摧毁它们。注意,我们必须在这里指定:class_name,因为不能再从:reverse_post_connections推断模型的类名。

With this in place, I bring you another irb session through 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

Instead of creating the association and then setting the category separately, you can also just create a PostConnection and be done with it:

与其创建关联,然后分别设置类别,你还可以创建一个后连接,然后使用它:

>> 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!">]

And we can also manipulate the post_connections and reverse_post_connections associations; it will neatly reflect in the posts association:

我们还可以操作post_connections和reverse_post_connections关联;它将巧妙地反映在邮务协会:

>> 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
=> []

Bi-directional looped associations

In normal has_and_belongs_to_many associations, the association is defined in both models involved. And the association is bi-directional.

在普通的has_and_belongs_to_many关联中,关联在涉及的两个模型中都有定义。这种联系是双向的。

But there is just one Post model in this case. And the association is only specified once. That's exactly why in this specific case, associations are uni-directional.

但这里只有一个Post模型。关联只指定一次。这就是为什么在这个特定的例子中,关联是单向的。

The same is true for the alternative method with has_many and a model for the join table.

对于具有has_many的替代方法和连接表的模型也是如此。

This is best seen when simply accessing the associations from irb, and looking at the SQL that Rails generates in the log file. You'll find something like the following:

这在简单地从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 )

To make the association bi-directional, we'd have to find a way to make Rails OR the above conditions with post_a_id and post_b_id reversed, so it will look in both directions.

为了使关联具有双向性,我们必须找到一种方法使Rails或上面的条件与post_a_id和post_b_id颠倒,这样它就可以在两个方向上查找。

Unfortunately, the only way to do this that I know of is rather hacky. You'll have to manually specify your SQL using options to has_and_belongs_to_many such as :finder_sql, :delete_sql, etc. It's not pretty. (I'm open to suggestions here too. Anyone?)

不幸的是,我所知道的做这件事的唯一方法是相当陈腐的。必须使用has_and_belongs_to_many(如:finder_sql、:delete_sql等)选项手动指定SQL。(我也对这里的建议持开放态度。有人知道吗?)

#2


15  

To answer the question posed by Shteef:

回答Shteef提出的问题:

Bi-directional looped associations

The follower-followee relationship among Users is a good example of a Bi-directional looped association. A User can have many:

用户之间的follow -followee关系是双向循环关联的一个很好的例子。用户可以有很多:

  • followers in its capacity as followee
  • 追随者以其能力作为追随。
  • followees in its capacity as follower.
  • 以追随者的身份追随。

Here's how the code for user.rb might look:

下面是用户的代码。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

Here's how the code for 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

The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career). But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow, or, like was done above in the posts example, through: :post_connections) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision. Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows.

需要注意的最重要的事情可能是术语:follower_follow和:user.rb中的followee_follow。以一个非循环联盟的运行为例,一个团队可能有很多:通过:合同的球员。这对一个球员来说也没什么不同,他可能有很多:团队通过:合同(在这个球员的职业生涯中)。但是在这种情况下,如果只有一个命名的模型存在(比如一个用户),那么对整个:关系进行相同的命名(例如,通过::follow,或者像上面的post示例中那样,通过::post_connections)将导致连接表的不同用例(或访问点)的命名冲突。如下所示:为了避免这种命名冲突,创建了followee_follow。现在,用户可以有许多:follower:follower_follow和many:followees through:followee_follow。

To determine a User’s :followees (upon an @user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows. To determine a User’s :followers (upon an @user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.

要确定用户的:followees(对@user)。后续调用到数据库),Rails现在可以查看class_name:“Follow”的每个实例,其中这个用户是追随者(例如,foreign_key: follower_id),通过:这个用户的:followee_follow。确定用户的:追随者(在@user上)。追随者调用数据库),Rails现在可以查看class_name:“Follow”的每个实例,其中此类用户是followee(即foreign_key::followee_id)通过:此类用户的:follower_follow。

#3


6  

If anyone came here to try to find out how to create friend relationships in Rails, then I would refer them to what I finally decided to use, which is to copy what 'Community Engine' did.

如果有人来这里试图了解如何在Rails中创建朋友关系,那么我将向他们介绍我最终决定使用的内容,即复制“社区引擎”所做的工作。

You can refer to:

您可以参考:

https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb

https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb

and

https://github.com/bborn/communityengine/blob/master/app/models/user.rb

https://github.com/bborn/communityengine/blob/master/app/models/user.rb

for more information.

为更多的信息。

TL;DR

博士TL;

# 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"

#4


1  

For bi-directional belongs_to_and_has_many, refer to the great answer already posted, and then create another association with a different name, the foreign keys reversed and ensure that you have class_name set to point back to the correct model. Cheers.

对于双向belongs_to_and_has_many,请参考已经发布的伟大答案,然后创建另一个具有不同名称的关联,并反转外键,确保class_name设置为指向正确的模型。欢呼。

#5


0  

If anyone had issues getting the excellent answer to work, such as:

如果有人在工作中遇到问题,比如:

(Object doesn't support #inspect)
=>

(对象不支持#inspect) =>

or

NoMethodError: undefined method `split' for :Mission:Symbol

NoMethodError:未定义的方法“分割”:任务:符号。

Then the solution is to replace :PostConnection with "PostConnection", substituting your classname of course.

然后解决方案是用“PostConnection”替换:PostConnection,当然要替换您的类名。

#6


0  

Inspired by @Stéphan Kochen, this could work for bi-directional associations

受到@Stephan 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

then post.posts && post.reversed_posts should both works, at least worked for me.

然后文章。帖子& &。反向post应该可以同时工作,至少对我有用。