Maintaining custom application tasks in Rails

Updated . Posted . Visible to the public.

Here are some hints on best practices to maintain your tasks in larger projects.

Rake Tasks vs. Scripts

  • The Rails default is using rake tasks for your application tasks. These live in lib/tasks/*.
  • In case you want to avoid rake for your tasks and just use plain ruby scripts, consider lib/scripts/* as folder.

Keeping tasks slim

For readability and testing it's easier to keep your tasks slim.

Example:

lib/gitlab/maintenance_tasks/user_export.rb:

module Gitlab
  module MaintenanceTasks
    class UserExport
      def export
        # Your code of exporting all users e.g. into a XSLX file
      end
    end
  end
end

As rake task: lib/tasks/gitlab.rb:

# bundle exec rake gitlab:user_export

namespace :gitlab do
  desc 'Export all users as XSLX'
  task user_export: :environment do
    Gitlab::MaintenanceTasks::UserExport.new.export
  end
end

As plain ruby script: lib/scripts/user_export.rb:

# bundle exec rails runner -e development lib/scripts/user_export.rb

Gitlab::MaintenanceTasks::UserExport.new.export

Testing should not be optional

Tasks should be tested the same way as other code, even if they are only run once.

Example:

spec/lib/gitlab/maintenance_tasks/user_export_spec.rb:

describe Gitlab::MaintenanceTasks::UserExport do
  it 'should export all users as XLSX file' do
    # Your code for testing all edge cases in the user export
  end
end

As rake task: spec/lib/tasks/gitlab_spec.rb:

describe 'lib/scripts/gitlab.rb' do   
  it 'calls the user export' do
    Rake::Task['gitlab:user_export'].invoke
      
    # Your code for testing e.g. that a file user_export.xlsx is written to disk (smoke test)
  end
end

As plain ruby script: lib/scripts/gitlab_spec.rb:

describe 'lib/scripts/gitlab.rb' do
  let(:script) { Rails.root.join(subject) }
    
  it 'calls the user export' do
    load(script)
    
    # Your code for testing e.g. that a file user_export.xlsx is written to disk (smoke test)
  end
end

RSpec setup

RSpec setup that avoids rewriting your scripts in a special way to make them testable:

module ScriptSpecHelpers
  def run_script(script_path, *args)
    stub_const('ARGV', args.map!(&:to_s))
    load(script_path, true)
  end
end

RSpec.configure do |config|
  config.define_derived_metadata(file_path: Regexp.new('/spec/script')) do |metadata|
    metadata[:type] = :script
  end

  config.include ScriptSpecHelpers, type: :script
end

# example usage spec/script/demo_spec.rb

describe 'script/some_script.rb' do
  it 'can be tested' do
    some_setup
    
    run_script(subject, 42, 'some_arg')
    
    assert_something
  end
end

Further reading

Last edit
Niklas Hä.
License
Source code in this card is licensed under the MIT License.
Posted by Emanuel to makandra dev (2024-01-15 14:25)