This blog is part of our Ruby 2.6 series. Ruby 2.6.0-preview2 was recently released.

We write scripts to automate setup and deployment of Rails applications. In those scripts, at many places, we need to run system commands like bundle install, rake db:create, rake db:migrate and many more.

Let’s suppose we need to run migrations using rake db:migrate in a rails project setup script. We can use Kernel#system method.

irb> system('rake db:migrate')

Ruby 2.5.0

Executing system returns true or false. Another feature of system is that it eats up the exceptions.

Let’s suppose our migrations can run successfully. In this case system command for running migrations will return true.

irb> system('rake db:migrate')
 => true

Let’s suppose we have a migration which is trying to add a column to a table which does not exist. In this case, system command for running migrations will return false.

irb> system('rake db:migrate')
== 20180311211836 AddFirstNameToAdmins: migrating =============================
-- add_column(:admins, :first_name, :string)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  relation "admins" does not exist
: ALTER TABLE "admins" ADD "first_name" character varying
.
.
.
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
 => false

As we can see, even when there is a failure in executing system commands, the return value is false. Ruby does not raise an exception in those cases.

However, we can use raise explicitly to raise an exception and halt the setup script execution.

irb> system('rake db:migrate') || raise('Failed to run migrations')
== 20180311211836 AddFirstNameToAdmins: migrating =============================
-- add_column(:admins, :first_name, :string)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  relation "admins" does not exist
: ALTER TABLE "admins" ADD "first_name" character varying
.
.
.
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Traceback (most recent call last):
        2: from /Users/amit/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>'
        1: from (irb):4
RuntimeError (Failed to run migrations)

Ruby 2.6.0-preview1

Ruby 2.6 make our lives easier by providing an option exception: true so that we do not need to use raise explicitly to halt script execution.

irb> system('rake db:migrate', exception: true)
== 20180311211836 AddFirstNameToAdmins: migrating =============================
-- add_column(:admins, :first_name, :string)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  relation "admins" does not exist
: ALTER TABLE "admins" ADD "first_name" character varying
.
.
.
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Traceback (most recent call last):
        3: from /Users/amit/.rvm/rubies/ruby-2.6.0-preview1/bin/irb:11:in `<main>'
        2: from (irb):2
        1: from (irb):2:in `system'
RuntimeError (Command failed with exit 1: rake)

Ruby 2.6 works the same way as previous ruby versions when used without exception option or used with exception set as false.

irb> system('rake db:migrate', exception: false)
== 20180311211836 AddFirstNameToAdmins: migrating =============================
-- add_column(:admins, :first_name, :string)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  relation "admins" does not exist
: ALTER TABLE "admins" ADD "first_name" character varying
.
.
.
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
 => false

Here is relevant commit and discussion for this change.

system is not the only way to execute such scripts. We wrote a blog 6 years back which discusses the differences between running commands using backtick, exec, sh, popen3, popen2e and Process.spawn.