ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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


2
23 revs, 4 users 82%

默认范围

这适用于 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

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)

从 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+

所以 ...如果您在想,嗯,那么这些就像方法一样...,是的,这正是这些作用域的含义!
它们就像拥有 def self.method_name ...code... end 但是与 ruby 一样,它们是很好的小语法快捷方式(或“糖”),可以让您更轻松!

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

但是,它们的格式正在发生变化,使用 rails 4 时,在不传递可调用对象的情况下使用 #scope 时会出现弃用警告。 例如,范围:red,where(color: 'red') 应更改为 {1 }。

附带说明一下,如果使用不当,default_scope 可能会被误用/滥用。
这主要是关于它何时用于诸如 where 的限制(过滤)default 选择(默认的坏主意),而不仅仅是用于排序结果。
对于 where 选择,只需使用常规命名范围。并在查询中添加该范围,例如 Book.all.published 其中 published 是命名范围。

总之,范围真的很棒,可以帮助您将事物推入模型中,以实现“胖模型瘦控制器”DRYer 方法。


顺便说一句,有没有办法将默认范围引用为排序顺序?像Book.order(:default_scope)
default_scope { order("#{table_name}.created_at DESC") } 不是更安全吗?
导轨 4:default_scope { order(created_at: :desc) }
至少 4.2.6 似乎按 updated_at 而不是 created_at 排序。
@AinTohvri 是对的。这让我在 Rails 4.2 中感到惊讶。为什么默认按 updated_at 排序? :-|
C
Community

快速更新迈克尔上面的出色答案。

对于 Rails 4.0+,您需要将排序放在这样的块中:

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

请注意,订单语句放置在花括号表示的块中。

他们改变了它,因为它太容易传递动态的东西(比如当前时间)。这消除了问题,因为该块是在运行时评估的。如果您不使用块,您将收到此错误:

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

正如 @Dan 在下面的评论中提到的那样,您可以执行如下更红的语法:

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

或多列:

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

谢谢@Dan


在 rails 4 中,如果您像我一样尝试最小化 rails 中的 sql 语法,这也可以写成 default_scope { order(created_at: :desc) }。<br/>如果您有多个列要排序并且您想使用新语法,您可以需要像这样 default_scope { order({begin_date: :desc}, :name) } 将 desc 列包裹在胡须中
@Dan - 您的评论不仅消除了 SQL,而且是一种更加 Rubyish 的语法。
C
Chris Aitchison

您可以使用 default_scope 来实现默认排序顺序 http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html


该链接有效,但该页面上没有关于 default_scope 的任何内容,因为它已从 ActiveRecord::Base 重构为 ActiveRecord::Scoping::Default::ClassMethods (api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/…)
j
jamesc

接受的答案曾经是正确的 - 默认范围是这样做的唯一方法。但是,默认范围实际上是链接的(范围本身应用于任何进一步的范围/调用),这可能会导致一些 unpredictable behavior。因此,默认范围被广泛认为是不惜一切代价避免的。

请务必注意,鉴于您的原始问题,默认范围实际上并相当满足这一点,因为为了实际跳过默认范围,任何模型都必须首先明确指定 .unscoped。这意味着如果调用 order(:something) 而没有 .unscoped,则结果实际上更类似于 order(:default).order(:something):something 优先,当然,但 :default 仍然存在,所以现在有一个多列排序,这可能不是我们想要的。原始问题指定如果调用另一个顺序,则忽略默认排序顺序,并且没有 .unscoped 的默认范围不满足该要求。

Since 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(或使用的任何列)没有键。这意味着选择会稍微减慢执行此操作的速度,至少在将键添加到该列以解决此问题之前。

最后,implicit_order_column 的功能有限 - 甚至无法设置排序顺序,更不用说执行更复杂的事情,如多列排序。如果需要该功能,default_scope 仍然是唯一可行的方法。