如何在Rails 4中处理枚举值

14

我的数据库中有一个 subscription 表。

我想要添加一个 state 列,它将具有以下任一值之一

Valid
Invalid
Cancelled
In Trial
Non Renewing
Future

有人能解释一下如何在Rails 4中将这些值用作枚举值吗?

3个回答

19

致谢: https://hackhands.com/ruby-on-enums-queries-and-rails-4-1/

声明一个枚举属性,其中值映射到数据库中的整数,但可以通过名称查询。例如:

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.update! status: 1
conversation.status = "archived"

# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

基于枚举字段允许的值,将提供相应的作用域。以上述示例为例,它将创建一个活动和一个已归档的作用域。
您可以在数据库声明中设置默认值,例如:
create_table :conversations do |t|
  t.column :status, :integer, default: 0
end

最佳实践是让第一个声明的状态成为默认值。

最后,还可以使用哈希显式地映射属性与数据库整数之间的关系:

class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 1 }
end

注意,当使用数组时,从值到数据库整数的隐式映射是从数组中值出现的顺序中派生出来的。在此示例中,:active被映射为0,因为它是第一个元素,而:archived则被映射为1。通常,第i个元素被映射到数据库中的i-1位置。
因此,一旦将值添加到枚举数组中,其在数组中的位置必须保持,并且新值只应添加到数组的末尾。要删除未使用的值,应使用显式哈希语法。
在极少数情况下,您可能需要直接访问映射。这些映射通过具有复数属性名称的类方法公开:
Conversation.statuses # => { "active" => 0, "archived" => 1 }

当你需要知道枚举的序数值时,请使用该类方法:

Conversation.where("status <> ?", Conversation.statuses[:archived])

枚举属性的条件查询必须使用枚举的序号值。

更多信息请参考:http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html


3
很遗憾,这只适用于Rails4.1。目前我不想升级我的应用程序到Rails 4.1。 - Omer Aslam
@cevaris,我最初是从Rails文档页面上复制了那个答案,而不是从你提到的链接。希望Rails是从你提到的源中复制了代码。 - PrivateUser

3
你可以使用名为 Workflow 的宝石。它使您能够使用自定义状态并优雅地处理状态之间的转换。我在许多Rails3和Rails 4应用程序中都使用了它。
以下是文档中的示例:
class Article
  include Workflow
  workflow do
    state :new do
      event :submit, :transitions_to => :awaiting_review
    end
    state :awaiting_review do
      event :review, :transitions_to => :being_reviewed
    end
    state :being_reviewed do
      event :accept, :transitions_to => :accepted
      event :reject, :transitions_to => :rejected
    end
    state :accepted
    state :rejected
  end
end

之后:

article = Article.new
article.accepted? # => false
article.new? # => true

2
编辑: 我的回答不使用ActiveRecord魔术枚举。我建议下面的方法适用于任何数据库库(如Sequel),而且我认为它更加健壮。例如,它不依赖于值的自动排序,并且使用RDBMS中的外键约束来确保您的值有效(不像ActiveRecord解决方案)。但是,它不是Rails惯用的方法。请参见Giri的答案(文档页面的复制/粘贴)以获得惯用的答案。
  1. Create a states table in your database with these string values populated along with a unique ID, e.g.

    states
    id | name 
    ---+--------------
     1 | Valid
     2 | Invalid
     3 | Cancelled
     4 | In Trial
     5 | Non Renewing
     6 | Future
    
  2. Use a standard foreign-key association in your subscription table referencing an entry from states.

  3. In your models, create a State class with constants matching the IDs, e.g.:

    class State < ActiveRecord::Base
      VALID   = 1
      INVALID = 2
      # etc.
    end
    
现在,您可以得到以下保证:a) 您的表格数据是有效的;b) 您可以使用方便的引用,例如请求subscriptions
Subscription.where( state_id: State::VALID )

这不是Rails 4处理枚举的方式。你的答案并没有错,但它不是op问题所需的Rails 4方式...请查看http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html - rizidoro
1
@rizidoro 谢谢你提供的信息。我个人认为 Rails 的方式不太理想,因为它依赖于在 Ruby 中强制执行引用完整性而不是数据库。Giri:请将已接受答案切换为您复制/粘贴的 Rails 文档。 - Phrogz
我不喜欢将ID硬编码为常量的想法。这会将您的代码与数据库中的数据耦合在一起。我以前做过这样的事情,当我不得不将应用程序部署到多个环境(单独的数据库)或编写自动化测试时,我非常讨厌它...此外,我会避免使用整数作为状态,因为当您必须编写自定义查询或扩展并且无法回忆出哪个数字表示什么时,您会发现它非常反直觉,除非您查看代码。 - Yurui Zhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接