Numeric data types in Rails migrations. Use of optional flags to improve storage and performance

Suleyman Musayev
5 min readMay 25, 2023

Numeric data types play a crucial role in defining the structure of numeric data in a database through Rails migrations. In this article, we will explore various numeric types that can be used in Rails migrations and will dive into detailed use case for unsigned and limit optional flags.

Integer The integer type is used to represent whole numbers: both positive and negative values. The size of an integer can be specified by passing a limit parameter when defining the column in the migration file. For example, to create an integer column with a maximum value of 2 billion, you can define the migration as follows:

class CreateProducts < ActiveRecord::Migration[7.0]
def change
create_table :products do |t|
t.integer :identifier, limit: 8
t.timestamps
end
end
end

In this example, we have specified a limit of 8 bytes for the identifier column, which allows us to store values up to 2⁶³ — 1.

Float The float type is used to represent decimal numbers with a floating point with support for both positive and negative values. The precision and scale of a float can be specified by passing parameters when defining the column. For example, to create a float column with a precision of 10 and a scale of 2, you can define the migration as follows:

class CreateProducts < ActiveRecord::Migration[7.0]
def change
create_table :products do |t|
t.float :price, precision: 10, scale: 2
t.timestamps
end
end
end

In this example, we have specified a precision of 10 digits and a scale of 2 decimal places for the price column.

Decimal Similar to the float, the decimal type is also used to represent decimal numbers but with a fixed precision and scale, most commonly it is used for financial and other calculations where accuracy is of outmost importancy. Precision and scale of a decimal can be specified by passing parameters when defining the column. For example, to create a decimal column with a precision of 15 and a scale of 2, you can define the migration as follows:

class CreateOrders < ActiveRecord::Migration[7.0]
def change
create_table :orders do |t|
t.decimal :total_amount, precision: 15, scale: 2
t.timestamps
end
end
end

In this example, we have specified a precision of 15 digits and a scale of 2 decimal places for the total_amount column.

Biginteger The big integer type is used to represent large whole numbers and it supports both positive and negative values. Size of a big integer can be specified by passing a limit parameter when defining the column. For example, to create a big integer column, which can hold a maximum value of 10³⁰, you can define the migration as follows:

class CreateAccounts < ActiveRecord::Migration[7.0]
def change
create_table :accounts do |t|
t.bigint :balance, limit: 30
t.timestamps
end
end
end

In this example, we have specified a limit of 30 bytes for the balance column, which allows us to store values up to 10³⁰ — 1.

As you may have noticed from the examples above, we can use several optional flags to customize the behavior and storage of the columns in the database.

default The default flag can be used to specify a default value for a numeric column. This value will be used if no other value is specified when creating a new record. Here’s an example:

class CreateProducts < ActiveRecord::Migration[7.0]
def change
create_table :products do |t|
t.decimal :price, precision: 10, scale: 2, default: 0.0
t.timestamps
end
end
end

In this example, we’ve added a default value of 0.0 to the price column. If no price is specified when creating a new product, it will default to 0.0.

null The null flag can be used to specify whether a numeric column can be null (i.e., have no value). By default, all numeric columns are nullable, but you can use this flag to enforce no null values. Here’s an example:

class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.integer :age, limit: 2, null: false
t.timestamps
end
end
end

In this example, we’ve added the null: false flag to the age column, which means it cannot be null.

precision & scale We’ve already covered precision and scale when defining decimal columns above but I would like to note that they can also be used as optional flags when altering an existing column. Here’s an example:

class ChangePricePrecision < ActiveRecord::Migration[7.0]
def change
change_column :products, :price, :decimal, precision: 12, scale: 4
end
end

In this example, we’re altering the price column in the products table to have a precision of 12 digits and a scale of 4 decimal places.

unsigned The unsigned flag can be used with integer and big integer columns to specify that only positive values should be allowed. Most commonly it is used for columns like ID’s or counts where negative values are practically not possible. Here’s an example:

class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.integer :age, limit: 1, unsigned: true
t.timestamps
end
end
end

In this example, we’ve added the unsigned flag to the age column, which means it can only store positive values up to a limit of 1 byte. Since the age cannot be a negative number we can use the unsigned flag here.

limit This option flag that can be used with integer and big integer columns to specify the maximum number of bytes that should be used to store the value in the database. Here’s an example:

class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.integer :age, limit: 2
t.bigint :salary, limit: 8
t.timestamps
end
end
end

In this example, we’re creating an age column with a limit of 2 bytes and a salary column with a limit of 8 bytes. Please note that the limit only affects the storage size, and not the range of values that can be stored. The range of values is determined by the signed or unsigned nature of the column and the number of bytes or digits used.

It is also worth noting that if you don’t specify a limit option, Rails will resort to a default limit based on the data type and database adapter being used. For example, the default limit for an integer column in MySQL is 4 bytes, while the default limit for an integer column in PostgreSQL is 4 bytes for smallint, 2 bytes for integer and 8 bytes for bigint.

For reference here is a table of integer size limits for PostgreSQL and MySQL:

These size limit apply to signed integers, so in case of the unsigned integers the maximum value will be doubled, but the maximum number of bytes will remain the same.

And here is table of integer value ranges that can be stored in PostgreSQL and MySQL:

In general, it is a good idea to use the limit option to specify the exact storage size for the numeric columns, instead of relying on defaults. This can help to optimize storage space and performance, especially if you’re working with large datasets or limited resources.

But it also worth noting that if you exceed the specified maximum size, you may encounter errors or unexpected behavior when trying to insert or update data. Therefore, it’s important to choose an appropriate size limit based on your needs and the capabilities of your database.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response