看護師向けのブックマークサイト「NursePicks」をリリースしました

nursepicks

本日11月7日に、看護記事ブックマークサイト「NursePicks」をリリースしました。

はじめに

フィヨルドブートキャンブ(以下、FBC)で学習中の@garammasala29です。
このたび最終課題である「Webサービスを作って公開する」にて、自作サービス「NursePicks」を作りました。
本記事では、サービスの紹介とリリースまでの振り返りをしていきます。

サービスの概要

NursePicksはWeb上にある看護記事を投稿・閲覧できる看護師向けのサービスです
一言で言えば、看護記事のまとめサイトです。

  • 記事の投稿
    Google、もしくはTwitterのアカウントがあれば、ユーザー登録でき、記事の投稿ができます。
    投稿した記事・論文が他のユーザーから評価され、評価が上がると多くのユーザーに注目されることになります。
    記事に対してコメントをしたり、いいねをすることができます。

  • 記事の閲覧
    アカウントなしでもサイトを閲覧することができ、リンクからサイト外の看護記事へと飛ぶことができます。
    記事に付けられたコメントも読むことができます。

サービスを作ったきっかけ

私自身、昨年の6月まで看護師として働いていました。
ここ5年くらいでWeb上に看護記事や論文、YouTubeが増えてきているという体感があり、その記事や論文を手元でブックマークする機会が多くなりました。
疾患について詳しく解説してくれている記事や看護処置のエビデンスが細かく書かれた記事などがあり、実際の現場で活用できる記事も多数見受けられました。
そのような良質な看護記事があるにも関わらず、拡散されずに読まれないのは勿体無い!と思い、この問題を解決するためのサービスを作ろうと思いました。

このあたりの背景や熱い気持ちはまだまだ語れますが、長くなるので今回はこのあたりにしておきます😂
もう少し詳しい話は、看護師向けリリースブログの方に記しています。

機能の紹介

使い方の動画を撮影しました

www.youtube.com

看護記事の閲覧

トップページです。投稿された看護記事一覧を確認することができます。
ユーザー登録していれば、記事にいいねを押すことができます。 top-page

看護記事の投稿

トップページにあるモーダルからURLを貼り付けることで記事を投稿することができます。 post-modal

コメントの投稿

記事詳細ページより記事についてコメントができます。 post-page

ユーザーページ

ユーザーが投稿した記事、コメント・いいねした記事の一覧が見れます。 user-page

技術スタック

こだわりポイント

匿名性の担保

看護師さんは守秘義務の中で活動しており、公式のSNSアカウントを持っている方は数少ないです。
ユーザー登録する上で、プライベートのSNSアカウントで登録することも多いと予想されるので、名前が変更できたり画像を選択できるようにしました。

signup-page
ユーザー登録ページ

また、SNS認証って怖いものなのでは?と思われる方(実際に看護師さんに使ってもらい判明した)に対して、安心して活用できるようユーザー登録部分には説明文を付け加えています。

signup-modal
ユーザー登録ボタンのモーダル

記事の検索

看護師といっても診療科(外科、内科、精神科など)や対象(高齢者、小児、妊婦など)で役立つ記事はそれぞれが違ってきます。
そのため、記事のタイトル、サイト元、タグから、必要な記事を検索できるようにしました。
また、FBCのアプリ内にて自身で導入したChoices-js を使って、インクリメンタルサーチできるようにしています。

https://user-images.githubusercontent.com/69446373/199777691-240a41ee-6b93-4b27-9883-ecb7166c4ab6.gif

勉強になったこと

omniauthを使ったSNS認証

初めてdevise gemを使わずにSNS認証を実装しました。
上述の匿名性を担保するため、ユーザー登録の際に名前やアイコンを変えるページを1枚挟むことになります。
認証ボタンはサインアップ、サインイン共に同じurlを返すので、どのタイミングでレンダリングさせればいいのか、セッションをどこで発行させるかなどに悩みました。
こちらはフローチャートを作成することで頭の整理ができ、解決することができました。

RailsとVue.jsの連携

FBCのチーム開発にてRailsAPIを作成し、Vue.jsで表示する経験を1度していましたが、環境構築から自分で行い導入していくのは初めてでした。
思うように画像やCSSが反映されないことへの対応や、Vue側でいいね機能を使えるようにすることが難しかったです。
また、チーム開発でVue.jsはバージョン2が使用されていましたが、今回は3を使うことにしたので、書き慣れない部分があり学習しながらの導入となりました。

デザイナーさんとのやりとり

