ActiveRecord self-join has_and_belongs_to_many
ActiveRecord self-join has_and_belongs_to_many
我有一个模型 User
,我需要创建一个 self-referential 关联,我们称之为 friends
,如果 user1
在 user2
的friends
collection,然后user2
,也在user1
的friends
collection.
我的理解是我需要一个连接 table 来完成这个,但是 ActiveRecord 不允许我创建一个连接 table ,其中两列具有相同的名称。我试图看看我是否可以自定义列名,例如 user1_id
、user2_id
,但我似乎找不到可以让我这样做的选项。
请注意,我对所有主键使用 UUID,并且我的数据库是 PostgreSQL。
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: :uuid do |t|
t.name :string
# ...
end
end
end
class CreateUsersJoinTable < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :users, column_options: { type: :uuid } do |t|
t.timestamps
t.index :user_id
t.index :created_at
t.index :updated_at
end
end
end
当然我得到:
ArgumentError: you can't define an already defined column 'user_id'.
我希望能够做 user1.friends << user2
,等等,这将导致 user1.friends
包含 user2
和 user2.friends
包含 user1
有什么方法可以使它工作吗?
编辑:我使用以下方法让它工作,但是集合似乎是一种方式,例如user1.friends << user2
并不意味着 user2.friends
将包含 user1
class CreateUsersFriends < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :friends, column_options: { type: :uuid } do |t|
t.timestamps
t.index :created_at
t.index :updated_at
end
end
end
class User < ApplicationRecord
has_and_belongs_to_many(:friends,
join_table: :users_friends,
foreign_key: :user_id,
association_foreign_key: :friend_id,
order: {created_at: :asc},
class_name: :User
)
end
我认为您要完成的是单向 has_many 关联,因此您应该能够按以下方式完成:
class CreateUsersJoinTable < ActiveRecord::Migration[6.0]
def change
create_join_table 'user_friends', :id => false do |t|
t.integer "user_a_id", :null => false
t.integer "user_b_id", :null => false
end
end
end
然后在您的用户模型中:
has_and_belongs_to_many(:users,
:join_table => "user_friends",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id")
如果你想做类似user1.friends << user2
的事情,那么你需要将上面的代码编辑为:
has_and_belongs_to_many(:friends,
:join_table => "user_friends",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id")
正如上面评论中提到的,双向方法可能难以维护和模拟,实现这一点的一种方法是使用 has_many through。话虽如此,如果您需要的是双向方法,那么您可以执行以下操作:
因此在您的用户模型中:
class User < ActiveRecord::Base
has_many :lover_loves, foreign_key: :love_id, class_name: "Love"
has_many :lovers, through: :lover_loves, source: :lover
has_many :loves_lover, foreign_key: :lover_id, class_name: "Love"
has_many :loves, through: :loves_lover, source: :love
end
class Love < ActiveRecord::Base
belongs_to :lover, foreign_key: "lover_id", class_name: "User"
belongs_to :love, foreign_key: "love_id", class_name: "User"
end
所以为了示例和命名,我改用了一个love关联,这样可以更'transitive',而且通俗易懂(你会的需要将其用作单独的关联)。
这是一个使用它的小片段:
user1.lovers << user2
user2.loves # should contain user1
希望对您有所帮助!
我有一个模型 User
,我需要创建一个 self-referential 关联,我们称之为 friends
,如果 user1
在 user2
的friends
collection,然后user2
,也在user1
的friends
collection.
我的理解是我需要一个连接 table 来完成这个,但是 ActiveRecord 不允许我创建一个连接 table ,其中两列具有相同的名称。我试图看看我是否可以自定义列名,例如 user1_id
、user2_id
,但我似乎找不到可以让我这样做的选项。
请注意,我对所有主键使用 UUID,并且我的数据库是 PostgreSQL。
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: :uuid do |t|
t.name :string
# ...
end
end
end
class CreateUsersJoinTable < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :users, column_options: { type: :uuid } do |t|
t.timestamps
t.index :user_id
t.index :created_at
t.index :updated_at
end
end
end
当然我得到:
ArgumentError: you can't define an already defined column 'user_id'.
我希望能够做 user1.friends << user2
,等等,这将导致 user1.friends
包含 user2
和 user2.friends
包含 user1
有什么方法可以使它工作吗?
编辑:我使用以下方法让它工作,但是集合似乎是一种方式,例如user1.friends << user2
并不意味着 user2.friends
将包含 user1
class CreateUsersFriends < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :friends, column_options: { type: :uuid } do |t|
t.timestamps
t.index :created_at
t.index :updated_at
end
end
end
class User < ApplicationRecord
has_and_belongs_to_many(:friends,
join_table: :users_friends,
foreign_key: :user_id,
association_foreign_key: :friend_id,
order: {created_at: :asc},
class_name: :User
)
end
我认为您要完成的是单向 has_many 关联,因此您应该能够按以下方式完成:
class CreateUsersJoinTable < ActiveRecord::Migration[6.0]
def change
create_join_table 'user_friends', :id => false do |t|
t.integer "user_a_id", :null => false
t.integer "user_b_id", :null => false
end
end
end
然后在您的用户模型中:
has_and_belongs_to_many(:users,
:join_table => "user_friends",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id")
如果你想做类似user1.friends << user2
的事情,那么你需要将上面的代码编辑为:
has_and_belongs_to_many(:friends,
:join_table => "user_friends",
:foreign_key => "user_a_id",
:association_foreign_key => "user_b_id")
正如上面评论中提到的,双向方法可能难以维护和模拟,实现这一点的一种方法是使用 has_many through。话虽如此,如果您需要的是双向方法,那么您可以执行以下操作:
因此在您的用户模型中:
class User < ActiveRecord::Base
has_many :lover_loves, foreign_key: :love_id, class_name: "Love"
has_many :lovers, through: :lover_loves, source: :lover
has_many :loves_lover, foreign_key: :lover_id, class_name: "Love"
has_many :loves, through: :loves_lover, source: :love
end
class Love < ActiveRecord::Base
belongs_to :lover, foreign_key: "lover_id", class_name: "User"
belongs_to :love, foreign_key: "love_id", class_name: "User"
end
所以为了示例和命名,我改用了一个love关联,这样可以更'transitive',而且通俗易懂(你会的需要将其用作单独的关联)。
这是一个使用它的小片段:
user1.lovers << user2
user2.loves # should contain user1
希望对您有所帮助!