GPT-4を使って既存のRailsアプリケーションにヘルスチェック機能追加をしてみる
概要
- GPT-4 の性能が良いので既存の Ruby on Rails で動いているアプリケーションに機能を追加して本番へリリースできるクオリティを目指します。
- GPT に指示出しをして PoC を作らせるのは簡単なので、今回はプロダクションレベルに出して良いレベルのコードを出力させて本番にデプロイします。
機能要求
最近、個人開発している サービスのコンテンツをCDNに乗せました。originの外形監視のためにヘルスチェック用のエンドポイントが必要になったのでその機能を実装してもらいましょう。
実装の難易度はかなり低いですが、細かいところの評価ポイントがあります。
- 作る人によって Controller の名称が微妙ではないか?
- 返却される JSON データのキーの名前が微妙ではないか?
- rspec を書いてくれるか?
実装
一発目は、とりあえずエンドポイント名だけを指定してコードを書いてもらいました。

なかなか良いです! しかしながら文字列で時間を返されてもちょっと扱いづらいので Unix タイムスタンプで返してもらおうと思います。

期待通りのコードができました。しかし、実装だけなら誰でもできる! 殆どのエンジニアが面倒だなと思うテストコードも書いてもらいましょう。

まぁ、90 点ぐらいのできです。
ちょっとこのコードでは 100%テストが通るとは限らないので修正してもらいます。

ヒントを与えすぎてしまったかもしれませんが、理想のコードになりました。
なぜ今年の元旦の日付に固定したのかは不明です。べつに Time.zone.now と書いたほうが良いです。コード量が削減できますし、マジックナンバーも減ります。まぁ、細かいことは気にしないで良いです。
しかしながら惜しいです。細かいですが、rubocop で指摘されるような細かい指摘をしてみました。