今回のロゴやアイコンのデザインは知り合いのデザイナーさんに依頼しました。
一緒にイラストを作りあげていく体験はとても面白いものでした。
一方でイメージをテキストにするのが難しく、結局対面でやりとりしたことも多かったです😅

今後改善したいこと

本日リリースする前にベータ版としてリリースし、看護師仲間に使ってもらうことでフィードバックを得て修正していました。
おかげさまで、アジャイル開発の「少しずつ作る(=インクリメンタル)」、「少しずつ反復して作る(=イテレーティブ)」を感じ取れた気がします。
ユーザーからの意見は「一緒にサービスをより良くしていこう」という感じがとれて嬉しかったです。ポジティブな気持ちで機能を反映していくことができました。
まだ取り組めてない、今後改善していきたいことは以下の通りです。

人気順にスコープをつけたい

現状では人気順、新着順のみで看護記事を並び替えするようにしていますが、1日、1ヶ月、1年などのスコープをつけた人気順を表示できたらトレンドも追いやすくなるのかなと思います。

匿名いいねができるようにしたい

ユーザー登録をしなくても「いいね」をつけれるようにして、より気軽にサイトを楽しんでいただけたらと思います。

看護記事のタグ選択による記事絞り込み

記事が増えてくると、なかなかお目当ての記事に辿り着けないという状況になってしまうので、記事につけた「循環器」「論文」などのタグをうまく活用して、より読みたい記事を探しやすいようにしていきたいです。

おわりに

看護師さんには、ぜひこのNursePicksを使っていただけたらなと思います。
そして、もしあなたの身の回りに看護師さんがいましたら宣伝していただけると幸いです。

このフォーマットを利用して、ニッチな「〇〇Picks」を作っても面白いなとも思いました。
改善点や要望などありましたら、@garammasala29にDMやリポジトリへのissue登録、プルリクをお願いします。

最後に、リリースに至るまで何度も相談させていただいたり、ペアプロをして下さった@komagataさん、@machidaさん、@cafedomancerさん、ありがとうございました!
加えて、FBCを卒業するまで、メンターの方々や受講生・卒業生のみなさんには大変お世話になりました。
特に日々の輪読会でご一緒させていただいている方々には、感謝してもしきれません。本当にいつもありがとうございます。

残すは就職活動のみとなりましたので、引き続き頑張っていこうと思います!

RubyとRailsの真偽判定まとめ

RubyRailsの真偽判定、存在確認に関するまとめ
見つけ次第追加していく予定

【Rails】左辺値が空文字や空配列の場合の短絡評価 - 832.59の派生記事

Rubyの真偽値

偽:falseまたはnil

真:falseまたはnil以外
  (0,'false',''も真)

Rubyのメソッド

  • nil?nilであればtrueを返す。
nil.nil? #=> true
false.nil? #=> false
  • empty?:文字列では空、配列・ハッシュは要素がない場合にtrueを返し、それ以外のクラスではNoMethodErrorが返る。
"".empty? #=> true
" ".empty? #=> false
[].empty? #=> true
{}.empty? #=> true
nil.empty? #=> NoMethodError 
false.empty? #=> NoMethodError 
  • none?:要素全てが偽の場合にtrueを返す。ブロック評価もできる。
[].none? #=> true
[false, nil].none? #=> true
[false, nil, ''].none? #=> false
[false, nil, 0].none? #=> false
[1, 3, 5].none? {|n| n.even?} #=> true
  • any?:要素の中にひとつでも真があればtrueを返す。こちらもブロック評価可。
[].any? #=> false
[false, nil].any? #=> false
[false, nil, ''].any? #=> true
[1, 3, 5].any? {|n| n.even?} #=> false
  • all?:すべての要素が真である場合にtrueを返す。ブロック評価可。
[].all? #=> true
['false', '', 0].all? #=> true
[1, 3, 5].all? {|n| n.odd?}  #=> true
  • one?:要素の中に1つだけ真がある場合にtrueを返す。ブロック評価可能。引数を用意すると===で評価してくれる。
[nil, false].one? #=> false
[nil, false, ''].one? #=> true
[1, 2, 3, 4].one? {|n| n > 3} #=> true
[1, 2, 3, 4].one? {|n| n > 2} #=> false
[nil, 'hoge', 0].one?(Integer) #=> true
[1, 2, 3, 4].include?(2) #=> true
'garammasala'.include?('masa') #=> true

Railsのメソッド

  • blank?nilfalse、上記のempty?な値に対してtrueを返す。「空白」であるか否か。
