rails - apply default value for enum field
Asked Answered
S

5

21

I want to have a default value for my enum field to prevent it from being nil. I did the following:

# db/schema.rb
create_table "templates", force: :cascade do |t|
  t.integer  "status"
end

# app/models/template.rb
class Template < ActiveRecord::Base
  STATUSES = [:draft, :published]
  enum status: STATUSES
  after_initialize :init

  def init
    self.status ||= STATUSES.index(:draft)
  end
end

I get the expected results in my local environment. But not quite in heroku. I need it to be the default value draft after updating status to nil, but in this context it becomes nil and yet the published scope still includes the updated row.

$ heroku run rails console

> Template.published.pluck :id
=> [1, 2]

> Template.find(1).update(status:nil)
Template Load (4.4ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 LIMIT 1  [["id", 1]]
Template Load (4.4ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 LIMIT 1  [["id", 1]]
(1.7ms)  BEGIN
(1.7ms)  BEGIN
(1.1ms)  COMMIT
(1.1ms)  COMMIT
=> true

> Template.find(1).status
=> nil

> Template.published.pluck :id
=> [1, 2]

Is this the correct use case to use an enum? Is there a peculiarity with my heroku environment that I'm missing out?

Scruple answered 31/1, 2016 at 9:11 Comment(4)
have you considered doing it directly from the database level, by providing a default integer value ( corespondent to the value of :draft ) for the status column in your migration? something like: t.integer :status, default: 1? You probably will have to do a change_column equivalent of that.Monotheism
@SunnyK I thought of that, but I placed it in the model cos the index number depends on the arrangement of the enum array elements, which is in the model. was thinking that this will make it easier in the future to change the enum values cos no need to create migration to change index of default value. but now that you say it, it appears to me that it will be easier to just agree that the element at 0 is default for all cases. thanks!Scruple
@SunnyK I changed it to that, but I still have the same problem.Scruple
on local or after pushing to Heroku? if after pushing to Heroku, try to run migrations on Heroku again manuallyMonotheism
B
25

You can set the default value from the database declaration.

create_table :templates do |t|
  t.column :status, :integer, default: 0
end

And then, map the relationship as following

class Template < ActiveRecord::Base
  enum status: { draft: 0, published: 1 }
end
Blank answered 31/1, 2016 at 9:17 Comment(6)
I used this approach but I still have the issue. @Blank ChoiScruple
Already worked. But I changed my approach since there is possibility for the enum array to change. Sting will work better for my case instead of enum.Scruple
@MartinVerdejo Did you ever figure out what was going wrong with heroku? I just tried enumerable on my app and I am getting some weird behavior from heroku. Even though I have default: 0 in the database if I run u = User.new in the attributes I see role="guest" as a string. Then running u.role results in nil. Not sure what's going on.Teutonic
That's one option, what it changing database is not an option?Dutcher
How would you achieve this without writing a new migration?Shushubert
Just a note. If you use the string version of the enum on the database migration, it won't save properly. You have to use the integer value. So default: 0 instead of default: 'draft'.Mccarthy
B
36

Rails 6.1+

Starting from Rails 6.1, it is possible to set a default enum value right in the model. For example:

class Template < ActiveRecord::Base
  enum status: [:draft, :published], _default: :draft
end

Here is a link to relative PR and a link to the docs.

Rails 7+

Starting from Rails 7, there is no more need to use leading underscore. For example:

class Template < ActiveRecord::Base
  enum :status, [:draft, :published], default: :draft
end

Here is a link to relative PR and a link to the docs.

Biplane answered 21/7, 2020 at 16:26 Comment(4)
Suggest linking to api.rubyonrails.org/v6.1.0/classes/ActiveRecord/Enum.html for your link the docs instead of the edge ones, as the syntax has changed for Rails 7Denier
Thanks, @TomLuce. This is a very useful suggestion.Biplane
@Biplane don't forget the colon before status ;)Palais
Straight to the point, very helpful. Thanks!!Hypognathous
B
25

You can set the default value from the database declaration.

create_table :templates do |t|
  t.column :status, :integer, default: 0
end

And then, map the relationship as following

class Template < ActiveRecord::Base
  enum status: { draft: 0, published: 1 }
end
Blank answered 31/1, 2016 at 9:17 Comment(6)
I used this approach but I still have the issue. @Blank ChoiScruple
Already worked. But I changed my approach since there is possibility for the enum array to change. Sting will work better for my case instead of enum.Scruple
@MartinVerdejo Did you ever figure out what was going wrong with heroku? I just tried enumerable on my app and I am getting some weird behavior from heroku. Even though I have default: 0 in the database if I run u = User.new in the attributes I see role="guest" as a string. Then running u.role results in nil. Not sure what's going on.Teutonic
That's one option, what it changing database is not an option?Dutcher
How would you achieve this without writing a new migration?Shushubert
Just a note. If you use the string version of the enum on the database migration, it won't save properly. You have to use the integer value. So default: 0 instead of default: 'draft'.Mccarthy
D
2

Rails 7

Rails 6.1 allowed the setting of a default enum value right in the model.

Rails 7 introduced new syntax for defining an enum. E.g.

class Template < ActiveRecord::Base
  enum :status, [:draft, :published], default: :draft
end

Here is a link to the relevant PR and a link to the edge docs.

Denier answered 20/10, 2021 at 14:37 Comment(0)
S
1

Another way of doing it is:

class Template < ActiveRecord::Base
  before_create :set_default_status

  enum :status: { draft: 0, published: 1 }

  def set_default_status
    self.status ||= :draft
  end
end

In this case, you can keep your migration file simple:

...
t.integer :status
...
Surveyor answered 25/3, 2022 at 18:44 Comment(0)
B
0

Rails 7

If you want the enum field to be string instead of integer, use the below method

 class Template < ActiveRecord::Base
   enum :status, { draft: "Draft", published: "Published" }, default: :draft
 end 
Boule answered 27/7, 2023 at 7:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.