Rails Enum返回整数值,而不是字符串表示形式

时间:2019-11-24 21:10:25

标签: ruby-on-rails postgresql ruby-on-rails-6

我有一个表,其中包含一个基于整数的列(状态),我将其用于Rails模型中的枚举属性。

此刻:

Post.select(:id, ..., :status)

定义为:

enum status: { inactive: 0, active: 1, ... }

它按预期返回所有内容,但是status列的字符串值返回为 inactive active 等。但是我需要它作为整数。

我该怎么办?

我目前仅使用ActiveRecord::Base.connection.execute并传递原始查询:

ActiveRecord::Base.connection.execute('select id, ..., status from posts')

8 个答案:

答案 0 :(得分:3)

这不是最优雅的方法,但这是我认为不循环对象的唯一方法。

您可以通过创建2个这样的类来实现此目的:

class Post < ApplicationRecord
end

另一个:

class PostSave < Post
  enum status: { inactive: 0, active: 1, ... }
end

使用此方法,当您使用Post类时,不会获得“状态”列的enum值,但是当您使用PostSave类时,可以使用enum因为您已经在使用。

现在,当您完成

Post.select(:id, :status)

它将根据需要为您提供“状态”列的整数值。

答案 1 :(得分:2)

是的。这就是枚举字段在Rails中的工作方式。数据存储为整数,但显示为字符串-基于枚举中的定义: https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/Enum.html

如果您真的想获取整数,请尝试以下方法之一:

  1. 删除枚举声明。数据存储为整数。没有该行,查询将返回Integer
  2. 那不是最漂亮的代码,但是:Post.statuses[post.status]可以工作

答案 2 :(得分:2)

您尝试过吗?

# Rails < 5
post = Post.find(123)
post.read_attribute(:status)

# Rails >= 5
post = Post.find(123)
post.read_attribute_before_type_cast(:status)

答案 3 :(得分:2)

以上解决方案有效。我尝试在MySQL 5.7.28版上使用原始sql。 以下查询仅适用于mysql。

我仍在寻找postgres的查询,我会尽快通知您。

 
create table Posts(id integer, title varchar(100), status ENUM ('active', 'inactive') NOT NULL);
insert into Posts(id, title, status) values(1, "Hello", 'inactive')
select title, status+0 from Posts; 

答案 4 :(得分:2)

您可以尝试一下:

services.AddMvc().AddJsonOptions(opts =>
{
    opts.JsonSerializerOptions.Converters.Add(new StringEnumConverter());
})

答案 5 :(得分:2)

我不确定这是否直接适用,因为我不知道Rails或您的确切要求。但是您表示希望“从数据库中获取记录的集合”。所以也许会。
Postgres在pg_catalog中包含2个表,您需要联接以获得一个枚举的集合值:pg_type和pg_enum,如下所示(其中ENUM_NAME被适当的枚举类型名称代替:

select t.typname,e.enumsortorder, e.enumlabel
  from pg_type t 
  join pg_enum e 
    on (t.oid = e.enumtypid)
 where t.typname = 'ENUM_NAME'    
 order by e.enumsortorder;

我注意到的一个区别是在Postgres中,关联的数字值定义为Real,而不是Integer。这是由于值的实际维护方式。示例:

create type t_ord_stat as enum ('Ordered', 'Delivered', 'Billed', 'Complete', 'Canceled');

select e.enumsortorder, e.enumlabel
  from pg_type t 
  join pg_enum e 
    on (t.oid = e.enumtypid)
 where t.typname = 't_ord_stat'    
 order by e.enumsortorder;


alter type t_ord_stat add value 'Suppended: Out-of-Stock' after 'Ordered';
alter type t_ord_stat add value 'Suppended: Credit Check' before 'Suppended: Out-of-Stock' ; 
alter type t_ord_stat add value 'Suppended: Customer Refused Delivery' before 'Billed'; 

select e.enumsortorder, e.enumlabel
  from pg_type t 
  join pg_enum e 
    on (t.oid = e.enumtypid)
 where t.typname = 't_ord_stat'    
 order by e.enumsortorder;

上面给出了枚举字符串值下面的实际数字值(因为列名表明它是用于排序的)。您可以使用

获得一个相对整数值
select row_number() over() relative_seq, en.enumlabel
  from (select e.enumsortorder, e.enumlabel
  from pg_type t 
  join pg_enum e 
    on (t.oid = e.enumtypid)
 where t.typname = 't_ord_stat'    
 order by e.enumsortorder) en;

我将把实际转换为Rails留给您。希望能帮助到你。

答案 6 :(得分:2)

您可以将SQL语句中的列映射到另一个字段,然后从那里读取它,这样,Rails便会直接传递值,因为它根本不知道它是枚举(它根本不知道该列):

Post.select('id, title, status, status AS status_int').each do |post|
  puts post.status     # 'inactive'
  puts post.status_int # 0
end

如果您必须使用相同的列名,那么除非您做更多的工作,否则您将很不走运。

class Post
  STATUSES = %w(inactive active)
end

Post.all.each do |post|
  index = Post::STATUSES.index(post.status)
  # use 'index' here instead of `post.status`
end

如果这些都不足够,那么这听起来肯定像http://xyproblem.info/ ...,因为这些答案在99%的情况下都有效。因此,您应该解释一下为什么在处理对象时不能仅使用status_int,或者为什么不能将int存储在变量中,或者...为什么根本需要访问整数,这会失败枚举的目的。

答案 7 :(得分:0)

我只是使用enum来避免在对posts表执行新的插入操作时两次击中数据库。我知道我可以(或必须)通过附加表来保持状态。

现在,由于Rails没有提供任何选项来仅返回枚举列的“普通”值(它返回其字符串表示形式),因此我将为status创建一个新表并添加所需的关系。创建每条记录时,它会多查询一个查询,但会返回我需要的内容。

根据将状态列强制转换为status::integer的解决方案,它需要为属性添加别名,该别名不能再次为status(这意味着它必须为status1statuses等)。对于我的情况而言,这并不是最佳选择,因为我必须更新使用已经使用状态编写的端点的每个应用程序。

简单地说,只需从posts表中删除该列以及模型中的任何引用即可。添加一个新模型,并向所需的表创建所需的外键。如果需要,还可以建立模型关系。