Rails環境でRakeタスク作成~テストまで

## モチベーション
Rakeタスクを作成に伴い、どのように利用するかを知ること！

## Rakeタスクって？

> Rake ファイルにおける基本単位です。タスクは名前と、事前タスクと、実行するアクションのリストを持ちます。
> 

https://docs.ruby-lang.org/ja/latest/library/rake.html

RakeというRubyで書かれたビルドツール利用される基本単位のことらしいです。

`***.rake` というファイル名でRubyスクリプトを実装し、Rakeタスクとして実行することができます。

## ユースケース

下記のユースケースが考えられます。

- Railsのアプリケーションコードを利用した、一回だけ実施したい処理
- バッチなどの定期実行処理

## 利用方法

`namespace:task_name` の形式で呼び出すことができます。

```zsh
# 引数なしの呼び出し方
bin/rake 'test:argument_parse'

# 引数ありの呼び出し方
bin/rake 'test:argument_parse[this,is,test,desu]'
```

:::note warn
引数と関数の間にスペースを入れるとスペースまでタスク名として判断されるため
「そんなタスク知らないよ！」ってエラーが出ます。
:::


```zsh
rake aborted!
Don't know how to build task 'test:argument_parse ' (See the list of available tasks with `rake --tasks`)
```

## タスクの作り方

Railsコマンドを実行すると `lib/tasks` 配下にrakeタスクファイルが作成されます。

```zsh
rails g task test argument_parse
```

`lib/tasks/test.rb` が生成され、下記の内容が書き込まれます。

```ruby:lib/tasks/test.rb
namespace :test do
  desc "TODO"
  task argument_parse: :environment do
  end
end
```

実利用に合わせてタスクの微修正とファイルのリネームを実施しました 😅
```ruby:lib/tasks/argument_parse.rake
namespace :test do
  # desc はrakeタスクで何をするかを説明する部分
  desc "TODO"
  # :environment でRailsのアプリケーションコードを読み込んでいる
  # See https://qiita.com/FumiyaShibusawa/items/11035fc640bb36a615ad
  task argument_parse: :environment do
    p "This is argument_parse task!"
  end
end
```

`rake --tasks` で作成したRakeタスクが読み込まれているかを確認して、タスクを実施してみましょう！

```zsh
rake --tasks

rake action_mailbox:ingress:exim        # Relay an inbound email from Exim to Action Mailbox (URL and INGRESS_PASSWORD required)

... 省略 ...

rake test:argument_parse                # 引数の挙動確認用rakeタスク

... 省略 ...
```

```zsh:実行結果
rake test:argument_parse
"This is argument_parse task!"
```

### 引数を含めたタスクを設定するには

`[:first, :second]` のようにハッシュの配列形式で設定できます。

```ruby:lib/tasks/argument_parse.rake
namespace :test do
  desc "引数の挙動確認用rakeタスク"
  task "argument_parse", [:first, :second] => :environment do |_task, args|
    # argsは Rake::TaskArguments で取得でき、設定したシンボルで値を「文字列」として取得できる
    first = args[:first]
    second = args[:second]
    # シンボルで指定していない余剰の引数については extras で配列形式で取得できる
    extra = args.extras

    p "first: #{first}, second: #{second}, extra: #{extra}"
  end
end
```

```zsh:実行結果
bin/rake 'test:argument_parse[this,is,test,desu]'
"first: this, second: is, extra: [\"test\", \"desu\"]"
```

## テスト(RSpec)の仕方

初期のRSpecの初期設定ではRakeタスクの読み込みが実施されないため、「タスク定義が見つからないよ」エラーが出ます。

```
Failure/Error: subject(:argument_parse){ Rake.application['test:argument_parse'] }

RuntimeError:
 Don't know how to build task 'test:argument_parse' (See the list of available tasks with `rake --tasks`)
```

そのため、Rakeタスクを読み込むためにヘルパーを作成してSpecファイルから読み込んであげるようにします。

#### Rakeヘルパー関数

[RailsでRakeタスクをシンプルかつ効果的にテストする手法](https://qiita.com/aeroastro/items/c97bd26ce8b8818b6bed) のヘルパー関数を利用させていただき、実際のRailsアプリケーションと同様の手順でRSpecでもRakeタスクを読み込めるようにします。

```ruby:spec/rake_helper.rb
require 'rake'

# See https://qiita.com/aeroastro/items/c97bd26ce8b8818b6bed
RSpec.configure do |config|
  config.before(:suite) do
    Rails.application.load_tasks # Load all the tasks just as Rails does (`load 'Rakefile'` is another simple way)
  end

  config.before(:each) do
    Rake.application.tasks.each(&:reenable) # Remove persistency between examples
  end
end
```

#### RSpecの実行

RSpecで利用する場合は、作成した `rake_helper.rb` を読み込むことを忘れないようにしてください 🙏

```ruby:spec/lib/tasks/argument_parse_spec.rb
# frozen_string_literal: true

require 'rails_helper'
require 'rake_helper'

RSpec.describe 'test:argument_parse', type: :task do
  subject(:argument_parse){ Rake.application['test:argument_parse'] }

  context "引数が1つ与えられたとき" do
    # extras を利用する都合上、Rake::TaskArgumentsを利用してパラメータを設定する
    # See https://docs.ruby-lang.org/ja/latest/class/Rake=3a=3aTaskArguments.html
    let(:args) { Rake::TaskArguments.new([:first], [1]) }

    it 'firstに設定した値が標準出力に表示される' do
      expected = "\"first: 1, second: , extra: []\"\n"
      expect { argument_parse.execute(args) }.to output(expected).to_stdout
    end
  end

  context "引数が2つ与えられたとき" do
    let(:args) { Rake::TaskArguments.new([:first, :second], [1, 2]) }

    it 'first, secondに設定した値が標準出力に表示される' do
      expected = "\"first: 1, second: 2, extra: []\"\n"
      expect { argument_parse.execute(args) }.to output(expected).to_stdout
    end
  end

  context "引数が2つを超えて与えられたとき" do
    let(:args) { Rake::TaskArguments.new([:first, :second], [1, 2, 3, 4]) }

    it 'first, secondに設定した値、余剰の引数が標準出力に表示される' do
      expected = "\"first: 1, second: 2, extra: [3, 4]\"\n"
      expect { argument_parse.execute(args) }.to output(expected).to_stdout
    end
  end
end
```

### レポジトリ

[GitHub - watame/rake_test: rakeを学ぶためのレポジトリ](https://github.com/watame/rake_test/tree/main)

## 参考記事

https://qiita.com/mmaumtjgj/items/8384b6a26c97965bf047

https://zenn.dev/waddy/articles/ruby-task-syntax

https://qiita.com/FumiyaShibusawa/items/11035fc640bb36a615ad

https://qiita.com/kazuooooo/items/23a621e59e8d9e924121

https://neko314.hatenablog.com/entry/2021/11/20/130050

https://qiita.com/aeroastro/items/c97bd26ce8b8818b6bed