A Rails script lives in lib/scripts and is run with bin/rails runner lib/scripts/...
. They are a simple tool to perform some one-time actions on your Rails application. A Rails script has a few advantages over pasting some prepared code into a Rails console:
Although not part of the application, your script is code and should adhere to the common quality standards (e.g. no spaghetti code). However, a script has a few special requirements. Please consider these when writing a Rails script.
Your script may crash while halfway through its task. Running it again should not break anything, but continue where it left off.
Use database transactions for atomic changes, i.e. changes that must happen together or not at all.
ActiveRecord::Base.transaction do
# Change something
# Another, dependent change
end
The script should always state what it is about to do, and await confirmation from the user. If a decision is needed, it should print all relevant information.
You can copy interaction helpers from Geordi Show archive.org snapshot .
When processing lists of records, print an identifier for each item. This way, you can watch it progress, and you'll know what the script was at in case it crashes.
Your script will encounter invalid records, and record.save!
will crash your script. Either use if record.save
, or wrap the respective code like this:
begin
# If using transactions, nest them here
# ... record.save! ...
rescue ActiveRecord::RecordInvalid
# Handle error
end
On error, log the record for manual care.
Not every script needs a test. However, sometimes you'll want to add some. Here is how. (Inspired by Functional core, imperative shell Show archive.org snapshot .)
# This class will be specced
# To simplify this, it should not read input
class YourScript
# Methods offering functionality
end
unless Rails.env.test?
script = YourScript.new
# All interactivity (i.e. reading input) goes here
end
require_relative '../your_script_name'
describe 'the purpose of your script' do
subject { YourScript.new }
it 'has output' do
expect { subject.print_summary }.to output(/success/).to_stdout
end
end