如果记录需要按特定顺序排列,如何编写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
我有一个类似这样的系统:
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