【個人開発】俺のターン！ドロー！アングリーカードを召喚！日常のモヤっとをカードゲーム風に消化させるアプリを作りました。

# はじめに
初めまして。大阪在住主婦の[イトミキ](https://twitter.com/itomiki_hiyoko)と申します。
夫の転勤により前職を辞めたことを機に、プログラミングの勉強を始めました。
その処女作として開発したのが、『アングリーカードジェネレーター』です。
プログラミング学習中の身であるため、技術的な内容に誤りが含まれている可能性があります。
間違っている点がありましたら、コメント等で教えていただけると幸いです！

# サービス名：[アングリーカードジェネレーター](https://www.angrycard-generator.com)

# サービス概要
俺のターン！ドロー！ストレスをアングリーカードとして召喚！
イラッとしたりモヤッとしたり・・日常のちょっとしたストレスを
カードゲーム風に遊び心を持って昇華させるためのアプリです。

# 開発の背景
日常の中に潜む、ちょっとした不満やストレス。
深刻に悩むほどのことじゃないけれど、モヤモヤの感情に引っ張られて気持ちが上がらないこともある。
でも、ストレスの感じ方も千差万別で、さっさと気持ちを切り替えられる人やネタに昇華できる人もいる。
日常で感じるストレスを、おもしろおかしくネタに昇華してあまり深刻に受け止めずに済めば良いな。
ストレス解消方法の一つの手段として使ってもらい、少しでもストレス解消の手助けになれば嬉しい。
との思いから開発に至りました。

### ターゲットユーザー
* 日常の中でプチストレスを感じることがある人
* ストレスをネタにして昇華したい人

# サービスの使い方
新規登録、もしくはゲストログインして『召喚する』リンクから召喚画面へ。
召喚画面の「タイトル」と「内容」を入力し、「召喚する」をクリックするだけ！
オリジナルのアングリーカードが召喚されます。
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1245191/037da286-9952-b426-ae41-2234e972cfc9.gif)

召喚したカードをTwitter共有してネタとして昇華したり、『怒りの墓場』では、他ユーザーが召喚したカードを一覧で見ることができます。
![angry_card.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1245191/19f07d9a-b7d5-5de3-aefa-b939d4859830.jpeg)

# 苦労した点
### なんと言っても、アプリのメイン機能である画像生成AI機能どうやって組み込むの問題

画像生成AIは、rinna株式会社の提供する日本語に特化した画像生成モデル「Japanese Stable Diffusion」のAPIを使用しました。

スクールでの一通りのカリキュラムを終え、なんとなくRailsのことはわかった気になっていました。何より、「わからないことは大抵Google先生に聞けば、先人のお知恵が得られるはず〜♪」と呑気に考えていたため痛い目をみました。

