我试图删除所有不再有任何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中编写解决方案,但我不明白为什么当我添加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
警告:这是未测试的,没有失败语法。我没有充分的数据来测试它,但我认为它会起作用。但这意味着在每次用户删除之后运行此操作,这是一个系统架构决策。如果这是一种选择,也许值得一试。