【Rails】dry runなrakeタスクの作成方法

# はじめに
私は4月に新卒入社したバックエンドエンジニアで、現在railsでの開発を行っています。dryrunなrakeタスクを最近書く機会があったので、簡単な例を交えつつ紹介したいと思います。

# Rakeタスクとは？
Rakeは設定したタスクをコマンドラインで実行できるもので、そのタスク自体のことを
Railsを触ったことがある人なら使用したことがあると思いますが、``rails db:migrate``もrakeタスクです。ちなみ``rake db:migrate``でも同じ動作になりますが、``rails``を使用するとよいかと思います。


# 準備
今回は単純なFruitsテーブルに果物のデータを追加・削除するタスクを作成してみようと思います。
そのための準備として以下のコマンドを実行します。
``rails g model Fruit name:string price:integer``
上記のコマンドを実行することでFruitsモデルとmigrationファイルが生成されるので、次に``rails db:migrate``しましょう。


すると以下のようなFruitsテーブルができます。

| Column     | Data Type | Options                   |
|------------|-----------|---------------------------|
| id         | bigint    | not null, primary key     |
| name       | string(255) |                         |
| price      | integer   |                         |
| created_at | datetime  | not null                  |
| updated_at | datetime  | not null                  |

これで準備は完了です

# Rakeタスクの作成
ここからはRakeタスクの作成に入ります。学習のために、果物を追加するタスクはdry runではないRakeタスクに、削除するタスクをdry runで実行できるようにしましょう。まず、Rakeファイルを作成するために以下のコマンドを実行します。

``rails g add_fruit``

上記コマンドを実行すると``lib/tasks/add_fruit.rake``が作成されます。
次に以下のように``add_fruit.rake``を記述していきます

```
namespace :add_fruits do
  desc '果物テーブルにりんごとみかんとぶどうを追加する'
  task add: :environment do
    logger = ActiveSupport::Logger.new($stdout)

    fruits_to_add = [
      { name: 'りんご', price: 100 },
      { name: 'みかん', price: 50 },
      { name: 'ぶどう', price: 150 }
    ]

    fruits_to_add.each do |fruit_data|
      fruit = Fruit.new(fruit_data)
      if fruit.save
        logger.info "追加完了：#{fruit.name}"
      else
        logger.error "追加エラー：#{fruit.errors.full_messages.join(', ')}"
      end
    end

    logger.info '追加が完了しました。'
  end
end

```

ここまで書いて保存したら``rails -T``してみましょう。タスク一覧の中に``rails add_fruits:add_fruits``があればOKです。このタスクを実行すると、メッセージが表示され、果物がFruitsテーブルに追加されます。

![スクリーンショット 2023-09-29 19.03.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439669/edd97b9d-2514-0f6b-d103-8f491bd09e0e.png)


# dryrunな書き方
次にdry runな書き方で果物を削除するrakeタスクを作成していきましょう。``rails g delete_fruit``を実行して削除用のrakeタスクを作成します。

次に、以下のようにdelete_fruitを記述します

```
namespace :delete_fruits do
  desc '果物テーブルからりんごとみかんを削除する'
  task :delete_fruits, [:status] => :environment do |_task, args|
    logger = ActiveSupport::Logger.new($stdout)
    logger.info '============================'
    puts 'ドライランで実行' unless args[:status] == 'exec'

    fruits_to_delete = ['りんご', 'みかん']

    deleted_fruits = Fruit.where(name: fruits_to_delete)

    print "#{fruits_to_delete.join('と')}を削除します。本当によろしいですか？ (Y/n): "
    confirmation = $stdin.gets.chomp.upcase

    if confirmation == 'Y'
      begin
        ActiveRecord::Base.transaction do
          deleted_fruits.each do |fruit|
            logger.info "削除中：#{fruit.name}"
            fruit.destroy if args[:status] == "exec"
          end
        end
        logger.info '削除が完了しました。'
      rescue StandardError => e
        logger.error "エラーが発生しました: #{e.message}"
        raise ActiveRecord::Rollback
      end
    else
      logger.info '削除はキャンセルされました。'
    end
    logger.info '============================'
  end
end
```

ここで、先程のコードと違う点は

- タスクの実行時に引数を入力する
- 引数がexecでないと削除の処理が行われない

という点です。``rails delete_fruits:delete_fruit``を実行すると、
``りんごとみかんを削除します。本当によろしいですか？ (Y/n):``
という文言が出てきますが、Yを入力してもテーブルに変化はありません。
``rails delete_fruits:delete_fruit[exec]``を実行し、Yを入力すると削除の処理が行われ、テーブルは以下のようになります。

![スクリーンショット 2023-09-29 19.34.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3439669/f37ed065-1930-a6f6-7aea-92cb9d7401c2.png)

# おわりに
Rakeタスクをdry runにすることで、本番実行前に操作をテストすることができ、Rakeタスクを安全に実行できます。