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



我试图删除所有不再有任何usersorganizations

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

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

当我添加delete_all时,我会得到相同的错误,如果我不包括references:

PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "users"

我可能会在纯SQL中编写解决方案,但我不明白为什么当我添加delete_all语句时,Rails不保持对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操作符,它的功能与连接类似,只是不能从连接表中选择字段。它形成一个有效的布尔表达式,可以对取反,并将扔入WHERE -conditions

在"SQL-free"形式中,我使用表达式"表的列",这在Rails的哈希条件中是可用的。这是一个偶然的发现,也是为数不多的不会使代码过于庞大的Arel用途之一。

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

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

警告:这是未测试的,没有失败语法。我没有充分的数据来测试它,但我认为它会起作用。但这意味着在每次用户删除之后运行此操作,这是一个系统架构决策。如果这是一种选择,也许值得一试。

最新更新