''.blank? #=> true
' '.blank? #=> true
[].blank? #=> true
{}.blank? #=> true
nil.blank? #=> true
false.blank? #=> true
true.blank? #=> false
  • present?:空白でない場合。!blank?と同等

  • in?:探している要素がオブジェクトに含まれている場合にtrueを返す。include?の引数とオブジェクトが逆になったパターン

numbers = [1, 2, 3, 4]
2.in?(numbers) #=> true
# Userモデルにガラムマサラ1人が存在すると仮定
User.exists? #=> true
User.exists?(name: 'ガラムマサラ') #=> true
User.exists?(2) #=> false 

present?を使うとレコードを全てDBから取得し配列としてから評価。
exists?ではSQLが exists式を使い、条件に該当するレコードが1件でも存在すればDBがそこで探索を打ち切ってくれるので、ActiveRecordではexists?を使うのがよさそう

ActiveRecord::Type::Boolean.new.cast()という形で、引数をtruefalseにしてくれる。文字列の'false'などもブール値として処理できる
nilnil,''
偽:false,0,"0",:"0","f",:f,"F",:F,"false",:false,
  "FALSE",:FALSE,"off",:off,"OFF",:OFF
真:上記以外のもの

ActiveRecord::Type::Boolean.new.cast(nil) #=> nil
ActiveRecord::Type::Boolean.new.cast('') #=> nil
ActiveRecord::Type::Boolean.new.cast(false) #=> false
ActiveRecord::Type::Boolean.new.cast('f') #=> false
ActiveRecord::Type::Boolean.new.cast(0) #=> false
ActiveRecord::Type::Boolean.new.cast('OFF') #=> false
ActiveRecord::Type::Boolean.new.cast('False') #=> true
ActiveRecord::Type::Boolean.new.cast('Off') #=> true

参考:ActiveRecord::Type::Boolean#cast の返り値が nil となるケースについて - Qiita
実際のRailsのコードも抽出してくれている


ちなみにJavaScriptのfalsyな値
false, undefined, null, 0, 0n, NaN, ""

JS版もいつかまとめれたら
追加推奨のメソッドあればコメントください。


  • 22/11/30 @haruguchiさんにone,include?,in?を教えていただいたので追加しました

Ajax通信とform_withメソッド(開発者ツール、ネットワークの確認方法)

現在、フィヨルドブートキャンブ内にて、@Sakiさん主催の「パーフェクト Ruby on Rails輪読会」が行われています。 こちらの本、大変参考になりますが、なんだか難しいし、もう少し深掘りがしたい!と思いまして、このブログに予習・復習した内容を簡単に載せていきたいなと考えています。

6-4-5

フォームからイベント作成、SJRを利用してエラーメッセージを出そうとしたときに、エラーが出てしまった。

ActionController::UnknownFormat in EventsController#create
EventsController#create is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []

なにやらテンプレートがない。けれども今回はviewファイルを作るわけでもないし、なぜだろう。

原因:フォームにlocale: trueを付けていたからだった。

今回はSJRということでAjaxを利用してPOSTしようとしていた。
コードを書いていく中で、いつもの流れでlocale: trueをつけてしまっていたよう
つけたことで同期通信となっていた。Railsのバージョンは6.0系。

form_withメソッドは、デフォルトでdata-remote="true"がついている。
Ajaxを無効にしたいときにはlocale: trueをつけると同期通信
ちなみに、Railsのバージョンが6.0、5.2、5.1であればform_withのデフォルトはlocal: false(=非同期通信)のよう
(参考:Rails 6.1で form_withのデフォルトが「remoteなし」になった(翻訳)|TechRacho by BPS株式会社


あとはこんなとき、リクエストをDevツールで確認しておけばすぐに解決だった。

失敗時(locale: trueをつけていた)

リクエストヘッダー

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

あまり見慣れない406エラー 。参考ページの406エラーへの対処法と原因の見つけ方には、

サイトのサーバーが誤ったファイル形式を送信したため、受け入れることができません。

どうやらこれっぽい。MIMEタイプの相違が原因。
要求したリソースのMIMEタイプはdocument、 リクエストヘッダーのAcceptにもクライアント側が受信可能なデータ形式に細かく書かれていた。

成功時

リクエストヘッダー

Accept: text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01

ステータスは200ということで成功。
要求したリソースのMIMEタイプはxhr、Ajaxのx。Ajax通信が行われていることがわかる。
Acceptも確認するとJavaScriptファイルを受け取りたいということになっている。

リクエストに関するエラーはネットワークタブで確認。
Devツールでのネットワーク確認方法はChrome DevTools の使い方を参考にした。

Herokuにpushしたらdb:migrateしよう(HerokuのRelease Phaseについても)

自作サービスで学んだことメモ

git push heroku mainをしたら、本番環境のデータが全部消えてまっさらなページになってしまっている。

heroku pg:psqlでDBの内容を確認。

SELECT * FROM users;してみるとデータあり。よしデータは消えていない!けどなんで?

今回pushした内容はというと、acts-as-taggable-onで記事にタグをつけられるようにした(= tagsテーブルとtaggingsテーブルを作った)

rails db:migrate:statusでDBを確認すると、Statusがdownだらけ。

heroku run rails db:migrateとすると、本番環境でデータが反映された!

(ちなみに自分の環境ではheroku runコマンドが実行できずに、Thor::Invocationエラーがでたのでheroku run bash -a <appname>bashを使用する方法、もしくはheroku run bin/rails db:migrateとする方法で解決した。このエラーは未解決で、修正できたら別記事にしたい。)

よくよく見たらRailsチュートリアルでも、Userモデルを追加した際にマイグレーションしていたようだ。

Heroku上でもマイグレーションを走らせる必要があった。またひとつ学んだ。


そして、マイグレーション忘れやコマンド入力の手間を省くため、Release Phasedb:migrateするように設定。

Release Phase(リリースフェーズ)とはアプリのビルド終了後、リリースする直前に任意のコマンドが実行できるというもの

Procfileに

release: bin/rails db:migrate

とするだけでHeroku側でマイグレーションしてくれる。

ちなみにRelease Phase中になんらかの処理が失敗した場合にはデプロイが中止されるため、リリースに必須な処理を挟み込む際にも安心して利用できるとのことでした。

参考記事

【Rails】左辺値が空文字や空配列の場合の短絡評価

自作サービスで学んだことメモ

postオブジェクトのimage属性を追加したい。
ogpの画像URLがあればそれを。なければdefault_imageにしようというもの。

post.image =  page.meta['og:image'] || 'default_image'

としていたが、||は左辺値がnilかfalse以外は真。
このサイトのogpの画像URLは空文字''だったので、post.imageには''が代入されしまっていた。

Rails における nil?, empty?, blank?, present? の使い分けとBetter Practice - Qiita
より、Railsでは空文字判定にblank?が便利。
(empty?だと、レシーバ(page)がnilになるとエラーが返る)

さっそくblank?を使ってみたけど短絡評価は使えず、三項演算子だと長くなる。

post.image = if page.meta['og:image'].blank?
               'default_image'
             else
               page.meta['og:image']
             end

rubocopをかけたら

post.image = page.meta['og:image'].presence || 'default_image'

presenceメソッドは存在すればそれ自身を、なければnilを返す
ありがとうrubocop

StimulusのDataMaps

現在、フィヨルドブートキャンブ内にて、@Sakiさん主催の「パーフェクト Ruby on Rails輪読会」が行われています。 こちらの本、大変参考になりますが、なんだか難しいし、もう少し深掘りがしたい!と思いまして、このブログに予習・復習した内容を簡単に載せていきたいなと考えています。

4-4-4

DataMapsはStimulusで状態管理をしたい時に使う(DOMで状態を持つ)

HTMLからJSへ、JSからHTMLへデータを渡すときに便利

data-コントローラ名-データ名でデータを定義

<div data-controller='hello' data-hello-index='0'></div>

定義したデータはthis.data.get(データ名)で取得可

import { Controller } from 'stimulus'

export default class extends Controller {
  connect() {
    fetch(this.data.get('index'))
  }
}

これでindexの情報を取得でき、変更を加えたりすることができる

このget以外にもhassetdeleteなどのメソッドがある

Stimulus公式ページのサンプルの流れ

現在、フィヨルドブートキャンブ内にて、@Sakiさん主催の「パーフェクト Ruby on Rails輪読会」が行われています。 こちらの本、大変参考になりますが、なんだか難しいし、もう少し深掘りがしたい!と思いまして、このブログに予習・復習した内容を簡単に載せていきたいなと考えています。

4-4

Stimulus: A modest JavaScript framework for the HTML you already have.

控えめなJavaScriptとして紹介されている。ページの一部だけを動的に動かせられるのがメリットのよう。

ReactやVueなどのJSフレームワークのようにフロントエンド側でHTMLを返すのではなく、サーバー側でHTMLを返せる。公式サイトではHTMLの強化とも書かれている。

HTMLのdata-controllerdata-actiondata-targetを通じてJS側に結びついている



追記:data-targetの書き方がVer2.0から変わっているようだ

Release v2.0.0 · hotwired/stimulus

サンプルの例だとdata-target="hello.name"からdata-hello-target="name"とコントローラ名をdataとtargetに挟む形になった