Rails ActiveRecord Enum 实战总结

grantbb · 发布于 2017年03月07日 · 最后由 liuyang_1991 回复于 2017年06月29日 · 2127 次阅读
4594
本帖已被设为精华帖!

基本使用方法

案例说明: 给已经存在的 Company 增加一个 size 属性, 属性包括 large, medium, small 三个选项

从 Rails4.1 开始,可以通过 ActiveRecord::Enum 来快速实现这样的功能

class Company < ActiveRecord::Base
  enum size: [:large, :medium, :small]
end

定义了 enum 后,Rails会自动产生下面这些方法

Company.sizes
# => {"large"=>0, "medium"=>1, "small"=>2}

# Scope methods
Company.large
Company.medium
Company.small

# Query methods
company.large?
company.medium?

# Action methods
company.large!
company.small!

Migration

enum 的实现是基于该字段是一个 integer 类型的。所以添加这个新的字段我们需要下面的 migration

class AddSizeToCompanies < ActiveRecord::Migration
  def change
    add_column :companies, :size, :integer, null: false, default: 0
    add_index  :companies, :size
  end
end

赋值操作

通过上面 Company.sizes 方法,我们看到 Rails 默认是从0开始对应的。所以上面的 migration 中默认值是0。当新建一个Company时,默认 company 的 size 是 large

company = Company.new
company.size # => "large"

# 多种赋值操作
company.size = :medium
company.medium? # => true

company.size = 2
company.small? # => true

company.large = 'large'
company.large? # => true

enum 会自动添加一些验证,如果给 size 属性赋错误的值,Rails会抛出异常

company.size = 5
# => ArgumentError: 5 is not a valid billing_category
company.size = :bala
# => ArgumentError: 'bala' is not a valid billing_category

Form下拉菜单的填充

当在前端的Form中填充到下拉列表中,可以这样做

f.input :size, collection: Company.sizes.keys.map {|s| [s.titleize, s]}, prompt: "Select a size"

需要注意的地方:

由于存到数据库的仍然是 number,所以如果有别的应用也使用同样的数据库,那么该应用需要知道对应关系。

Rails4.1 及之后的版本中,由于 enum 会自动产生一些方法,所以要特别注意选项的命名问题,尽量用明确的命名。 另外如果你还需要不同的属性,拥有相同的选项,那么你可以考虑这个 gem activerecord-enum-without-methods

同时在 Rails5 中,就可以使用_prefix_postfix选项来避免相应的问题。·

更好的Migration方法

上面的migration方法对于产品环境已经有数据的情况下,可能会产生问题。

对于Mysql 数据库,新加字段的时候对于已有的记录,mysql 会自动设置 NOT NULL 的已有记录为 default 但是对于 postgreSQL 就会出现下面的错误:

PG::NotNullViolation: ERROR: column "size" contains null values

这时可以先add_column添加字段,不要加 NOT NULL 的限制,然后更新已有数据,然后再通过change_column_null来添加 NOT NULL 限制。

共收到 12 条回复
De6df3 huacnlee 将本帖设为了精华贴 03月07日 14:30
12706

为啥不用字符串呢,比integer在数据库里直观一点,难道是为了索引速度?

23224

哎呀 亲 我也写了一个 可惜没有你这么详细

3962

rails 5 可以 Company.where(size: :large)

4594
12706alixiaomiao 回复

恩,索引速度和查询速度是一个考虑

4594
3962nowherekai 回复

对的

96

一直用的gem 'enumerize' 原来rails自己就带了这个功能了,刚知道

19780

对于enum的migration会报错的情况, 暂时木有遇到, 不过灰常感谢这个提示. ps: enum真的hash好用太多.

28759
3962nowherekai 回复

Company.large 就可以实现你的where查询啦,但是文章里也受教啦!

332

更喜欢使用 enumerize gem,而不是 rails 的这个特性,原因是 enum 自动生成的方法太容易引起命名冲突了,一不小心就会中招。

15
332vincent 回复

同意

2329
332vincent 回复

5.0开始就支持前缀和后缀了。就解决了你这个顾虑啦。

96

我今天用的enum总是显示 '0' is vaild ! 一定要自己转成integer么?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册