如何在Rails中将枚举类型以字符串形式存储到数据库中

48

我该如何在Ruby中创建一个迁移(migration),使默认值为字符串而非整数?我想将枚举(enum)存储到数据库,但不希望将其存储为整数,因为这样对于另一个想使用同一张表的应用程序来说就没有意义了。我要如何使用default: "female"代替default: 0

class AddSexToUsers < ActiveRecord::Migration
  def change
    add_column :users, :sex, :integer, default: 0
  end
end
class User < ActiveRecord::Base
  enum sex: [:female, :male]
  has_secure_password
end

I


你使用的是哪个数据库? - Alexey Shein
我在开发中使用sqlite,生产环境使用mysql。 - George
3
这个老生常谈了;) - Harlan T Wood
6个回答

78
阅读enum文档,您可以看到Rails使用Array值索引的解释如下:

请注意,当使用数组时,从值到数据库整数的隐式映射是从数组中值出现的顺序派生的。

但也指出可以使用Hash

还可以使用哈希显式地映射属性和数据库整数之间的关系。

以下是示例:

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

所以我使用了 Rails 4.2.4sqlite3 进行测试,并创建了一个 User 类,其中 sex 属性的类型为 string,并在 enum 中使用了一个 Hash,其中包含了 string 值(我使用 femmal 值来区分于 femalemale):

迁移:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :sex, default: 'fem'
    end
  end
end  

型号:

class User < ActiveRecord::Base
  enum sex: { female: 'fem', male: 'mal' }
end

而在控制台中:

u = User.new
#=>  #<User id: nil, sex: "fem">
u.male?
#=> false
u.female?
#=> true
u.sex
#=> "female"
u[:sex]
#=> "fem"
u.male!
# INSERT transaction...
u.sex
#=> "male"
u[:sex]
#=> "mal"

2
数据库中存储的是 fem 吗? - Arnold Roa
@ArnoldRoa 是的,fem 是数据库中的字段,可以设置为 female - thisismydesign
2
非常好的解释和演示,附有官方文档的参考。这是一个高质量的SO答案的绝佳例子!谢谢! - Michael Gaskill

50

我通常会做以下事情:

# in the migration in db/migrate/…
def self.up
  add_column :works, :status, :string, null: false, default: 'offering'
end

# in app/models/work.rb
class Work < ApplicationRecord
  ALL_STATES = %w[canceled offering running payment rating done].freeze

  enum status: ALL_STATES.zip(ALL_STATES).to_h
end

通过使用哈希作为 enum 的参数 (参见文档),可以将字符串存储在数据库中。同时,这仍然允许您使用所有很酷的Rails辅助方法:

w = Work.new
#=>  #<Work id: nil, status: "offering">
w.rating?
#=> false
w.offering?
#=> true
w.status
#=> "offering"
w[:status]
#=> "offering"
w.done!
# INSERT transaction...
w.status
#=> "done"
w[:status]
#=> "done"

一行代码更新:

我完全忽略了自从Rails 1.2.6版本以来,我们已经有了index_by。这使得解决方案甚至成为一行代码:

enum status: %w[canceled offering running payment rating done].index_by(&:to_sym)

另外,自Rails 6.0.0起,我们还有index_with

enum status: %i[canceled offering running payment rating done].index_with(&:to_s)

似乎文档中没有任何提示将枚举值存储为字符串。你确定它是以字符串而不是整数存储的吗? - Anwar
@Anwar 是的,如果您自己尝试一下,它会存储为字符串。这可以通过上面的 w [:status] 看到,在正常的Rails枚举中将返回一个字符串。 - nitsujri
1
哦,那就是@coorasse得到“他”的想法的地方。 - Darme

20

enum在Rails中和ENUM类型在MySQL中是两码事。

  1. Rails中的enum只是一个包装器,使您能够在查询中更轻松地使用字符串而不是整数。但在数据库级别上,它都被转换为整数(由Rails自动完成),因为这是列的类型。

  2. MySQL中的ENUM类型是供应商特定的列类型(例如,SQLite 不支持它,但PostgreSQL支持)。在MySQL中:

枚举是一个字符串对象,其值从在表创建时的列规范中显式枚举的允许值列表中选择。

CREATE TABLE shirts (
    name VARCHAR(40),
    size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
);
INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'),
  ('polo shirt','small');
SELECT name, size FROM shirts WHERE size = 'medium';
+---------+--------+
| name    | size   |
+---------+--------+
| t-shirt | medium |
+---------+--------+

对于迁移,你需要这样做:

class AddSexToUsers < ActiveRecord::Migration
  def change
    add_column :users, :sex, "ENUM('female', 'male') DEFAULT 'female'"
  end
end

4
如果您选择这种方法,您需要切换到 SQL 模式进行架构转储,否则 Rails 将在您的 schema.rb 文件中放置一个字符串列类型。请在 config/application.rb 中使用以下代码进行切换:config.active_record.schema_format = :sql - Andrew Smith
关于“你需要转换为 SQL schema dumps”的评论仍然准确吗?我有一个使用MySQL(mysql gem v0.5.3)的Rails 6.0.3.3项目,在迁移中使用t.column :foo, "enum('bar')"会在schema.rb中生成t.column "foo", "enum('bar')" ,而不是字符串列。 - Jordan Brough

2

0

有步骤将枚举类型添加为字符串到模型Company

bin/rails g migration AddStatusToCompanies status

class AddStatusToCompanies < ActiveRecord::Migration[7.0]
  def change
    add_column :companies, :status, :string, null: false, default: 'claimed'
    add_index  :companies, :status
  end
end

bin/rails db:migrate
  • 值为字符串(符号无效)
  • 添加默认值
  • 添加前缀
enum status: {
  claimed: 'claimed',
  unverified: 'unverified',
  verified: 'verified',
}, default: 'claimed'
  • 添加验证(否则将引发 SQL 异常)
validates :status, inclusion: { in: statuses.keys }, allow_nil: true

-1
据我所知,使用标准的Rails枚举是不可能的。可以看一下https://github.com/lwe/simple_enum,它更加功能丰富,还允许将枚举值以字符串形式存储到数据库中(列类型为字符串,在数据库术语中为varchar)。

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