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 ofb.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 ahas_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 throughpost_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 ofb.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 ahas_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 throughpost_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应该可以同时工作,至少对我有用。