在某些情况下,default_scope 会中断(更新|删除|销毁)_全部

发布于 2024-09-27 11:42:33 字数 896 浏览 8 评论 0原文

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

鉴于我有一个帖子模型和一个评论模型。帖子有_许多评论,评论属于_帖子。

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

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

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。

I believe this is a bug in Rails 3. I am hoping someone here can steer me in the correct direction. The code posted below, is purely for illustration of this problem. Hopefully this does not confuse the issue.

Given I have a Post model, and a Comment model. Post has_many Comments, and Comment belongs_to Post.

With a default_scope set on the Post model, defining joins() and where() relations. In this case where() is dependent on joins().

Normally Posts wouldn't be dependent on Comments. Again, I just want to give a simple example. This could be any case when where() is dependent on joins().

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

Running the following command:

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

Produces the following query, and ultimately throws ActiveRecord::StatementInvalid:

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

Again, update_all, delete_all, destroy_all behave the same way. I discovered this behaviour when my application complained when trying to update the counter_cache. Which eventually drills down into update_all.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

世界如花海般美丽 2024-10-04 11:42:33

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

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

对于delete_all,如果存在连接条件,我会引发错误,以使您必须执行的操作更加明显(而不是仅仅扔掉连接条件并在所有内容上运行delete_all,您会收到错误) 。

不确定 Rails 人员将如何处理我的拉取请求,但认为这与本次讨论相关。 (另外,如果您需要修复此错误,您可以尝试我的分支并在拉取请求上发表评论。)

I had this problem also, but we really needed to be able to use update_all with complex conditions in the default_scope (for example, without the default scope eager-loading is impossible, and pasting a named scope literally everywhere is no fun at all). I have opened a pull request here with my fix:

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

For delete_all I've raised an error if there's a join condition to make it more obvious what you have to do (instead of just tossing the join condition and running the delete_all on everything, you get an error).

Not sure what the rails guys are going to do with my pull request, but thought it was relevant to this discussion. (Also, if you need this bug fixed, you could try out my branch and post a comment on the pull request.)

北城孤痞 2024-10-04 11:42:33

也遇到了这个问题

如果你有

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

end

然后只是你update_all!或删除全部!当它有默认范围时。

I ran into this as well.

If you have

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

and you do a

Topic.update_all(...)

it’ll fail with

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

The work around for this is:

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

You can monkey patch this using this code (and requiring it in environment.rb or else where)

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

end

Then just you update_all! or delete_all! when it has a default scope.

标点 2024-10-04 11:42:33

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

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

You can also do this on the class level, without creating new methods, like so:

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
漫雪独思 2024-10-04 11:42:33

我不认为我会称其为错误。对我来说,这种行为似乎很合乎逻辑,尽管不是很明显。但我制定了一个似乎运行良好的 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 将连接对象设为只读的默认行为。

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

I don't think I'd call it a bug. The behavior seems logical enough to me, although not immediately obvious. But I worked out a SQL solution that seems to be working well. Using your example, it would be:

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

In reality I'm using reflection to make it more robust, but the above gets the idea cross. Moving the WHERE logic into the JOIN ensures that it won't be applied in inappropriate places. The :readonly option is to counteract Rails's default behavior of making joins'd objects readonly.

Also, I know that some people deride the use of default_scope. But for multi-tenant apps, it's a perfect fit.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文