Back to blog overview

September 15, 2023

Understanding Schema Formats in Ruby on Rails: SQL vs. Ruby

Jesse House



Senior Software Engineer
Prescott, AZ

In Ruby on Rails applications the schema.rb file acts as a representation of your database schema in Ruby code.
It provides a concise and human-readable way to define the structure of your database tables, columns, indexes, and constraints using Ruby DSL (Domain-Specific Language) statements.

The schema.rb file is updated whenever you run database migrations (`bin/rails db:migrate`).
Rails inspects the database after each migration and updates schema.rb to reflect the current state of the schema.

When you create a new database or reset an existing one, Rails uses the schema.rb file to recreate the database structure. You may find yourself rebuilding your development or test databases often, which can be done using commands like:

- `bin/rails db:setup` or `bin/rails db:reset`
- or specifing each step: `bin/rails db:drop db:create db:schema:load db:seed`

## Schema formats

By default Rails applications use the `:ruby` schema format and save your schema to `db/schema.rb`.
This format is database-agnostic, meaning you can use it for PostgreSQL, MySQL, and more.

The `:sql` schema format, on the other hand, is database-specific and saves your schema to `db/structure.sql`.
Rails will shell out to database specific tools to generate the file; for PostgreSQL, it will use `pg_dump`.

## Configuring the schema format

If you want to use the `:sql` format, add this line to your `config/application.rb` file:

config.active_record.schema_format = :sql

## Why Use the SQL Format?

While the `:ruby` format will meet the needs of most applications,
you should use the `:sql` format if your database includes views, triggers, or other objects not supported by the `:ruby` schema.
For example, if you need to grant read access to a reporting system while excluding certain columns or data from being read, you might need to introduce database views instead of granting `SELECT` permissions to the tables directly. Here's a contrived example that hides a column in the `users` table (`super_secret`) and never returns the admin user:

CREATE VIEW public.user_reporting_view AS
 FROM public.users
 WHERE <> '';

If you were using the `:ruby` format and rebuilt the database the view would not exist

bin/rails db:drop db:create db:schema:load db:seed

psql $DATABASE_URL -c "select * from user_reporting_view limit 1;"

   ERROR:  relation "user_reporting_view" does not exist
   LINE 1: select * from user_reporting_view limit 1;

Using the `:sql` format ensures that database objects will be created:

bin/rails db:drop db:create db:schema:load db:seed

psql $DATABASE_URL -c "select * from user_reporting_view limit 1;"

id | name |      email      |         created_at         |         updated_at
 1 | FOO  | | 2023-09-12 21:19:23.104644 | 2023-09-12 21:19:23.104644
(1 row)

Note: If you're rebuilding your database from the migration files instead of the schema, the format you choose won't matter as the database views will be created from the migrations.

## Issues with the SQL Format

Rails will run `pg_dump` to generate the structure file every time you run migrations. This can result in errors if the major version of `pg_dump` differs from the database server's major version of PostgreSQL. For example:

pg_dump: error: server version: 15.4 (Debian 15.4-1.pgdg120+1); pg_dump version: 14.1
pg_dump: error: aborting because of server version mismatch
rails aborted!
failed to execute:
pg_dump --schema-only --no-privileges --no-owner --file ./db/structure.sql db_development

Please check the output above for any errors and make sure that `pg_dump` is installed in your PATH and has proper permissions.

Tasks: TOP => db:schema:dump
(See full trace by running task with --trace)

This issue might occur in development if your PostgreSQL server is running in a Docker container with a different database version than your local `pg_dump` version.

## `db:schema:load` vs `db:structure:load`

In older versions of Rails you would need to use specific load tasks depending on the format:

- For the `:ruby` format, you'd use `bin/rails db:schema:load`
- For the `:sql` format, you'd use `bin/rails db:structure:load`

However, for versions `6.1.x` and higher, you should always use `bin/rails db:schema:load` for both formats.

## Conclusion

In the world of Ruby on Rails, managing your database schema is a critical aspect of application development. Understanding the key differences between the `:ruby` and `:sql` schema formats can greatly impact how you handle your database structure.

The `:ruby` schema format, being database-agnostic and the default choice, works perfectly for most applications. It provides an elegant, Ruby-based representation of your schema and supports a wide range of use cases.

On the other hand, the `:sql` schema format is your go-to option when your database requirements become more complex. If your application incorporates database views, triggers, or any other database-specific objects, `:sql` offers the flexibility you need. It ensures that every database object, including those not covered by the `:ruby` schema, is properly created.

Remember, your choice between these schema formats should align with your project's specific needs and goals. Consider your database's complexity, external integrations, and long-term maintenance when making this decision.

## Resources

- Sample code can be found at:

Let's Chat

Are you ready to build something brilliant? We're ready to help.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
RedwoodJS Logo

for builders

Grants Pass, Oregon • September 26 - 29, 2023
View All