Rails: Rails模型的默认排序顺序?

我想在我的模型中指定一个默认排序顺序。

因此,当我执行.where()而不指定.order()时,它使用默认排序。但如果我指定了.order(),它将覆盖默认值。

166517 次浏览
您可以使用default_scope来实现默认排序顺序 http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html < / p >

default_scope

这适用于Rails 4+:

class Book < ActiveRecord::Base
default_scope { order(created_at: :desc) }
end

对于Rails 2.3, 3,你需要这样做:

default_scope order('created_at DESC')

对于Rails 2.x:

default_scope :order => 'created_at DESC'

其中created_at是你想要进行默认排序的字段。

注意:<一口> ASC > < /晚餐是升序代码,<子> DESC子> < /是降序代码(desc dsc !)。

scope

一旦你习惯了,你也可以使用scope:

class Book < ActiveRecord::Base
scope :confirmed, :conditions => { :confirmed => true }
scope :published, :conditions => { :published => true }
end

对于Rails 2,你需要named_scope

:published作用域给你Book.published而不是 Book.find(:published => true)。< / p >

从Rails 3开始,你可以通过将这些方法用句点连接起来将它们“链接”在一起,所以对于上面的作用域,你现在可以使用Book.published.confirmed

使用这种方法,直到需要实际结果时才实际执行查询(惰性求值),因此可以将7个作用域链接在一起,但只产生1个实际的数据库查询,以避免执行7个独立查询带来的性能问题。

你可以使用一个传入的参数,如日期或user_id(这些参数在运行时会改变,因此需要“惰性求值”,使用lambda,如下所示:

scope :recent_books, lambda
{ |since_when| where("created_at >= ?", since_when) }
# Note the `where` is making use of AREL syntax added in Rails 3.

最后,你可以禁用默认作用域:

Book.with_exclusive_scope { find(:all) }

或者更好:

Book.unscoped.all

它将禁用任何筛选(条件)或排序(排序)。

请注意,第一个版本适用于Rails2+,而第二个(无作用域)仅适用于Rails3+


< >强所以 ... 如果你在想,嗯,这些就像方法,那么……是的,这正是这些瞄准镜的作用!< br > 它们就像def self.method_name ...code... end一样,但与ruby一样,它们是很好的语法捷径(或“糖”),让事情变得更容易!< / p >

事实上,它们是类级方法,因为它们操作的是1组“所有”记录。

例如scope:red,其中(color: 'red')应该更改为scope :red, -> { where(color: 'red') }

作为旁注,当使用不正确时,默认的_scope可能被误用/滥用 这主要是关于当它被用于诸如where限制(过滤)默认的选择(默认为坏主意)这样的操作时,而不仅仅是用于排序结果 对于where选择,只需使用常规命名作用域。并在查询中添加该作用域,例如Book.all.published,其中published是一个命名作用域

总之,作用域真的很棒,可以帮助你把东西推到“胖模型瘦控制器”dry方法的模型中。

快速更新一下上面迈克尔的精彩回答。

对于Rails 4.0+,你需要像这样把你的排序放在一个块中:

class Book < ActiveRecord::Base
default_scope { order('created_at DESC') }
end

注意,order语句被放置在花括号表示的块中。

他们之所以改变它,是因为它太容易传入动态的东西(比如当前时间)。这消除了问题,因为块是在运行时计算的。如果你不使用block,你会得到这样的错误:

取消了不带块调用#default_scope的支持。例如,请使用default_scope { where(color: 'red') }代替default_scope where(color: 'red')。(或者你也可以重新定义self.default_scope。)

正如@Dan在下面的评论中提到的,你可以这样做一个更ruby的语法:

class Book < ActiveRecord::Base
default_scope { order(created_at: :desc) }
end

或者有多个列:

class Book < ActiveRecord::Base
default_scope { order({begin_date: :desc}, :name) }
end

谢谢@Dan !

公认的答案过去是正确的——默认作用域是做到这一点的唯一方法。然而,默认作用域实际上是链式的(作用域本身应用于任何进一步的作用域/调用),这可能会导致一些不可预知的行为。因此,默认作用域被广泛认为是要不惜一切代价避免的东西。

重要的是要注意,对于你最初的问题,默认作用域实际上并不满足这一点,因为为了实际跳过默认作用域,任何模型都必须首先显式地指定.unscoped。这意味着如果调用order(:something),而不调用.unscoped,结果实际上更类似于order(:default).order(:something):something优先,当然,但是:default仍然在那里,所以现在有一个多列排序,这可能不是我们所希望的。最初的问题指定如果调用另一个顺序,默认排序顺序将被忽略,而默认作用域没有.unscoped,则不满足该要求。

从Rails 6开始,然而,现在有implicit_order_column,它可以在一个模型上设置。

class Book < ApplicationRecord
self.implicit_order_column = 'publish_date'
end

这将导致模型默认按该列排序,而不是按主键排序。与默认的内置排序(使用id)一样,然而,如果指定了不同的顺序(使用order(:something)),则忽略此隐式排序,而不是链式排序:.order(:something)不会导致多列排序,因此不需要.unscoped

它最常见的用途之一是切换到UUID作为主键。默认情况下,Rails仍然会对主键进行排序,但是由于该键现在是一个没有意义的字节字符串,因此这个顺序现在同样没有意义。因此,为了复制旧的行为,比如SomeModel.last应该默认返回最近创建的记录,只需在ApplicationRecord上设置这个:

class ApplicationRecord < ActiveRecord::Base
self.implicit_order_column = 'created_at'
end

注意,理论上可能在这里引起一个错误。created_at不是唯一的-它以微秒精度存储,但理论上可以用完全相同的created_at时间创建记录,因此,任何仅依赖于此的返回结果都将是不确定的-后续调用可能返回不同的记录。然而,这样做通常被认为是安全的——特别是如果代码不依赖于这里的确定性结果(它可能不应该依赖于确定性结果——如果需要确定结果,尽可能引用主键或其他一些唯一列会更好)。

同样值得注意的是,created_at(或使用的任何列)默认情况下没有键。这意味着select操作会稍微慢一些,至少在将一个键添加到该列以修复此问题之前。

最后,implicit_order_column的功能有限——它甚至不能设置排序顺序,更不用说做更复杂的事情,比如多列排序。如果该功能是必需的,default_scope仍然是唯一的方法。