如果记录需要按特定顺序排列,如何编写Rails关联排序?

How to write a Rails association sort if the records need to be in a specific sequence?

我有一个类似这样的系统:

class Tag < ActiveRecord::Base
  # attr :name
end

class Article < ActiveRecord::Base
  has_many :tag_associations, dependent:  :destroy
  has_many :tags, through: :tag_associations
end

目前系统中只有5个标签:

'Foo'
'Bar'
'Baz'
'Hello'
'World'

我希望 article.tags 上的关联标签在通过关联从数据库中查询时完全按照这个顺序排列,而不管它们实际上以什么顺序插入到关联中。 Rails 如何做到这一点?

因此,例如,如果我们有 [ baz, hello, foo ],当我们 article.tags 时,我们应该按 [ foo, baz, hello ] 的顺序获取它们。如果我必须调用一个额外的方法或执行某种范围,这没问题,只是想知道进行这种排序的正确方法。

如果可以接受按Tag#id或Tag#排序created_at,则只需在Tag模型中设置默认范围即可:

# in tag.rb

default_scope { order(:id) } # or :created_at, or :name

但如果您需要一些特殊的顺序,则必须添加一个 rank 属性来设置您需要的顺序。

编辑:如果您使用 mySQL(在 Postgresql 上不可用)

,这是另一种方法
# in tag.rb
SORT_ORDER = ['Foo', 'Bar', 'Baz', 'Hello', 'World']

default_scope { order(Arel.sql("find_in_set(name,'#{SORT_ORDER.join(',')}')")) }

您可以将此列表作为常量添加到您的 Article 模型中并动态生成查询:

class Article < ActiveRecord::Base
  TAGS_ORDER = ['Foo', 'Bar', 'Baz', 'Hello', 'World'].freeze

  has_many :tag_associations, dependent:  :destroy
  has_many :tags, -> { order(Article.tags_order_query) }, through: :tag_associations

  def tags_order_query
    return "id" if TAGS_ORDER.blank?

    query = "CASE name"
    query += TAGS_ORDER.each_with_index.map{ |t,i| " WHEN '#{t}' THEN #{i}"}.join
    query += " ELSE #{TAGS_ORDER.length} END, id"
    # generated query will be "CASE name WHEN 'Foo' THEN 0 WHEN 'Bar' THEN 1 WHEN 'Baz' THEN 2 WHEN 'Hello' THEN 3 WHEN 'World' THEN 4 ELSE 5 END, id"
  end
end