基于枚举值的自定义顺序

Fel*_*ger 10 enums activerecord ruby-on-rails rails-activerecord ruby-on-rails-5

我为角色定义了Enum:

enum role: {ordinary: 0, manager: 1, admin: 2}
Run Code Online (Sandbox Code Playgroud)

我想按以下顺序订购一组对象:

admin (first all admins)
ordinary (then all ordinaries)
manager (and lastly all managers)
Run Code Online (Sandbox Code Playgroud)

这有可能吗?

Hie*_*ham 12

解决方案:

class YourModel < ActiveRecord::Base
  ROLE_ORDERS = [2, 0, 1]

  scope :order_by_role, -> {
    order_by = ['CASE']
    ROLE_ORDERS.each_with_index do |role, index|
      order_by << "WHEN role=#{role} THEN #{index}"
    end
    order_by << 'END'
    order(order_by.join(' '))
  }
end
Run Code Online (Sandbox Code Playgroud)

那么你的查询将很简单:

YourModel.order_by_role
Run Code Online (Sandbox Code Playgroud)

生成的查询是:

SELECT * from your_models
ORDER BY ( CASE
           WHEN role=2 THEN 0
           WHEN role=0 THEN 1
           WHEN role=1 then 2
           END
         )
Run Code Online (Sandbox Code Playgroud)

从很好的参考这个

  • 这非常棒.看起来很丑陋,但很好的解决方案IMO到一个非常棘手的问题. (2认同)

Mar*_*n13 6

ActiveRecord::QueryMethods#in_order_of (Rails 7+)


Rails 7开始,有一个新方法ActiveRecord::QueryMethods#in_order_of

引用自 Rails 官方文档:

in_order_of(列,值)

允许通过一组特定值指定顺序。根据您的适配器,这将使用 CASE 语句或内置函数。

User.in_order_of(:id, [1, 5, 3])
# SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
Run Code Online (Sandbox Code Playgroud)

它也适用于模型枚举:

User.in_order_of(:id, [1, 5, 3])
# SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
Run Code Online (Sandbox Code Playgroud)

资料来源:

  • 这是个好消息!我一直希望Rails能够包含这样的查询方法 (2认同)

小智 5

从 Rails 6.0 开始,@hieu-pham 的解决方案将抛出此弃用警告:“使用非属性参数调用的危险查询方法(其参数用作原始 SQL 的方法)[..] Rails 6.1。不应使用用户提供的值调用此方法,例如请求参数或模型属性。已知安全值可以通过将它们包装在 Arel.sql() 中来传递。”

因此,根据他的回答和@fellow-stranger 的回答,我建议:

class YourModel < ActiveRecord::Base
  ROLE_ORDERS = [2, 0, 1]
  scope :order_by_role, -> { order(Arel.sql(ROLE_ORDERS.map{ |role| "role=#{role} DESC" }.join(', '))) }
end
Run Code Online (Sandbox Code Playgroud)

然后在您的代码中使用它,就像在@hieu-pham 的解决方案中一样......

YourModel.order_by_role
Run Code Online (Sandbox Code Playgroud)

...生成此查询:

SELECT * from your_models
ORDER BY role = 2 DESC, role = 0 DESC, role = 1 DESC
Run Code Online (Sandbox Code Playgroud)