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.
1irb> 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 the system command for running migrations will return true.
1irb> system('rake db:migrate') 2 => 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.
1irb> system('rake db:migrate') 2== 20180311211836 AddFirstNameToAdmins: migrating ============================= 3-- add_column(:admins, :first_name, :string) 4rake aborted! 5StandardError: An error has occurred, this and all later migrations canceled: 6 7PG::UndefinedTable: ERROR: relation "admins" does not exist 8: ALTER TABLE "admins" ADD "first_name" character varying 9. 10. 11. 12Tasks: TOP => db:migrate 13(See full trace by running task with --trace) 14 => 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.
1irb> system('rake db:migrate') || raise('Failed to run migrations') 2== 20180311211836 AddFirstNameToAdmins: migrating ============================= 3-- add_column(:admins, :first_name, :string) 4rake aborted! 5StandardError: An error has occurred, this and all later migrations canceled: 6 7PG::UndefinedTable: ERROR: relation "admins" does not exist 8: ALTER TABLE "admins" ADD "first_name" character varying 9. 10. 11. 12Tasks: TOP => db:migrate 13(See full trace by running task with --trace) 14Traceback (most recent call last): 15 2: from /Users/amit/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 16 1: from (irb):4 17RuntimeError (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.
1irb> system('rake db:migrate', exception: true) 2== 20180311211836 AddFirstNameToAdmins: migrating ============================= 3-- add_column(:admins, :first_name, :string) 4rake aborted! 5StandardError: An error has occurred, this and all later migrations canceled: 6 7PG::UndefinedTable: ERROR: relation "admins" does not exist 8: ALTER TABLE "admins" ADD "first_name" character varying 9. 10. 11. 12Tasks: TOP => db:migrate 13(See full trace by running task with --trace) 14Traceback (most recent call last): 15 3: from /Users/amit/.rvm/rubies/ruby-2.6.0-preview1/bin/irb:11:in `<main>' 16 2: from (irb):2 17 1: from (irb):2:in `system' 18RuntimeError (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.
1irb> system('rake db:migrate', exception: false) 2== 20180311211836 AddFirstNameToAdmins: migrating ============================= 3-- add_column(:admins, :first_name, :string) 4rake aborted! 5StandardError: An error has occurred, this and all later migrations canceled: 6 7PG::UndefinedTable: ERROR: relation "admins" does not exist 8: ALTER TABLE "admins" ADD "first_name" character varying 9. 10. 11. 12Tasks: TOP => db:migrate 13(See full trace by running task with --trace) 14 => 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.