如何创建可以邀请、邀请和拥有用户的群组?

How do I create groups users can be invited to, invite to and own?

TLDR:创建用户可以使用邀请功能加入的群组returns 一个明显的错误:

PG::UndefinedColumn: ERROR:  column user_groups.user_id does not exist

但我不清楚答案。用户组有邀请者和被邀请者。因此,我该如何设置才能确保不返回错误?

问题: 我想设置一个可以加入组的用户。这种关系将由 UserGroup 管理,因为用户可以是多个组的成员。一个群组也将有一个所有者用户,即群组的创建者和管理员。

用户也可以是邀请者,也可以邀请作为受邀者的用户。为了邀请朋友加入群组,用户在邀请者将向被邀请者发送邀请的情况下。然后受邀者需要接受才能成为该组的成员。

Schema.rb

ActiveRecord::Schema.define(version: 2020_09_29_204316) do

  enable_extension "plpgsql"

  create_table "addresses", force: :cascade do |t|
    t.string "address_type"
    t.string "address_line_1"
    t.string "address_line_2"
    t.string "address_line_3"
    t.string "city"
    t.string "county"
    t.string "postcode"
    t.string "country"
    t.bigint "user_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_id"], name: "index_addresses_on_user_id"
  end

  create_table "groups", force: :cascade do |t|
    t.string "group_name"
    t.bigint "owner_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["owner_id"], name: "index_groups_on_owner_id"
  end

  create_table "offers", force: :cascade do |t|
    t.string "partner"
    t.string "offer_copy"
    t.string "offer_url"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "requests", force: :cascade do |t|
    t.bigint "requester_id"
    t.bigint "requestee_id"
    t.integer "accepted", default: 0
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["requestee_id"], name: "index_requests_on_requestee_id"
    t.index ["requester_id"], name: "index_requests_on_requester_id"
  end

  create_table "user_groups", force: :cascade do |t|
    t.bigint "group_id", null: false
    t.bigint "invitee_id"
    t.bigint "inviter_id"
    t.integer "accepted", default: 0
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["group_id"], name: "index_user_groups_on_group_id"
    t.index ["invitee_id"], name: "index_user_groups_on_invitee_id"
    t.index ["inviter_id"], name: "index_user_groups_on_inviter_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "first_name", default: "", null: false
    t.string "last_name", default: "", null: false
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.date "birthday", default: "2020-10-22", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet "current_sign_in_ip"
    t.inet "last_sign_in_ip"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "invitation_token"
    t.datetime "invitation_created_at"
    t.datetime "invitation_sent_at"
    t.datetime "invitation_accepted_at"
    t.integer "invitation_limit"
    t.string "invited_by_type"
    t.bigint "invited_by_id"
    t.integer "invitations_count", default: 0
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true
    t.index ["invitations_count"], name: "index_users_on_invitations_count"
    t.index ["invited_by_id"], name: "index_users_on_invited_by_id"
    t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id"
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "addresses", "users"
  add_foreign_key "groups", "users", column: "owner_id"
  add_foreign_key "requests", "users", column: "requestee_id"
  add_foreign_key "requests", "users", column: "requester_id"
  add_foreign_key "user_groups", "groups"
  add_foreign_key "user_groups", "users", column: "invitee_id"
  add_foreign_key "user_groups", "users", column: "inviter_id"
end

群组迁移

class CreateGroups < ActiveRecord::Migration[6.0]
  def change
    create_table :groups do |t|
      t.string :group_name
      t.references :owner, foreign_key: { to_table: :users }

      t.timestamps
    end
  end
end

用户组迁移

class CreateUserGroups < ActiveRecord::Migration[6.0]
  def change
    create_table :user_groups do |t|
      t.references :group, null: false, foreign_key: true, null: false
      t.references :invitee, foreign_key: { to_table: :users }
      t.references :inviter, foreign_key: { to_table: :users }
      t.integer :accepted, default: 0

      t.timestamps
    end
  end
end

User.rb(型号)

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :invitable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :trackable

  validates :first_name, :last_name, :birthday, presence: true

  has_many :accepted_sent_requests, -> { where accepted: 1 }, foreign_key: :requester_id, class_name: 'Request'
  has_many :friends, through: :accepted_sent_requests, source: :requestee
  has_many :sent_requests, foreign_key: :requester_id, class_name: 'Request', dependent: :destroy
  has_many :received_requests, foreign_key: :requestee_id, class_name: 'Request', dependent: :destroy
  has_many :requestees, through: :sent_requests, dependent: :destroy
  has_many :requesters, through: :received_requests, dependent: :destroy

  has_many :user_groups

  has_many :accepted_sent_invites, -> { where accepted: 1 }, foreign_key: :inviter_id, class_name: 'UserGroup'
  has_many :friend_groups, through: :accepted_sent_invites, source: :invitee
  has_many :sent_invites, foreign_key: :inviter_id, class_name: 'UserGroup', dependent: :destroy
  has_many :received_invites, foreign_key: :invitee_id, class_name: 'UserGroup', dependent: :destroy
  has_many :invitees, through: :sent_invites, dependent: :destroy
  has_many :inviters, through: :received_invites, dependent: :destroy

  has_many :groups, through: :user_groups
  has_many :groups_owned, foreign_key: :owner_id, class_name: 'Group'

  has_many :addresses, dependent: :destroy

  searchkick match: :word, searchable: [:email]

  after_create :send_welcome_email

  private

  def send_welcome_email
    unless invitation_token?
      UserMailer.with(user: self).welcome.deliver_now
    end
  end

  def search_data
    {
      email: email
    }
  end    
end

UserGroup.rb(型号)

class UserGroup < ApplicationRecord
  belongs_to :inviter, class_name: 'User', optional: true
  belongs_to :invitee, class_name: 'User', optional: true
  belongs_to :group, optional: true

  def accept
    self.update_attributes(accepted: 1)
    Request.create!(inviter_id: self.invitee_id,
                    invitee_id: self.inviter_id,
                    accepted: 1)
  end

end

Group.rb(型号)

class Group < ApplicationRecord
  has_many :user_groups
  has_many :users, through: :user_groups
  belongs_to :owner, class_name: 'User'
end

通过编辑用户模型以包含 user_groups:

的关联解决了上述错误
has_many :user_groups, foreign_key: :inviter_id, class_name: 'UserGroup'
  has_many :user_groups, foreign_key: :invitee_id, class_name: 'UserGroup'
  has_many :groups, through: :user_groups, foreign_key: :inviter_id, class_name: 'UserGroup'
  has_many :groups, through: :user_groups, foreign_key: :invitee_id, class_name: 'UserGroup'

引用@james00794:

The error is quite clear. It is saying that there is no user_id column on the user_groups table, which is true according to your schema. Your has_many :groups, through: :user_groups is likely what is causing the error when you call user.groups, since the has_many :user_groups relation does not specify a foreign key. Rails then implicitly assumes that the column is called user_id. If you specify a foreign_key on the has_many :user_groups relation, this should resolve your error. Though you may need separate through relations for the inviters and invitees.