4

I have a table which contains an integer based column (status) which I'm using for an enum attribute within the Rails model.

At the moment of doing:

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

Defined as:

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

It returns everything as expected, but the status column is returned in its string value as inactive, active, etc. But I need it as an integer.

How can I get that?

I'm currently just using ActiveRecord::Base.connection.execute and passing a raw query:

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

7 Answers 7

5

Have you tried this?

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

# Rails >= 5
post = Post.find(123)
post.read_attribute_before_type_cast(:status)
Sign up to request clarification or add additional context in comments.

2 Comments

No, I haven't, because I'm not trying to access a particular row, but a "collection" of records. In that way I'd have to map the result of the query and invoke post.read_attribute_before_type_cast in every element.
Maybe I didn't explain myself well, but I need the query to return the numeric value of status, not its string value (the value in the enum definition, not the key).
4
+200

It won't be the most elegant way but this is the only way I see possible without looping over your objects.

You can achieve this by creating 2 classes like this:

class Post < ApplicationRecord
end

And the other one:

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

With this when you use Post class you won't get enum value for the "status" column but when you use PostSave class you can use your enum as you were already using.

Now when you do

Post.select(:id, :status)

It will give you integer values for the "status" column as you desire.

3 Comments

Thanks, that's a very interesting approach. I'm gonna give it a try.
@seb it’s basically all classes so I thought we can have it in an another way :)
I like this approach. Thanks a lot for your time :)
3

Yep. And that's simply how enum field works in Rails. Data is stored as integer, but displayed as string - based on what's defined in enum: https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/Enum.html

If you really want to get integer try one of those:

  1. Remove enum declaration. Data is stored as Integer. Without that line query will return Integer
  2. Thats not the most beautiful code but: Post.statuses[post.status] will work

Comments

3

You can give this a try:

Post.select(:id, ..., 'status as int_status')

1 Comment

This returns the status value as an integer, but also modifies the output, as it's returned as int_status. It's an approach, thanks!
3

You could map the column in the SQL statement to another field and read it from there instead, that way Rails passes the value directly as it doesn't know it's an enum (it doesn't know the column at all):

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

If you HAVE to have the same column name, then you're out of luck unless you do a little bit more work:

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

If neither of those are sufficient then this most certainly sounds like the http://xyproblem.info/ ... as these answers should work for 99% of cases. So you should probably explain WHY you cant just use status_int while working with the objects, or why you can't store the int in a variable, or... why you need access to the integer at all, which defeats the purpose of an enum.

Comments

2

I'm not sure this directly applies as I do not know Rails or your exact requirement. But you indicated the desire to "get a collection of records from the DB"; so perhaps it will.
Postgres contains 2 tables in pg_catalog you need to join to get the collection values for a enum: pg_type and pg_enum as follows (where ENUM_NAME is replaced by the appropriate enum type 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;

One difference I've notice is that in Postgres the associated numeric value is defined as Real, not as Integer. This is due to how the values are actually maintained. Example:

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;

The above gives the actual numeric values underlying the enum string value (as column name indicates it's for sorting purpose). You could get a relative integer value with

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;

I'll leave the actual conversion to Rails to you. Hope it helps.

1 Comment

Thanks Belayer. It seems this is too much (custom) code for this case, which seems to be a missing feature in the ActiveRecord core. Anyway +1 for the time you took for this. Appreciate it ;)
2

The above solutions are working. I have tried with raw sql on mysql version 5.7.28. the below query is working with mysql only.

I am still looking query for postgres, I will let you know soon.

 
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; 

3 Comments

Hey dude, please read the question again. It's postgresql tagged and I'm using Ruby on Rails (Active Record ORM), which I'd prefer for an answer. Also, don't post images, please.
Yes I know you are looking for postgresql, that's why i specifically mentioned above mysql only.
Wow what a "hack". Nice one. But unfortunately when I do Post.select('..., status + 0').as_json it returns the column name as ?column?" and when I do Post.select('..., status + 0 AS status').as_json it returns the status value corresponding to the enum value defined in the model (the value in the key-value).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.