In this chapter, we'll explore SQL schema dumps in Rails, how they differ from
the default Ruby schema dumps, their benefits, and their impact on development.
We'll also discuss how to manage SQL schema dumps in Rails projects.
Types of Schema Dumps
Rails generates schema dumps based on the config.active_record.schema_format
setting in the config/application.rb file. By default, this format is set to
:ruby, but it can also be set to :sql.
Using the Default :ruby Schema
When the format is set to :ruby, the schema is stored in the db/schema.rb
file. This file is created by inspecting the database and expressing its
structure using create_table, add_index, and other similar methods.
Using the :sql Schema Dumper
The :ruby schema dump may not be suitable for databases using advanced
features like triggers, sequences, stored procedures, etc., as they cannot be
expressed in the Ruby migration DSL. In such cases, you should set the schema
format to :sql to create an accurate schema file for generating new database
instances.
When the schema format is set to :sql, the database structure is dumped using
a tool specific to the database into the db/structure.sql file. To load the
schema from db/structure.sql, run bin/rails db:schema:load.
Benefits of Using SQL Schema Dumps
Using SQL schema dumps in Rails projects offers several advantages over the
default :ruby schema, particularly when working with advanced database
features. Here are some key benefits:
-
Support for advanced database features: SQL schema dumps allow you to fully
leverage the capabilities of your database management system. For example,
with PostgreSQL, you can utilize features like triggers, partitions, table
constraints, materialized views, and more. The :ruby schema format cannot
accurately represent these features, limiting your ability to use them in your
application. Example: Let's say you want to create a trigger in PostgreSQL to
automatically update a last_modified column whenever a row is updated. With
an SQL schema dump, you can create the trigger using a custom SQL statement in
your migration, and the trigger will be accurately represented in your
db/structure.sql file. This would not be possible using the :ruby schema
format.
-
Fewer schema conflicts: With SQL schema dumps, schema conflicts are less
frequent, as the schema version conflicts that often occur when using the
:ruby format are reduced. This can make managing schema changes across
multiple branches easier and more efficient.
-
Increased flexibility: SQL schema dumps provide you with greater flexibility
when designing your database schema, as you can work directly with SQL
statements and tailor your schema to fit the specific needs of your
application.
-
Improved testing: When using SQL schema dumps, you can write test cases for
database-level constraints, ensuring that your application behaves correctly
under different conditions. This is especially important when using custom SQL
statements in your migrations, as these constraints are not included in the
:ruby schema dump.
By switching to SQL schema dumps, you can take full advantage of the features
offered by your database management system, leading to more robust and efficient
applications.
Impact on Development
With SQL schema dumps, you'll work with plain SQL to describe the schema. You
can continue to use Rails abstractions in migrations, but the resulting database
schema is now reflected in the db/structure.sql file.
Switching to SQL Schema Dumps
To switch to an SQL schema dump, set config.active_record.schema_format to
:sql and regenerate the schema in SQL. You may need to adjust some build steps
as well.
Handling Git Conflicts When Switching to :sql Format
When switching from the :ruby to the :sql format, you may encounter git
conflicts, especially if multiple branches modify the schema. These conflicts
can be resolved by following the steps below:
- Pull in changes or rebase your feature branch onto the master branch (ignore
conflicts in db/schema.rb and discard their changes).
- If you have new migrations in your branch, reflect their changes in
db/structure.sql. To update db/structure.sql with the changes from your
migrations, you have two options:
Option A: Reset the local database schema (using rake db:reset, which also drops
local database data) and re-run the migration on top of it. This results in
relevant changes to db/structure.sql, which should be committed (similar to
how schema.rb changes were committed previously).
Option B: Run rake db:structure:dump to dump the schema of your local database
into db/structure.sql. Ensure that you commit only relevant changes
corresponding to database migrations.
Once you've resolved the conflicts and updated db/structure.sql, commit your
changes, and continue with your normal development workflow. Remember that
switching to :sql format may require some adjustments and getting used to, but
it provides benefits such as the ability to use advanced PostgreSQL features and
potentially fewer schema conflicts in the long run.
Handling Custom SQL Statements in Migrations
In some cases, you may want to use custom SQL statements in migrations to add
constraints, for instance. When using an SQL schema dump, you can add these
custom SQL statements directly to the migrations. In addition, you can maintain
a test/extra.sql file, which is loaded in the test_helper.rb file, to ensure
these custom SQL statements are executed in the test environment.
Example: Suppose you want to prevent overlapping booking slots in a scheduling
application. Then in that case, we can either resort to moving the schema format
fully to :sql or stick onto :ruby format itself and add the custom raw sql
to take care of the constraints. In BigBinary we usually go with the latter. So
in this case, we can create a migration like this: