Rails ActiveRecord Enum 实战总结

grantbb · March 07, 2017 · Last by StephenZzz replied at May 30, 2019 · 4933 hits
Topic has been selected as the excellent topic by the admin.

基本使用方法

案例说明:给已经存在的 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 限制。

huacnlee mark as excellent topic. 07 Mar 14:30

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

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

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

Reply to alixiaomiao

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

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

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

Reply to nowherekai

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

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

Reply to vincent

同意

Reply to vincent

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

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

15 Floor has deleted
You need to Sign in before reply, if you don't have an account, please Sign up first.