default_scope在某些情况下会中断(更新|删除|销毁)_all

时间:2010-10-15 16:07:38

标签: ruby-on-rails destroy default-scope

我相信这是Rails 3中的一个错误。我希望有人能指引我朝着正确的方向前进。下面发布的代码纯粹是为了说明这个问题。希望这不会混淆这个问题。

鉴于我有一个Post模型和一个Comment模型。发表has_many评论,评论发表于发帖。

在Post模型上设置default_scope,定义连接()和where()关系。在这种情况下,()依赖于连接()。

通常帖子不依赖于评论。再说一遍,我只想举一个简单的例子。当where()依赖于join()时,这可能是任何情况。

class Post < ActiveRecord::Base
  has_many :comments, :dependent => :destroy

  default_scope joins(:comments).where("comments.id < 999")
end

class Comment < ActiveRecord::Base
  belongs_to :post, :counter_cache => true
end

运行以下命令:

Post.update_all(:title => Time.now)

生成以下查询,并最终抛出ActiveRecord :: StatementInvalid:

UPDATE `posts` SET `title` = '2010-10-15 15:59:27'  WHERE (comments.id < 999)

同样,update_all,delete_all,destroy_all的行为方式相同。当我的应用程序在尝试更新counter_cache时抱怨时,我发现了这种行为。最终深入研究update_all。

4 个答案:

答案 0 :(得分:7)

我也遇到了这个问题,但我们确实需要能够在update_all中使用复杂条件的default_scope(例如,没有默认范围,切换加载是不可能的,并粘贴一个任何地方的命名范围都没有任何乐趣。我已经在我的修复程序中打开了一个拉取请求:

https://github.com/rails/rails/pull/8449

对于delete_all我已经引发了一个错误,如果有一个连接条件使你更明显你必须做什么(而不是只是抛出连接条件并在所有内容上运行delete_all,你得到一个错误)。

不确定我的拉动请求会对rails人员做些什么,但认为这与此讨论有关。 (另外,如果您需要修复此错误,可以尝试我的分支并在拉取请求上发表评论。)

答案 1 :(得分:4)

ran into this as well

如果你有

class Topic < ActiveRecord::Base
  default_scope :conditions => "forums.preferences > 1", :include => [:forum]
end

然后你做了

Topic.update_all(...)

它将失败

Mysql::Error: Unknown column 'forums.preferences' in 'where clause'

解决这个问题的方法是:

Topic.send(:with_exclusive_scope) { Topic.update_all(...) }

您可以使用此代码进行修补(并在environment.rb或其他地方需要它)

module ActiveRecordMixins
  class ActiveRecord::Base
    def self.update_all!(*args)
      self.send(:with_exclusive_scope) { self.update_all(*args) }
    end
    def self.delete_all!(*args)
      self.send(:with_exclusive_scope) { self.delete_all(*args) }
    end
  end
end

然后就是你update_all!或者delete_all!当它有默认范围时。

答案 2 :(得分:1)

您也可以在类级别执行此操作,而无需创建新方法,如下所示:

def self.update_all(*args)
  self.send(:with_exclusive_scope) { super(*args) }
end

def self.delete_all(*args)
  self.send(:with_exclusive_scope) { super(*args) }
end

答案 3 :(得分:0)

我认为我不认为它是一个错误。这种行为对我来说似乎合乎逻辑,虽然不是很明显。但是我找到了一个似乎运行良好的SQL解决方案。使用您的示例,它将是:

class Post < ActiveRecord::Base
  has_many :comments, :dependent => :destroy

  default_scope do
    with_scope :find => {:readonly => false} do 
      joins("INNER JOIN comments ON comments.post_id = posts.id AND comments.id < 999")
    end
  end
end

实际上我正在使用反射来使其更加强大,但上面提到了这个想法。将WHERE逻辑移动到JOIN中可确保它不会应用于不适当的位置。 :readonly选项是为了抵消Rails默认使用joins'd个对象的行为。

另外,我知道有些人嘲笑使用default_scope。但对于多租户应用程序,它非常适合。