使用 ActiveRecord 助手删除 VS 查找孤儿

Deleting VS Finding Orphans using ActiveRecord helpers

我正在尝试删除不再有 users 的所有 organizations

使用下面的代码,我可以找到我想删除的所有记录:

Organization.includes(:users)
  .where(users: { id: nil })
  .references(:users)

当我添加 delete_all 时,我得到了与不包含 references:

时相同的错误
PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "users"

我可能可以用纯 SQL 编写解决方案,但我不明白为什么 Rails 在我添加 [=15] 时没有保留对 users 的引用=]声明。

以下是更多详细信息:

Organization:
  has_many :users

User:
  belongs_to :organization

我发现 includes 仅对预先加载有用(它 很少 处理我的情况),并且当与 references 结合使用时生成一些完全疯狂的东西(用tN_rM之类的东西给每个字段起别名),即使它实际上做了一个LEFT OUTER JOIN...可能 如果 消失 一旦 delete_all 出现,请帮助!

我发现使用 exists 更清晰、更简单。它是 Arel(避免它是没有意义的,无论如何它都在 ActiveRecord 的引擎盖下),但它是一个很小的部分,几乎不会引起注意:

Organization.where(
  User.where('users.organization_id = organizations.id').exists.not
)

或者,如果这串 SQL 对您来说不好看,请多使用一点 Arel,这样它就会引人注目:

Organization.where(
  User.where(organization_id: Organization.arel_table[:id]).exists.not
) # I tend to extract these   ^^^^^^^^^^^^^^^^^^^^^^^ into local variables

这可以很好地处理顶部的链接 .delete_all,因为它(在语法上)不是连接,即使它实际上等同于一个连接。

这背后的魔力

SQL 有一个 EXISTS 运算符,它在功能上类似于连接,除了无法从连接的 table 中选择字段。它形成一个有效的布尔表达式,可以 取反 抛入 WHERE-conditions.

在 "SQL-free" 形式中,我使用了表达式 "column of a table",它在 Rails' 散列条件中可用。这是一个偶然的发现,是 Arel 为数不多的不会使代码过于庞大的用途之一。

我不确定您打算如何在 MVC 框架中实现它,但通过模型操作清除组织似乎很干净。每当删除用户时,检查组织是否还有剩余成员。

在User.rb

class User < ActiveRecord::Base
  before_destroy :close_user
  ...

 def user_organization
   Organization.where(user_id: id)
 end

  private
    def close_user
        unless user_organization.users.any? 
          user_organization.destroy
        end
    end

end

已添加 将回调删除解决方案应用于作为多个组织成员的用户

如果用户有多个组织

class User < ActiveRecord::Base
      before_destroy :close_user
      ...

     def user_organizations
       Organization.where(user_id: id)
     end

      private
        def close_user
            user_organization.find_each do |organization| 
            unless organization.users.any? 
              organization.destroy
            end
        end

    end

注意:这没有经过测试,语法没有失败。我没有数据来全面测试它,但我认为它会起作用。但它意味着 运行 这个动作在每个用户删除之后,这是一个系统架构决定的。如果这是一个选择,它可能值得一试。