公式やgithub等の、英語なんかで書かれた文章をちゃんと読む癖がついていなかった（致命的）ため、まずは[HuggingFace](https://huggingface.co/rinna/japanese-stable-diffusion)や[GitHub](https://github.com/rinnakk/japanese-stable-diffusion)を読んでみるもよくわからない・・・
Railsでどのようにコードを書けば使えるのかわからない・・・イメージも湧かない・・・（；；）まさに暗闇の中で一つ一つ手探りで進めていく状態。

あれこれ血眼で調べた結果、rinna社の様々なプロダクトをAPIで試すことができる[rinna Developers](https://developers.rinna.co.jp/products)のText To Image APIで、以下のコードに辿り着きました。

```ruby
require 'net/http'

uri = URI('https://api.rinna.co.jp/models/tti/v2')

request = Net::HTTP::Post.new(uri.request_uri)

# Request headers
request['Content-Type'] = 'application/json'
request['Cache-Control'] = 'no-cache'
request['Ocp-Apim-Subscription-Key'] = '••••••••••••••••••••••••••••••••'

# Request body
request.body = '{
    "prompts": "天空の城",
    "scale": 7.5
}'

response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
    http.request(request)
end

puts response.code
puts response.body
```
あっコレ（~~進研ゼミで~~）見たことあるやつだ・・・！
斯くして、無事に画像生成AIを組み込んだアプリを開発できたのでした。ー完ー

とは当然ならず、その後も苦しみました。

### 生成した画像どう処理するの問題
上記の発見で雛形は手に入れたものの、「・・で？で？どうするの？画像、どう処理するの・・てか画像どうなってるの・・」の沼にハマりました。
手始めに、response.bodyの中身を確認したところ、
```
{\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAE
AAElEQVR4nFT9aa9sWXYkiJnZ2vu43zdFRE7MgcxkkixWsoo1qarVrUY3BAmSAAHqr/oqCPqtAiR0t6Quo
VTsGsgukplkZkZGxJvuvX7OXsv0YR1/mR1IJCLe4Nf9+N5rsGVmiz/67u/vz88P15cWUUvSOgoAyRh00TZ
JBKoKLFhgDWwkwbIdutgJgCOqyvb5VwCIzmI5YZK2NUJSVbmIlXEdqoQ0xlj7XlUYMzPp0ojMrFvqMkdsw
............................................................................}
```
どうやら、Text to ImageのResponseは、Base64エンコード文字列が返ってくるらしい。
~~・・・Base64ってなに〜〜〜〜〜（；；）~~

>Base64とは、64種類の文字（アルファベット「 a～z , A～Z 」と数字「 0～9 」、一部の記号「 + と / 」）と末尾の記号「 = 」を用いて一定の規則に基づきデータを変換するエンコード方式（データを他の形式に変換する際の方式）の一つです。[引用](https://medium-company.com/base64/)

変換したBase64の文字列をデコードすると変換前のデータに戻すことができるとのことなので、試しに変換ツールで、生成された画像のBase64の文字列を画像変換（Base64デコード）した結果、ちゃんと画像が表示されることが確認できました！

手順としては、
①json形式のデータをハッシュに変換
②Base64エンコード文字列を取り出す
③Base64デコードして画像を保存する(画像の保存にはCarrierWaveを使用)

```ruby
res_body = JSON.parse(response.body)　
b64_image = res_body['image']  

start = b64_image.index(',') + 1　　
bin = Base64.decode64 b64_image[start .. -1]
file = Tempfile.new(['img', '.png'])
file.binmode
file << bin
file.rewind

file = MiniMagick::Image.read(file)
self.image = file
```

[記事1](https://qiita.com/dauuricus/items/e125ccf003638b26abc8)、[記事2](https://qiita.com/maruware/items/407fc668f06e7784b198)を参考にしながら、なんとか画像をimageカラムに保存することができました。

だいぶいい調子です。先人の知恵、万歳！

### 画像どうやって合成するの問題
アングリーカードを召喚させないといけないので、カードの枠画像と、AI生成した画像を合成させねばなりません。
これは、MiniMagickを使用することで解決することができました。先人の知恵、万歳！

３つ目はあっさり書きましたが、もちろん簡単にはいかず、CarrireWaveで保存したAI生成画像を取り出すのに地味に苦労して一つ一つbinding.pryで中身を確認しながら模索したり、ちまちま文字の位置を調整したり、文字の改行ができなくて己のRuby力を恨んだりしながら取り組みました。

Railsは色々とよしなにしてくれるもんだから、Rubyをおろそかにしていたツケが回ってきました。
反省して、それ以来ちょこちょこpaizaなどでアルゴリズム学習をしています。

公式リファレンスや記事をしっかり読むことの大切さや、Ruby力の大切さ、ふんわりとしか理解していなかったhttp通信など、実践（？）を通していろいろ学べました。日々学習だ！人生は勉強や！

# 工夫した点
### ゲストログイン機能
サービス内容が内容だけに、いちいち新規登録かましてたら手軽に使ってもらえない！と思い、ゲストログインで簡単に召喚できるようにしました。
当初、1つのゲストアカウントをゲストログインユーザーで共有する仕様にしており、ゲストユーザー全員が他のゲストユーザーの投稿を削除できちゃったり、Twitter共有できちゃったりしたため、ゲストログイン時に新たにユーザーを作成するように修正し、それぞれのゲストユーザーのデータが競合しない仕様にしました。

```ruby
def guest_login
　　random_value = SecureRandom.hex
 guest = User.create!(name: 'ゲスト', email: "guest_#{random_value}@example.com", password: "#{random_value}", password_confirmation: "#{random_value}", guest: true)
 auto_login(guest)
 redirect_to new_angry_card_path, success: 'ゲストユーザーとしてログインしました'
end
```
# MVPリリース後の改善
MVPリリースして色々な人に触っていただいたところ、ページが重いという声がありました。そこで、[サイトスピード計測ツール](https://pagespeed.web.dev/)を使用したところ、特に画像サイズが問題になっていることがわかりました。
画像を生成したり合成することに必死で画像サイズのことを考えておらず、1つの画像につき2MB以になっていたため、画像をリサイズしてsaveする処理を加えて、1MB以下に収まるように修正しました。
今後もパフォーマンス向上の工夫をしていきたいです。

# 主な使用技術
### バックエンド
* Ruby3.1.3
* Ruby on Rails 7.0.4
### フロントエンド
* HTML/CSS/Javascript
* CSSフレームワーク
    * Tailwind css
    * daisyUI
### 主要ライブラリ
* Sorcery
* MiniMagick
* Carrierwave
* fogAWS
* MetaTags(SEO)
* kaminari
### インフラ
* Heroku
* PostgreSQL(データベース)
### その他
* TextToImage API

# 開発期間
MVPリリースまで約2ヶ月
本リリースまで2週間

# おわりに
　軽い気持ちで画像生成AIを取り入れようとして、実装に苦労し、自分の技術力のなさを痛感しました。
でもその苦労の中で、わからないことを一つ一つトライアンドエラーして作り上げていく経験や、楽しさを身をもって知ることができました。何より、自分が考えたものを形にできるって楽しい！素晴らしい！と実感しました。
　まだまだ未熟ですが、いつかこの記事を見返して「こんな日もあったなー」と懐かしむ日が来るように、これからも日々学習をして、スキルや経験値を上げていけるよう頑張ります。