ActiveRecord self-join has_and_belongs_to_many

ActiveRecord self-join has_and_belongs_to_many

我有一个模型 User,我需要创建一个 self-referential 关联,我们称之为 friends,如果 user1user2friendscollection,然后user2,也在user1friendscollection.

我的理解是我需要一个连接 table 来完成这个,但是 ActiveRecord 不允许我创建一个连接 table ,其中两列具有相同的名称。我试图看看我是否可以自定义列名,例如 user1_iduser2_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 包含 user2user2.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

希望对您有所帮助!