GPT に書かせて、rubocop -a を使って自動に校正できるような内容なので全く問題無いです。
テスト
上記のコードをコピー&ペーストして、rspec を実行してみます。
% docker compose run --rm app bundle exec rspec spec/controllers/healthcheck_controller_spec.rb[+] Running 2/0 ⠿ Container train-chrome-1 Running 0.0s ⠿ Container train-db-1 Running 0.0s
Randomized with seed 11651
HealthcheckController GET #index returns http success returns current time in unix timestamp format
Top 2 slowest examples (0.05121 seconds, 94.6% of total time): HealthcheckController GET #index returns http success 0.0462 seconds ./spec/controllers/healthcheck_controller_spec.rb:13 HealthcheckController GET #index returns current time in unix timestamp format 0.005 seconds ./spec/controllers/healthcheck_controller_spec.rb:18
Finished in 0.05413 seconds (files took 2.79 seconds to load)2 examples, 0 failures
Randomized with seed 11651素晴らしいです。1発で成功しました。
つぎに、実際にHTTP経由で叩いてみましょう。
% docker compose up% curl -s http://localhost:3001/healthcheck.json | jq{ "time": 1679753425}成功しました! 完璧です。
こうして作られた機能が以下のURLになります。 https://train.teraren.com/healthcheck.json
成果物
今回作ったコードをdiff形式で以下のURLにおいておきました。
そのまま取り込めば同じ機能を自分のプロジェクトにそのまま使えると思います。
| diff --git a/Gemfile b/Gemfile | |
| index ddc9c44..d824388 100644 | |
| --- a/Gemfile | |
| +++ b/Gemfile | |
| @@ -96,4 +96,6 @@ group :test do | |
| gem 'capybara-screenshot' | |
| gem 'rspec-rails' | |
| + gem "timecop" | |
| end | |
| + | |
| diff --git a/Gemfile.lock b/Gemfile.lock | |
| index 93812e6..68ebe2c 100644 | |
| --- a/Gemfile.lock | |
| +++ b/Gemfile.lock | |
| @@ -275,6 +275,7 @@ GEM | |
| tailwindcss-rails (2.0.21-x86_64-linux) | |
| railties (>= 6.0.0) | |
| thor (1.2.1) | |
| + timecop (0.9.6) | |
| timeout (0.3.1) | |
| turbo-rails (1.3.2) | |
| actionpack (>= 6.0.0) | |
| @@ -301,6 +302,7 @@ GEM | |
| zeitwerk (2.6.6) | |
| PLATFORMS | |
| + aarch64-linux | |
| x86_64-linux | |
| DEPENDENCIES | |
| @@ -336,6 +338,7 @@ DEPENDENCIES | |
| sprockets-rails | |
| stimulus-rails | |
| tailwindcss-rails | |
| + timecop | |
| turbo-rails | |
| tzinfo-data | |
| web-console | |
| diff --git a/app/controllers/healthcheck_controller.rb b/app/controllers/healthcheck_controller.rb | |
| new file mode 100644 | |
| index 0000000..584b1f5 | |
| --- /dev/null | |
| +++ b/app/controllers/healthcheck_controller.rb | |
| @@ -0,0 +1,6 @@ | |
| +class HealthcheckController < ApplicationController | |
| + def index | |
| + current_time = Time.now.to_i | |
| + render json: { time: current_time } | |
| + end | |
| +end | |
| diff --git a/app/helpers/healthcheck_helper.rb b/app/helpers/healthcheck_helper.rb | |
| new file mode 100644 | |
| index 0000000..6c52fff | |
| --- /dev/null | |
| +++ b/app/helpers/healthcheck_helper.rb | |
| @@ -0,0 +1,2 @@ | |
| +module HealthcheckHelper | |
| +end | |
| diff --git a/config/routes.rb b/config/routes.rb | |
| index fcccb90..ad1e9d4 100644 | |
| --- a/config/routes.rb | |
| +++ b/config/routes.rb | |
| @@ -18,4 +18,5 @@ Rails.application.routes.draw do | |
| root to: 'home#index' | |
| get '/doc' => 'home#doc', :format => false | |
| get '/doc/redoc' => 'home#redoc', :format => false | |
| + get '/healthcheck', to: 'healthcheck#index', defaults: { format: 'json' } | |
| end | |
| diff --git a/spec/controllers/healthcheck_controller_spec.rb b/spec/controllers/healthcheck_controller_spec.rb | |
| new file mode 100644 | |
| index 0000000..0093de0 | |
| --- /dev/null | |
| +++ b/spec/controllers/healthcheck_controller_spec.rb | |
| @@ -0,0 +1,28 @@ | |
| +require 'rails_helper' | |
| + | |
| +RSpec.describe HealthcheckController, type: :controller do | |
| + describe 'GET #index' do | |
| + before do | |
| + Timecop.freeze(Time.zone.local(2023, 1, 1, 12, 0, 0)) | |
| + end | |
| + | |
| + after do | |
| + Timecop.return | |
| + end | |
| + | |
| + it 'returns http success' do | |
| + get :index | |
| + expect(response).to have_http_status(:success) | |
| + end | |
| + | |
| + it 'returns current time in unix timestamp format' do | |
| + get :index | |
| + json_response = JSON.parse(response.body) | |
| + current_time = Time.now.to_i | |
| + returned_time = json_response['time'] | |
| + | |
| + expect(returned_time).to eq(current_time) | |
| + end | |
| + end | |
| +end | |
| + | |
| diff --git a/test/controllers/healthcheck_controller_test.rb b/test/controllers/healthcheck_controller_test.rb | |
| new file mode 100644 | |
| index 0000000..e2a212a | |
| --- /dev/null | |
| +++ b/test/controllers/healthcheck_controller_test.rb | |
| @@ -0,0 +1,7 @@ | |
| +require "test_helper" | |
| + | |
| +class HealthcheckControllerTest < ActionDispatch::IntegrationTest | |
| + # test "the truth" do | |
| + # assert true | |
| + # end | |
| +end |
% cd <root of rails project>% curl https://gist.githubusercontent.com/matsubo/ba0db8ba1386119ff0ffff1142a038ba/raw/1c5b63a98a366fb566aad998dba636f0d3900ba8/healthcheck.diff | patch -p1そもそも
1 つ上位の視点で考えると、なんで Chat-GPT の結果を自分でコピー&ペーストして、他のプロジェクトに利用できるように diff を貼り付けているのだろう。 ここまでの流れをやらせれば良いのでは? と思いました。
試しに、Chat-GPT4 に diff を出させてみました。 
ちゃんと出力されています。
ここまで、長々と GPT4 と対話して成果物を作製しましたが、以下のように司令を書けば 1 発で終わります。
ここで言えることは、issue に書くようなフォーマットで実装を見越して要求定義をしっかり書けばコードを生成してくれます。 プロジェクトで共通の事項である rspec がインストール済みといったことは 1 回覚えさせてしまえば良いので省略できます。

まとめ
- 簡単な実装ならばコードを生成できそうです。
- 当初、懸念していたキーワード選定の正しさや、テストコードの正しさはほぼ私のコーディングスタイルと一致しているので違和感は無いコードが出てきました。
- 単純に要求を書いて、出されるようなコードは 100 点のコードは出てきません。事前条件の設定や、要件の設定でチューニングするのが良いです。

