Read more

Rails: Using database default values for boolean attributes

Emanuel De
March 06, 2023Software engineer at makandra GmbH

In the past we validate and set default values for boolean attributes in Rails and not the database itself.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show snapshot

Reasons for this:

  • Older Rails didn't support database defaults when creating new records
  • Application logic is "hidden" in the database

An alternative approach, which currently reflects more the general opinion of the Rails upstream on constraints in the database, is adding default values in the schema of the database itself. We also encourage to set boolean attributes to not null. For boolean attributes with explicit three states Show snapshot you might consider using an enum instead.

create_table :users do |t|
  t.string :name
  t.boolean :locked, default: true, null: false


Migrating from application defaults to database is straight forward in most cases. Let's assume we have the following schema:

create_table :users do |t|
  t.string :name
  t.boolean :locked

The a migration might look like this:

update("UPDATE users SET locked = #{quoted_false} WHERE locked IS NULL")
change_column_null(:users, :locked, false)
change_column_default(:users, :locked, from: nil, to: false)

In case you're using the does_flag trait from the card above, you can drop

  • the does_flag trait
  • the includes in all your models

Note that the following code from the does_flag trait is only necessary in rare cases and should be avoided in general:

validates :locked, inclusion: { in: [true, false] }


In general it should be save to always use defaults in the database schema. This would make our has_default Show snapshot gem unnecessary.

There's only subtile difference: For defaults with procs Active Record inserts the class as argument and has_defaults the instance.

has_default :datetime => { } # Works
attribute :datetime, default: -> { } # Works
has_default :receive_newsletter => proc { self.role == 'customer' } # Works (`self` is an instance)
attribute :receive_newsletter, default: proc { self.role == 'customer' } # Doesn't work (`self` is the class)

Generally spoken: Default attributes based on other values of the instance are error prone. You still can achieve this by using a callback.

before_initialize :set_newsletter


def set_newsletter
  return unless [nil, ''].include?(receive_newsletter)

  self.receive_newsletter = (user.role == 'customer')

Note: When using active_type Show snapshot , the attribute method within a ActiveType::Object or aActiveType::Record will set self to the object and not the class.


In case you want to ensure developers don't forget to add a default value and a not null constraint, you can use the Rails/ThreeStateBooleanColumn Show snapshot cop.

Emanuel De
March 06, 2023Software engineer at makandra GmbH
Posted by Emanuel De to makandra dev (2023-03-06 11:53)