简体   繁体   English

Rails:记录的自定义排序

[英]Rails: Custom ordering of records

Does ruby or rails provide a method to order strings in a specified order? ruby 或 rails 是否提供按指定顺序对字符串进行排序的方法? Say that I have the following priorities "Severe, High, Medium, Low".假设我有以下优先级“严重、高、中、低”。

These priorities are not going to change frequently (if at all).这些优先级不会经常改变(如果有的话)。 I have a Task model with a priority column:我有一个带有优先级列的任务 model:

tasks
  - id (integer)
  - name (string)
  - priority (string)

I'd like to get an array of all the tasks ordered by priority.我想获得按优先级排序的所有任务的数组。 Since the logical order does not follow alphabetical order, it's not possible to simply order by the priority column:由于逻辑顺序不遵循字母顺序,因此不可能简单地按优先级列排序:

Task.all(:order => :priority)

What I've done is create a Priority model and defined the associations: Task belongs_to Priority.我所做的是创建一个优先级 model 并定义关联:Task belongs_to Priority。 In the priorities table, I then assigned each priority name a value and ordered by that value.在优先级表中,我为每个优先级名称分配了一个值并按该值排序。 Is there a better way to do this?有一个更好的方法吗? I'd rather not have a priorities table at all and declare a PRIORITY constant (as a hash), or simply specify the priority as a string in the tasks table.我宁愿根本没有优先级表并声明一个优先级常量(作为哈希),或者只是将优先级指定为任务表中的字符串。

You could use a case statement in your where clause. 您可以在where子句中使用case语句。 It's a little ugly but should work: 这有点难看,但应该工作:

class Task
  PRIORITIES_ORDERED = ['high', 'medium', 'low']

  # Returns a case statement for ordering by a particular set of strings
  # Note that the SQL is built by hand and therefore injection is possible,
  # however since we're declaring the priorities in a constant above it's
  # safe.
  def self.order_by_case
    ret = "CASE"
    PRIORITIES_ORDERED.each_with_index do |p, i|
      ret << " WHEN priority = '#{p}' THEN #{i}"
    end
    ret << " END"
  end

  named_scope :by_priority, :order => order_by_case

end

And then when you want them ordered by priority, you can do something like this: 然后,当您希望按优先级排序时,您可以执行以下操作:

Task.all.by_priority

Of course as other people have mentioned, it's cleaner to make a foreign key to a Priority table instead of a text string column. 当然,正如其他人所提到的,为Priority表而不是文本字符串列创建外键更为清晰。 In the Priorty table, you could add a position column as an integer that you could sort by, and plug-ins exist to make this easier. 在Priorty表中,您可以将位置列添加为可以排序的整数,并且存在插件以使其更容易。

Here's up updated version that works for rails 4.1 and a little customized: 这是更新版本,适用于rails 4.1和一些自定义:

  PRIORITIES_ORDERED = ['Unknown', 'Critical', 'Warning', 'Ok']
  def self.order_by_case
    ret = "CASE"
    PRIORITIES_ORDERED.each_with_index do |p, i|
      ret << " WHEN status = '#{p}' THEN #{i}"
    end
    ret << " END"
  end

  scope :order_by_priority, -> { order(order_by_case) }

I would use integers in db. 我会在db中使用整数。 It's easier to sort and index, then override the attribute getter and setter to use symbols to interface externally. 排序和索引更容易,然后覆盖属性getter和setter以使用符号进行外部接口。

Switch to a integer like Aaron said, then you will probably want to use a default_scope . 切换到像Aaron这样的整数,然后你可能想要使用default_scope

For example: 例如:

class Task < ActiveRecord::Base
  default_scope :order => 'tasks.position' # assuming the column name is position
  ...

With a default scope you won't have to specify the sort order in any of your find method calls - the tasks will automatically be sorted. 使用默认范围,您不必在任何find方法调用中指定排序顺序 - 任务将自动排序。

If changing your schema to use an integer priority isn't an option, you can also define a custom <=> method on Task to define the sort order. 如果不能选择更改模式以使用整数优先级,则还可以在“任务”上定义自定义<=>方法以定义排序顺序。

class Task

  PRIORITIES = {
    :high => 3,
    :med  => 2,
    :low  => 1,
  }

  def <=> (other)
    PRIORITIES[self.priority] <=> PRIORITIES[other.priority]
  end

end

The downside of doing it this way is that you'll need to do the sorting on the Ruby side, rather than let your database do it for you, which will preclude the use of default_scope . 这样做的缺点是你需要在Ruby端进行排序,而不是让你的数据库为你做,这将排除使用default_scope The upside is that any time you call sort on an Array of Tasks the order will come out correctly (or, if you only want a custom ordering in one particular place, you can use sort_by ). 好处是,无论何时在一个任务数组上调用sort ,订单都会正确显示(或者,如果您只想在某个特定位置进行自定义排序,则可以使用sort_by )。

Just to solve your specific question: You can order it by the last letter. 只是为了解决您的具体问题:您可以通过最后一封信来订购。

Sever(e), Hig(h), Mediu(m), Lo(w) Sever(e),Hig(h),Mediu(m),Lo(w)

you can either do it in rails or sql: 你可以在rails或sql中做到这一点:

order by RIGHT(priority, 1)

I choose this approach because 我选择这种方法是因为

  1. it is the easiest possible way. 这是最简单的方法。
  2. you mentioned it is not going to change. 你提到它不会改变。

the approach might not be flexible enough, but too me, I try not to generalize the problem unless it is necessary 这种方法可能不够灵活,但我也是这样,除非有必要,否则我尽量不将问题概括为一般

I would suggest using acts_as_list ( http://github.com/rails/acts_as_list/tree/master ), which will add a position field to your object but will also give you all the methods for moving things up and down the list while preserving the order correctly. 我建议使用acts_as_list( http://github.com/rails/acts_as_list/tree/master ),它会为你的对象添加一个位置字段,但也会为你提供在保留的同时在列表中上下移动的所有方法订单正确。

If the priorities are never going to change it might be better to make priority a field on the task table. 如果优先级永远不会改变,那么在任务表上优先考虑字段可能会更好。 The problem is that you then need to be able to sort by priority. 问题是您需要能够按优先级排序。 For that you have a few options, but whatever you choose it should be done by the db. 为此您有几个选项,但无论您选择什么,都应该由db完成。

Here's an updated version for Rails 7:这是 Rails 7 的更新版本:

Task.in_order_of(:priority, %w[Severe High Medium Low])

Docs and PR + discussion which introduced it介绍它的文档PR + 讨论

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM