2021年10月3日にAWSへ移行の為URLを一部修正しました。
はじめに
はじめまして。閲覧いただきありがとうございます。
今回、Ruby on Rails を用いてポートフォリオを作成いたしましたので開発の経緯・工夫・苦悩などをまとめさせていただきます。
尚、機能面の紹介等は Github と重複する為、一部割愛しています。
関連URL
アプリ : https://medimo.tokyo (現在停止中ですので下記をご利用ください)
https://medimov1.herokuapp.com
Github: https://github.com/minasenanami/medimo
インフラ構成図
アプリ概要
「看護に関する情報・知識を記録・共有」をテーマにしたアプリで主な機能は以下となっております。
- 看護に関する記事の作成・編集・削除機能
- 記事は「公開・下書き・非公開」の公開範囲の選択が可能
- 「いいね」・「保存」機能を実装し「保存」した記事は一覧で閲覧が可能です。
- 記事のタイトル or タグ検索機能
使用技術
- 言語 : Ruby ( 2.7.2 )
- フレームワーク : Ruby on Rails (6.1.4.1)
- フロントエンド : HTML&CSS/Bootstrap/Javascript
- DB : PostgreSQL
- テスト : RSpec
- (旧)インフラ : Heroku(ステージング環境 → 本番環境)+AWS(S3)
- (現)インフラ : AWS(VPC, EC2, RDS, S3, ACM, ALB, CloudFront, IAM) + Capistrano
- ソースコード管理:GitHub(Projects のカンバン方式で issues を作成しタスクを管理)
アプリの制作背景
看護師として 5 年ほど勤務していた際に、教育担当や新人の方から以下の相談が多々ありました。
1. 毎日課題が多い
・ 翌日の担当患者さんの病態生理の把握
・ 薬剤治療前後の注意点
・ 検査値・項目の理解 etc...
2. ネットは一般向けな内容なので欲しい情報に至るまでに時間がかかる
3. ケア・業務内容の種類が多く頻度の少ない物は忘れてしまう
この課題は私の職場だけでなく他病棟や病院単位でも問題としてよく耳にしていました。
そこで、日々の「学び・体験」を共有する場所を作ることができれば、各課題に対し解決の一助になるのではないかと思い制作に取り組みました。
機能一覧
主な機能は以下となっております。
機能 | Gem | |
---|---|---|
1 | ログイン・ログアウト | devise |
2 | アカウント登録、設定変更 | devise |
3 | ユーザーアイコン登録 | ActiveStorage |
4 | 記事検索 | ransack |
5 | 記事投稿欄 | ActionText |
6 | ページネーション | kaminari |
7 | レスポンシブ | Bootstrap |
8 | テスト実装 | RSpec / Faker / FactoryBot |
9 | フォーマッター | Rubocop |
10 | ゲストログイン | x |
11 | 記事投稿機能(CRUD) | x |
12 | いいね機能(非同期) | x |
13 | 保存機能(非同期) | x |
14 | タグ機能 | x |
15 | 記事の公開範囲の設定 | x |
工夫した事
アプリ作成時に工夫した点は以下の5点です。
- 利用ユーザー層の選定から考えて UI を決定
- メインユーザーは自分ではなく他者なのでサービスを作る気持ちで利用者の想定から着手
- メインターゲットは「看護師・20代・女性」を想定
- アプリーのカラーは女性向けのサイトや好まれるカラーパレットなどを調べて選定
- 実際に配色サンプルを見てもらいフィードバックをもらう
- 最終的に下記の配色でデザイン
- markdown ではなく Action Text を導入
- 日常的に PC を使うという人が少ない
- PCの使用用途は限定的でレポートの作成・必要な資料集めなどが多い
- markdown は学習コストが掛り敬遠材料になる
- 実際にどちらが良いか聞いた所「覚えられる気がしない」との回答
- 仮にmarkdownを覚えたとしても他で転用する機会も無い
- スマホでmarkdown記法での実装をするとキーボードの切り替えが多く不便
- 上記の結果から直感的に操作できる Action Text を導入しました
- 記事に公開範囲を設けたこと
- 情報の共有以外の用途としてメモとしても利用できるようにするため
- 既存のメモアプリにメモすると他の情報と混ざり探すのに手間取る経験あり
- 業務的な内容は非公開でメモ代わりに使用し情報の一元化を図る為設けました
- マルチデバイスでのデバッグ
- iPhoneとmacをlocale環境で共有
- PCとスマホの表示デザインの崩れや調整を同時に行い後戻り作業を減らす
- スマホ上の動作も確認できるので異常を見つけやすい
- 実務でのチーム開発を意識した開発
- Git, GitHub を用いたソース管理
- Projects のカンバン方式でタスクを管理
- issue・ブランチをタスクごとに作成し、作業を進めました
苦労したこと
アプリ作成時に特に苦労したのは以下の2点です
1. ActionTextのバリテーションの設定
2. ActionText使用時のActive_Stroge_Blobsの未使用データの増殖
ActionTextのバリテーションの設定
投稿機能で使用するActionText
はデフォルトだと様々なファイルが添付可能な状態なので、添付ファイルを画像のみに制限する必要がありました。
しかし、ActionText
のバリテーションに関する情報自体が少なく、Railsガイド
を読んでも詳細な情報が見付けられず、Rails 側でどのように処理するれば良いのか悩みました。
というのもActionText
で添付された画像群をどのように取得すれば良いのかが分からない状態でした。
解決策 [ 概要 ]
- 現状をメンターの方に相談し質問をさせていただき参考文献を頂きました。
- その結果今回は
ActionText
の元であるtrix
つまりJavaScript
側で添付の制限をする事にしました - 制限内容は以下の3点です
- ファイルタイプ (jpeg, png, jpg)
- 上限枚数 10枚/回
- 画像容量の上限 5MB/枚
- 参照元はこちらです => Handling attachments in Action Text in Rails 6
※JavaScript
で処理した理由は次の苦労した事の内容と一部重複するのでそちらでご説明いたします。
実装
-
app/javascript/trix-editor-overrides.js
ファイルを作成 - 上記のファイルを
app/javascript/packs/application.js
にインポートする
window.addEventListener("trix-file-accept", function (event) {
const acceptedTypes = ["image/jpeg", "image/png", "image/jpg"];
// 添付画像ファイルの拡張子の選別処理
if (!acceptedTypes.includes(event.file.type)) {
event.preventDefault();
alert("画像ファイル以外は投稿できません");
}
// 1枚の画像の容量制限の設置
const maxFileSize = 1024 * 1024 * 5; // 5MB
if (event.file.size > maxFileSize) {
event.preventDefault();
alert("5MB以上の画像は投稿できません");
}
// 画像の枚数制限処理
const maxFileCount = 9;
const fileCount = document.getElementsByTagName("figure").length;
if (fileCount > maxFileCount) {
event.preventDefault();
alert("1回の投稿では10枚が上限です");
}
});
import "../trix-editor-overrides"
他の条件の時も同様にalert
が出て無事に画像の添付を拒否してくれるようになりました!
ActionText使用時のActive_Stroge_Blobsの未使用データの増殖
問題は以下です。
-
添付したファイルが画像以外のファイル(csv,mp4)などもDBに保存される
解決策 [ 概要 ]
[ 1, 2 ]はデプロイ時にS3を利用するので、実運用を考えた際には無用なコストになると思い不使用なデータは削除する必要がありました。
[ 3 ]への事前対策として上記のように添付制限をJavascript
で実装する事にしました。
実装
実装内容は以下です
- 未使用のデータを削除する処理を作成
- 削除処理を行うタイミングを決める
未使用データの削除処理の実装
-
ActionText
は画像を添付した瞬間に下記の画像のようにDBに添付されます。
この NULL
になっているデータを削除を実施する処理が下記です
ActiveStorage::Blob.includes(:variant_records, :preview_image_attachment).unattached.each(&:purge) if ActiveStorage::Blob.unattached.any?
# 使用されていないデータの数を取得
pry(main)> ActiveStorage::Blob.unattached.count
# => 6
# 上記のデータを削除する
pry(main)> ActiveStorage::Blob.unattached.find_each(&:purge)
Disk Storage (0.5ms) Deleted file from key: xxxxxxxxxxxxxxxxx
Disk Storage (0.1ms) Deleted files by key prefix: variants/xxxxxxxxxxxxxxxxxxxxx/
# 対象の回数分上記の処理が行われ
=> nil
未使用データが綺麗に削除されました!
今回はこちら(SmartHR TechBlogさん)の記事を参考にさせていただきました。
削除処理のタイミングを決める
削除する処理は実装できたのでこの処理を実装するタイミングを決める必要がありました。
最初は削除できているか確認したかったので仮として下記のようにマイページに遷移した際に未使用データが存在すれば処理を行うという風にしていました。
def show
check_blobs
@user = User.with_attached_avatar.find(params[:id])
省略
end
private
def check_blobs
ActiveStorage::Blob.includes(:variant_records, :preview_image_attachment).unattached.each(&:purge) if ActiveStorage::Blob.unattached.any?
end
動作として問題がない事は確認できたのですが、ここで行う処理として正しいのか心配になったのでメンターの方に相談し
get(ページ遷移)してるのに、裏で削除処理が走るのはあまり好ましくない!
とアドバイスをいただけたので、今回はheroku scheduler
にて行う事にしました。
heroku schedulerの実装
実装内容は以下です。
-
Pipeline
を使用しているのでアプリ名を指定しheroku
にアドオンを追加 (heroku addons:create scheduler:standard -a アプリ名
) - 不要データを削除する
rakeタスク
の作成
namespace :clean_active_storage_blobs do
desc "ActionTextに添付したが使用しなかった画像データを削除"
task metadata: :environment do
ActiveStorage::Blob.includes(:variant_records, :preview_image_attachment).unattached.each(&:purge) if ActiveStorage::Blob.unattached.any?
end
end
# 作成したタスクの確認
% be rake -T
....
rake clean_active_storage_blobs:metadata # ActionTextに添付したが使用しなかった画像データを削除
3, 作成したrakeタスクをheroku scheduler
に登録
以上でheroku scheduler
に定期処理を持たせる事ができ、不要なデータに悩まされる事がなくなりました!
2021年10月3日にAWSへ移行後はGem whenever
で対処。
今後の課題
- 現在非公開記事は作成者のみの閲覧になっているので、記事のurlを知っている人は閲覧できるようにしたいです。
- そのために現状のidのurlからハッシュ形式にした推測しにくい形に変えていく必要があると感じました。
- 適宜不具合やバグがあれば発見次第修繕。
作成後の感想
設計やデザインを1から考えたアプリが、日毎に形になっていく過程は、とても楽しかったです。
実装時に思うように行かず悩む事もありましたが自力で解決し、必要なタイミングで相談・質問を行うことで大変でしたが、同時に物作りの楽しさを感じました。
色々な方々が「1番の勉強は自分でアプリを作ること」と仰る意味がわかったような気がしました。
今後はRails × Vue.js
のようなモダンなフロントにも触れていきたいです。
長い記事になってしまいましたが、ここまで読んで頂きありがとうございました!