Rails8系のscriptフォルダの紹介とデータ移行のためにscriptをブラウザから実行したくてGemを作った話

## はじめに
本番環境にsshして`rails c`を使ってデータを弄るとかはしたくないですよね。
まさか、まさか、そんなこと誰もしていないと思います...。

...と言いつつ、まだ環境が整い切ってない現場では、稀によくある話だとも思っています。

実際僕もたまにやってしまっていたので、それを解決する手段ないかと検討していました。

今回はそんな課題に対して、「こんな手法の解決策ができるかな」ってことを試したくて、Gemを作ってみました。(ちなみに、Gemはほぼ作ったことないGem初心者です。)

また、このGemはrails8系から入った`scriptフォルダ`も関連しています。

この記事では、
- データ移行の課題と
- (あまり有名でないと思うので)scriptフォルダの説明と
- 作ったGemの話

をしていきます。
が、まずはどんなもの作ったか紹介した方がわかりやすいので、作ったものの紹介です。

## 作ったGem - Scriptor

![scriptor2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/a6f53b2d-1821-60c9-edd0-d64d818de287.gif)

これは何ができるのかというと、

- ブラウザからscriptフォルダのコードが閲覧できて
- ブラウザからscriptフォルダのコードが実行できて
- ブラウザからscriptフォルダのコードの実行履歴が見られる

みたいなことができます。リポジトリはこちらです。

https://github.com/hatsu38/scriptor

ってことで、続いてこのGemを作った背景を書いてきます。

## データ移行どうやってる？
データ移行の方法は、よく話題になるトピックと思います。

