【问题标题】:Deleting VS Finding Orphans using ActiveRecord helpers使用 ActiveRecord 助手删除 VS 查找孤儿
【发布时间】:2016-01-15 18:30:22
【问题描述】:

我正在尝试删除所有不再有任何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

【问题讨论】:

  • 不应该是User.includes(:organizations)...吗?
  • 我更新了问题,我也改变了一些关系
  • 您能否发布定义组织和用户之间关联的代码?
  • 我更新了一些细节。如果你还想要什么,请告诉我
  • @IvanSelivanov 那个特定的已经在 Rails 4 中修复了。虽然它没有用,因为joins 做了一个INNER JOIN,它不会捕获没有用户的组织。这是一种真正“缺少功能”的错误,必须先设计功能......

标签: ruby-on-rails postgresql ruby-on-rails-4 activerecord arel


【解决方案1】:

我发现 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”形式中,我使用了“表的列”表达式,结果证明它可用于 Rails 的哈希条件。这是一个偶然的发现,是 Arel 为数不多的不会使代码过于庞大的用途之一。

【讨论】:

  • 这看起来很酷,但我还没有看到太多的嵌套语法。 Organization.where 怎么知道要得到什么?在我看来,User 查询会返回用户,但我猜它是一个子查询会改变它的功能?
  • 试了一下,但我得到了 0 条记录。我认为这可能是因为内部查询没有返回任何用户(因为它们不存在),然后外部查询正在获取 0 个用户的组织
  • @TMP 在这种情况下,单独的内部查询是没有用的,因为在没有连接的情况下引用where 中的另一个表会隐式执行INNER JOIN。但是,在 "EXISTS 中使用它会使它的行为有所不同。这种语法很难得到,我自己偶然发现了它(使用 exists 而不是 exists?,哇!)。无论如何,应该可以工作,你能粘贴生成的 SQL 吗?我在不同的模型上测试它。
  • @TMP 而且,嗯,它应该只作为一个隐式内部连接使用适当的FROM-clause 我没有填写。所以查询应该触发一个 SQL 语法错误,如何你让它返回 0 个用户吗? O_o
  • 我今天又试了一次,成功了!我想在我已经删除所有记录之前,所以它当然返回了 0 条记录
【解决方案2】:

我不确定您打算如何在 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

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

【讨论】:

  • 我相信在 TMP 的情况下,关系是由 users 表中的外键 organization_id 建立的,而不是相反。
  • @jaxx 我同意你的观点,但也许你会同意检查组织以删除用户是有道理的。我可能会更新关系,以便为零用户组织删除实施干净的解决方案。
  • 哦,是的,绝对同意回调,这是一个聪明的实现,实际上可以省去您在后期寻找无用户组织的麻烦;)
  • 明智之举,是的。如果此时不存在空组织,则应保持此状态。除了...我不确定,但我很想知道在两个并行事务(a 和 b)中(a)最后一个用户被删除 -> 组织被擦除和(b)一个用户被添加时会发生什么到那个组织。这是一个极端的情况......
  • 回调的问题是你通常调用destroydestroy_all 来调用它。就我而言,有超过 10 个相关模型,每个模型都有自己的销毁回调。我稍微简化了我的问题,但真正发生的是我正在删除一个组织。在before_destroy 回调中,它会删除所有相关模型:UsersOrganizations、Resources、Options、Data 等等。
猜你喜欢
  • 2011-02-09
  • 2011-01-02
  • 2012-10-22
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 2010-09-23
  • 2014-10-07
相关资源
最近更新 更多