覆盖 Rails 的 default_scope
问题出现的原因:在Rails中,默认情况下,如果没有指定排序方式,查询操作会按照模型的default_scope
中定义的顺序进行排序。然而,在某些情况下,我们可能需要改变默认的排序方式。
解决方法:使用reorder
方法可以改变default_scope
中定义的排序方式。例如,在Foo
模型中,如果默认排序方式为created_at desc
,我们可以使用Foo.reorder('created_at asc')
来改变排序方式为created_at asc
。
整理成一篇文章:
在Rails中,默认情况下,如果没有指定排序方式,查询操作会按照模型的default_scope
中定义的顺序进行排序。然而,在某些情况下,我们可能需要改变默认的排序方式。
如果只是想改变default_scope
中定义的排序方式,可以使用reorder
方法。例如,在Foo
模型中,如果默认排序方式为created_at desc
,我们可以使用Foo.reorder('created_at asc')
来改变排序方式为created_at asc
。
reorder
方法的使用非常简单,只需在模型的类定义中调用reorder
方法,并传入新的排序方式作为参数,即可改变默认的排序方式。
除此之外,我们还可以定义一个新的作用域,来实现不使用默认排序的查询操作。例如,我们可以定义一个名为without_default_order
的作用域,使用reorder("")
来取消默认排序。然后,我们可以使用Foo.without_default_order.order("created_at ASC")
来进行排序,这样的代码可读性更好。
如果需要改变Rails中默认的排序方式,可以使用reorder
方法来实现。此外,还可以通过定义新的作用域来实现不使用默认排序的查询操作。
在Rails 3中,使用unscoped
方法可以绕过默认作用域进行查询。然而,这会导致一个副作用,即如果Post模型具有多个Comment模型,那么Post.first.comments.unscoped
将返回所有评论,而不仅仅是没有默认作用域的评论。
这个副作用曾经给我带来了很大的困扰。特别是当你把unscoped
放在类方法中时,比如:def self.random; unscoped.order('rand()'); end
,unscoped
将删除所有的SQL查询,而不仅仅是删除默认作用域下的查询。虽然从技术上讲这是正确的,但是在使用unscoped
时要小心。
需要注意的是,unscoped
并不仅仅删除默认作用域,正如另一个评论中已经提到的,它可能会对其他方面造成混乱。
一个很好的经验法则是,只有在直接跟随在模型之后使用unscoped
时才可以使用,例如:Foo.unscoped.blah()
是可以的,但是绝对不要使用Foo.blah().unscoped
。
stackoverflow.com/questions/1834159/…提供了绕过上述副作用的解决方法。
在Rails中,使用default_scope
可能会导致一些问题,因此不建议使用它,除非你真的有必要。相反,你可以使用命名作用域(named scopes)来取代default_scope
。如果你需要覆盖默认作用域,你可以使用with_exclusive_scope
。
为什么不建议使用default_scope
呢?因为它可能会在应用程序的生命周期中引起多个问题。有人在Stack Overflow上提问了类似的问题,并得到了类似的回答:
“在没有必要的情况下,不要使用default_scope
。这是一个很好的建议!谢谢!”
实际上,有一些情况下确实需要使用default_scope
。比如,在一个Product
模型中,有一个inactive
标志,如果你希望在大多数情况下不显示不活跃的产品,那么设置一个default_scope { where inactive: false }
是一个好主意。对于剩下的1%的情况,你可以调用unscoped
来取消作用域,比如在管理员面板中。
然而,default_scope
违反了最小惊讶原则(principle of least astonishment),可能会让人感到困惑。因此,一些开发者对之前使用default_scope
的决策感到后悔。
总之,除非有必要,否则不要使用default_scope
。有些情况下,使用命名作用域或其他方法可能更好。在Rails 3中,with_exclusive_scope
已经被移除了,所以如果你在使用Rails 3或更高版本,你需要考虑其他解决方案。
需要注意的是,有一些gem(例如paranoid gem)仍然使用default_scope
,但这并不意味着它是一个好的实践。在使用这些gem时,还是要慎重考虑是否真的需要使用default_scope
。
虽然default_scope
在某些情况下是有用的,但在大多数情况下,尽量避免使用它,以免引起不必要的问题。