これまでのデータを修正するやり方は、[maintenance_tasks gem](https://github.com/Shopify/maintenance_tasks)を使ったり、[data_migrate gem](https://github.com/ilyakatz/data-migrate)を使ったり、はたまた `rake task` を実行することだと思います。

[maintenance_tasks gem](https://github.com/Shopify/maintenance_tasks) は私も普段お仕事でお世話になっています。

[Rails Guide](https://railsguides.jp/active_record_migrations.html#%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B) には
> データをマイグレーションで操作するよりも、maintenance_tasks gemの利用を検討してください。このgemは、スキーマのマイグレーションを妨げることなく、安全かつ簡単に管理できる方法で

と、[maintenance_tasks gem](https://github.com/Shopify/maintenance_tasks)の利用が勧められていますね。

一方で、`maintenance_task` を導入するほどではないような場合、`rake task`を書いて実行することもメジャーな手法と思います。

`maintenance_task`が出てくる以前は、`rake task`での実行が非常に多かった印象です。

そんないろいろな手段があるなか、(全く紹介されているのを見かけないので有名ではない気がしますが) Ruby on Rails 8.0.0からは、 `script`フォルダというのが誕生しました。

このプルリクエストで作られています。

https://github.com/rails/rails/pull/52335

## scriptフォルダについて

[Rails Guide](https://railsguides.jp/getting_started.html#%E3%83%96%E3%83%AD%E3%82%B0%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)には、scriptフォルダについて

> 使い捨て、または汎用のスクリプトやベンチマークをここに置きます。

と説明されています。scriptフォルダの使い方が書かれている[script/USAGE](https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/script/USAGE)にも

> Generate a one-off or general purpose script, such as a data migration script, cleanup script, etc.

つまり「単発のデータ移行のためのscriptを生成する」と書いていそうです。

ってことで、実はひっそりと、Rails8系から、単発のデータ移行のために`script`フォルダってものが提供されるようになりました。

## scriptフォルダの使い方
このscriptフォルダの使い方は非常に簡単です。


```ruby
bin/rails generate script my_script
```

と実行すると `script/my_script.rb` ファイルが生成されます。

そして`script/my_script.rb`ファイルはデフォルトでは、以下のようになっています。

```ruby
require_relative "../config/environment"

# Your code goes here
```

非常に簡単ですね。このファイルをいじってRailsのコードを書いていきます。

例えば、以下のように書いてデータの移行を行なったりができます。

```ruby
require_relative "../../config/environment"

title, body = ARGV

if post = Post.find_by(title: title)
  post.update!(body: body)
  puts "Post updated!"
else
  Post.create!(title: title, body: body)
  puts "Post created!"
end
```

このscriptフォルダの実行方法は、例えば以下です。
```ruby
ruby script/my_script.rb hello world
```

ってことで簡易的なものを実行するのにはscriptフォルダも良さそうです。

もっとscriptフォルダの背景について知りたい場合は [Is there any “official” way to organize one-off scripts?](https://discuss.rubyonrails.org/t/is-there-any-official-way-to-organize-one-off-scripts/74186) が参考になるかもしれません。

## このスクリプトをブラウザから実行させたいし実行履歴を管理したい

このscriptフォルダ、簡易的データ移行については、そこそこ便利だなって思ったのですが...。
この実行のために本番環境にsshするのとかは、したくないです。
そしてこの実行のログも念の為とっておきたい、とかの欲も出てきました。

その欲を実現しようと思って作ったのが今回のScriptorというGemです。

再掲
![scriptor2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/a6f53b2d-1821-60c9-edd0-d64d818de287.gif)

https://github.com/hatsu38/scriptor

## Scriptorの簡単な説明
このGemは
- ブラウザからscriptフォルダのコードが閲覧できて
- ブラウザからscriptフォルダのコードが実行できて
- ブラウザからscriptフォルダのコードの実行履歴が見られる

ようなものです。

例えば、scriptフォルダが以下のようになっているとします。

```shell
❯ tree script                                       
script
├── migration
│   ├── hoges.rb
│   └── posts.rb
└── my_script.rb

2 directories, 3 files
```

このとき、http://localhost:3000/scriptor にアクセスすると以下のようなscript一覧画面が表示されます。

![スクリーンショット 2024-12-20 2.05.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/f45dc26d-c541-61fb-4e58-62d755d85b6f.png)

scriptの詳細ページへ行くと、そのスクリプトのコードが載っています。


![FireShot Capture 068 - Scriptor - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/d2cedbed-4afc-24e1-4f95-33e5e91f6c3a.png)

このページには `🛠️Run Script` ボタンがあり、そのボタンを押すと、このページのコードが実行できます。
また Scriptの実行に引数が必要ならば、textfieldに引数を書くとそれを反映してくれます。

![スクリーンショット 2024-12-20 2.36.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/b95defcc-c4d5-1d4a-0de4-1ccb5fad8ff7.png)

一番下には、実行の履歴も載っています。
![スクリーンショット 2024-12-20 2.36.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/c2c0eb51-a958-66ec-25de-379f1f38f93f.png)

実行履歴では
- ステータス(running, success, error)
- 開始日時
- 終了日時
- 実行コマンド
- 実行したファイル

などを保持しています。


### 導入
導入も簡単と思います。

1. `bundle add scriptor` で scriptor gemを追加
2. `bin/rails generate scriptor:install` を実行
    - `config/routes.rb` に `mount Scriptor::Engine => "/scriptor"` が記載されます
    - `db/migrations/****_create_scriptor_executions.scriptor.rb` ファイルが作成されます
3. scriptフォルダにスクリプトを作ると、http://localhost:3000/scriptor ページにスクリプト一覧が並びます

なかなか簡単で便利？なGemができた気がしています。

## 終わりに
データ移行の方法の一つとして、Rails8系からscriptフォルダが出ました。
これをうまく使えないかなと思って、今回Rails EngineのGemを作ったりしてみました。
Gem作りは慣れていないですが、楽しくやってたらちょっと良いGemできたなって気分でいます。
が、まだまだ足りていないこととか沢山ある(英語の説明もないし、テストも全然書けていない)ので、Contributeなんかも大歓迎です。Starも嬉しいです。

https://github.com/hatsu38/scriptor

ってことでscriptフォルダの話と作ったGemの話は終わります。ありがとうございました。