rails模型的默认排序顺序?

Jus*_*ner 247 ruby-on-rails

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

因此,当我在.where()没有指定.order()的情况下执行时,它使用默认排序.但是如果我指定了一个.order(),它会覆盖默认值.

Mic*_*ant 529

default_scope

这适用于Rails 4+:

class Book < ActiveRecord::Base
  default_scope { order(created_at: :desc) }
end
Run Code Online (Sandbox Code Playgroud)

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

default_scope order('created_at DESC')
Run Code Online (Sandbox Code Playgroud)

对于Rails 2.x:

default_scope :order => 'created_at DESC'
Run Code Online (Sandbox Code Playgroud)

created_at您希望在哪个字段上进行默认排序.

注意:ASC是用于升序的代码,DESC用于降序(desc,不是 dsc!).

scope

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

class Book < ActiveRecord::Base
  scope :confirmed, :conditions => { :confirmed => true }
  scope :published, :conditions => { :published => true }
end
Run Code Online (Sandbox Code Playgroud)

对于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.
Run Code Online (Sandbox Code Playgroud)

最后,您可以使用以下命令禁用默

Book.with_exclusive_scope { find(:all) } 
Run Code Online (Sandbox Code Playgroud)

甚至更好:

Book.unscoped.all
Run Code Online (Sandbox Code Playgroud)

这将禁用任何过滤器(条件)或排序(排序依据).

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


所以 ...如果你在想,嗯,所以这些就像方法那样......,是的,这正是这些范围的本质!
他们就像def self.method_name ...code... end拥有红宝石一样,但它们是很好的小语法快捷键(或"糖"),让你的事情变得更轻松!

事实上,它们是类级方法,因为它们在1组"所有"记录上运行.

但是它们的格式正在改变,使用rails 4时,在使用#scope而不传递可调用对象时会出现弃用警告.例如范围:红色,其中(颜色:'红色')应更改为scope :red, -> { where(color: 'red') }.

作为旁注,如果使用不当,默认的 _scope可能会被滥用/滥用.
这主要是关于什么时候它被用于where限制(过滤)默认选择(默认坏主意)等操作,而不仅仅是用于排序结果.
对于where选择,只需使用常规命名范围.并在查询中添加该范围,例如Book.all.published,在哪里published是命名范围.

总之,范围非常好,可以帮助您将模型推向"胖模型瘦控制器"DRYer方法.

  • Rails 4:`default_scope {order(created_at :: desc)}` (36认同)
  • 迈克尔,这是关于这个主题的最好的写作,我发现任何地方...荣誉! (10认同)
  • `default_scope {order("#{table_name} .created_at DESC")}`是不是更安全? (3认同)
  • 至少`4.2.6`似乎按`updated_at`而不是`created_at`排序. (2认同)
  • @AinTohvri是对的.这让我在Rails 4.2中大吃一惊.为什么默认按`updated_at`排序?: - | (2认同)

Pau*_*ver 109

迈克尔上面的优秀答案快速更新.

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

class Book < ActiveRecord::Base
  default_scope { order('created_at DESC') }
end
Run Code Online (Sandbox Code Playgroud)

请注意,order语句放在由大括号表示的块中.

他们改变了它,因为它太容易传递动态的东西(比如当前时间).这样可以解决问题,因为在运行时会计算块.如果您不使用块,您将收到此错误:

删除了对没有阻止调用#default_scope的支持.例如default_scope where(color: 'red'),请使用default_scope { where(color: 'red') }.(或者你可以重新定义self.default_scope.)

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

class Book < ActiveRecord::Base
  default_scope { order(created_at: :desc) }
end
Run Code Online (Sandbox Code Playgroud)

或多列:

class Book < ActiveRecord::Base
  default_scope { order({begin_date: :desc}, :name) }
end
Run Code Online (Sandbox Code Playgroud)

谢谢@Dan!

  • 在rails 4中,这也可以写成`default_scope {order(created_at :: desc)}`,如果像我一样,你试着最小化rails中的sql语法.<br/>如果你有多列要排序,你想要要使用新的语法,你可能需要将desc列包装在诸如`default_scope {order({begin_date :: desc},:name)}之类的胡须中. (26认同)
  • 这比接受的答案要清楚得多. (2认同)
  • @Dan - 您的评论不仅消除了 SQL,而且是一种更像 Rubyish 的语法。 (2认同)

msc*_*ltz 16

接受的答案曾经是正确的 - 默认范围是执行此操作的唯一方法。然而,默认作用域实际上是链接的(作用域本身应用于任何进一步的作用域/调用),这可能会导致一些不可预测的行为。因此,默认范围被广泛认为是不惜一切代价避免的事情。

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

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

class Book < ApplicationRecord
  self.implicit_order_column = 'publish_date'
end
Run Code Online (Sandbox Code Playgroud)

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

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

class ApplicationRecord < ActiveRecord::Base
  self.implicit_order_column = 'created_at'
end
Run Code Online (Sandbox Code Playgroud)

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

还值得注意的是created_at(或使用任何列)默认情况下没有键。这意味着选择将稍微减慢执行此操作的速度,至少直到将键添加到该列来解决此问题为止。

最后,implicit_order_column功能有限 - 甚至无法设置排序顺序,更不用说执行多列排序等更复杂的操作了。如果需要该功能,default_scope这仍然是唯一的选择。


Slo*_*vic 6

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

  • 该链接正在运行,但该页面上没有关于`default_scope`的内容,因为它已从`ActiveRecord :: Base`重构为`ActiveRecord :: Scoping :: Default :: ClassMethods`(http://api.rubyonrails.组织/类/ ActiveRecord的/确定范围/默认/ ClassMethods.html) (6认同)