April 25, 2018
This blog is part of our Ruby 2.6 series.
We write scripts to automate setup and deployment of Rails applications. In
those scripts, in 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 the Kernel#system
method.
irb> system('rake db:migrate')
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 the system
command for running migrations will return true.
irb> system('rake db:migrate')
=> true
Let's suppose we have a migration that is trying to add a column to a table
which does not exist. In this case, the 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 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 the
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 the relevant commit and discussion for this change.
system
is not the only way to execute scripts like these. We wrote
a blog
6 years ago which discusses the differences between running commands using
backtick
, exec
, sh
, popen3
, popen2e
and Process.spawn
.
The Chinese version of this blog is available here.
If this blog was helpful, check out our full blog archive.