- 投稿日:2021-01-29T23:52:31+09:00
【独学・未経験】Nuxt.js, Rails, Docker, AWS ECS(Fargate), TerraformなSPAポートフォリオを作成しました。
はじめに
プログラミング歴半年(独学)の実務未経験者がSPAなポートフォリオを制作しましたので紹介していきたいと思います!
今後もアップデートしていくのでフィードバックなど頂けますと嬉しいです。
記事の最後には、お世話になったWebサイトや教材をまとめておきましたので参考になれば幸いです。作者のスペック
年齢は27歳で今までにプログラミング経験は全くなし。
サーバーサイドエンジニアを目指してプログラミング学習中の初学者です。
本記事を執筆している時点でプログラミング学習期間は半年。(2021/1/29時点)
ポートフォリオに関わる技術のキャッチアップをしながら約4ヶ月程かけて完成させました。ポートフォリオ制作に着手した時点では、ProgateとRailsチュートリアルを終えた程度の実力。
至る所に詰みポイントがありひたすらググりまくって問題解決してました。(Google先生は神)アプリの概要
制作したアプリケーションがこちら。
『ガジェコミ!』はガジェット好きが集まれるSNS型のWebアプリケーションです。
簡単に言ってしまえば『Twitterクローン』に近いものなので、SNSに必要な機能を重点的に実装済み。
スマホ利用を想定しているのでモバイルからも気軽にお試し下さい!(ゲストログイン有ります)
トップページ
レスポンシブ対応
スマホ利用も想定したUI設計。
Ajax処理
ネストした要素でもAjaxに動作します。
例) 『コメントアイコンをタップ → コメント欄を表示 → 既にあるコメントに返信』
ポートフォリオに使用した技術
各バージョン
- Ruby 2.7.1
- Rails 6.0.3.4
- nuxt 2.14.6
- @nuxtjs/vuetify 1.11.2
- Docker 19.03.13
- docker-compose 1.27.4
- Terraform 0.14.3
フロントエンド
名称 説明 Nuxt.js (SSR mode) フロントエンドフレームワーク Vuetify UIコンポーネント Firebase Authentication JWTを用いたログイン・ログアウト
Vuetifyコンポーネントを導入することで、スマホ利用を想定したレスポンシブデザインを実現。
Vuexストアでステート管理。
個人情報(メールアドレス・パスワード)は、外部API(Firebase Authentication)に保存する仕組みで、外部APIで発行されるJWTをSession Storageに保存してログイン・ログアウト機能を実装しました。
未ログイン状態でアクセスして欲しくないページ( /users/editなど )へのアクセス対策には、Nuxt.jsのmiddlewareを活用することで自動的にリダイレクトするようにしました。
ログイン状態でアクセスして欲しくないページ (ログインページ, 新規作成ページ) へのアクセスも同じくリダイレクトします。
バックエンド
名称 説明 Rails (API mode) APIサーバーとして利用 PostgreSQL データベース
RailsはAPIサーバーとして利用しており、フロントエンドコンテナからのリクエストに対してJSONデータを返しています。
画像データはActiveStorage経由でS3バケットに保存。
その他のデータはRDSに保存。
個人情報(メールアドレス・パスワード)は外部API(Firebase Authentication)に保存しているので、バックエンド側にセンシティブな情報を保存しない仕組みです。
テストコード
名称 説明 Jest フロントエンドテスト, Vuexストアの動作を少しだけ RSpec バックエンドテスト, バリデーションとアソシエーションのテストのみ
- CodePipelineのTestステージで実行するテストコードです。
インフラ
名称 説明 ECS Fargate サーバーレスな本番環境, オートスケール CodePipeline CI/CD環境 RDS 本番用DB(PostgreSQL) Docker, Docker-compose コンテナ環境 Github バージョン管理 Terraform 本番用インフラをコード管理
ローカル開発環境からデプロイまで一貫してDockerを使用。
ALBを通すことで常時SSL通信化。
CodePipelineは、 『Sourceステージ => Testステージ => Buildステージ => Deployステージ』の順で実行され、Testステージで問題が発生した場合は当該ソースでのDeployは実行されません。
本番環境の環境変数については SSM で管理。『システム環境変数 => Terraform => SSM => 各AWSサービス』というフローで環境変数を受け渡しています。
インフラ構成図
インフラの全体像をまとめたものがこちら。
RDSにはPostgreSQLを採用し、画像を除いたデータを保管。
画像データはS3に保管しています。ER図
一貫性のあるテーブル名称を意識しました。
アプリの機能紹介
0. 機能一覧
機能名 説明 ユーザー機能 新規登録、登録内容変更、アバター登録、ログイン、ログアウト、フォロー つぶやき機能 投稿、編集、削除、画像複数枚登録 つぶやきコメント機能 つぶやきに対してコメント投稿、コメントへのリプライ投稿、削除、画像複数枚登録 つぶやきいいね機能 つぶやきをいいねできる、マイページのいいねタブにいいねしたつぶやきを一覧表示 掲示板機能 質問掲示板、雑談掲示板の作成、編集、削除、画像複数枚登録 掲示板コメント機能 掲示板に対してコメント投稿、コメントへのリプライ投稿、削除、画像複数枚登録 私物ガジェット機能 登録、編集、削除、画像複数枚登録 フィード機能 つぶやき新着表示、タイムライン表示、タグフィード表示 タグ管理機能 つぶやき・掲示板・私物ガジェットにはタグを登録可能、タグをタップしてタグ詳細ページを表示、タグ詳細ページではタグを含むコンテンツを表示 検索機能 各コンテンツを検索可能 通知機能 つぶやきにいいね・コメント、掲示板にコメント、他ユーザーからフォローされると通知を表示 管理者モード Godmode のトグルスイッチをONにすると、一時的に管理者権限が有効化、各コンテンツの削除メニューが表示される 1. ユーザー機能
登録・編集・削除
メールアドレスとパスワードを入力するだけでアカウント作成できます。
メールアドレス確認機能は未実装。VeeValidateを使用してフォームにバリデーションを設定しています。
詳細設定ページで登録情報を編集できます。
ユーザーにはアバターを設定できます。
アバター画像は各コンテンツで表示されます。
2. つぶやき機能
投稿・編集・削除
つぶやき編集は即時反映されます。
つぶやき削除は即時反映されます。
タグ付け
つぶやきにはタグを複数登録できます。
コメント
つぶやきにはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。いいね
つぶやきにいいねできます。
3. 掲示板機能
作成・編集・削除
掲示板作成は即時反映されます。
掲示板編集は即時反映されます。
掲示板削除は即時反映されます。
タグ付け
掲示板にはタグを複数登録できます。
掲示板タイプ
『雑談』『質問』を選択できます。
コメント
掲示板にはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。4. 私物ガジェット機能
登録・編集・削除
自分の私物ガジェットやお気に入りアイテムを登録できます。
登録したガジェットは一覧ページでは『みんなのガジェット』として新着表示されます。ガジェット編集は即時反映されます。
ガジェット削除は即時反映されます。
タグ付け
私物ガジェットにはタグを複数登録できます。
5. フィード機能
つぶやきを一覧表示します。
無限スクロールで順次読み込みます。<タブによって表示する内容が異なります>
- 新着順 => 全てのつぶやきを新着順で表示
- タイムライン => フォロー済みユーザーのつぶやきのみ新着順で表示
- タグフィード => フォロー済みタグを含むつぶやきのみ新着順で表示
6. タグ管理機能
タグをフォローすることができます。
タグフィードに表示したいタグをフォローします。つぶやき・掲示板・私物ガジェットはタグ付けすることができます。
タグをタップするとタグ詳細ページに遷移し、タグを含むつぶやき・掲示板・私物ガジェット・フォローしているユーザーの一覧を表示します。7. 検索機能
検索ページでは、検索フォームに入力した内容に合わせて結果がリアルタイムに表示される機能を実装しております。
8. 通知機能
つぶやき・掲示板・ユーザーフォローにおいて、イベントが発生すると通知が表示されます。
新着通知
新しい通知が発生すると、ヘッダー内のベルアイコンにバッジが表示されます。
タップすると新しい通知がリスト表示され、通知をタップするとイベント発生元へ画面遷移します。通知一覧
通知一覧をタップすると一覧ページへ画面遷移します。
通知一覧ページでは、今まで受け取った全ての通知を表示します。9. ゲストユーザー機能
ユーザー専用機能を簡単に利用できるように、ゲストユーザーログイン機能を実装しています。
『ゲストユーザーとしてログイン』をタップするだけです。
未ログイン状態でユーザー専用機能にアクセスすると、ゲストユーザーログインを促すダイアログを表示するようにしております。
10. God mode
一時的に管理者権限を有効にするモードです。(管理者という概念が存在することを確認して頂くためのモードです。)
DB内 の Userモデルの adminカラム の true/false で 各ユーザーの管理者権限を管理しておりますが、God modeではVuexストアの値を一時的に変更して管理者権限を有効化しています。(DB上には保存されないのでブラウザ再読み込みでfalseに戻ります。)
つぶやき・掲示板・私物ガジェットにはそれぞれ『管理メニュー』が設定されており、作成者本人にのみ表示される仕組みです。
God mode を有効化にすると他のユーザーが作成したコンテンツでも『管理メニュー』を表示するようになり、『削除のみ』実行可能となります。
制作を終えてみて
ポートフォリオのアップデートは今後も行いますが、いったん区切りがついたのでホッとしています。
制作中に苦労したポイントをまとめてみると以下の内容で、主に初期学習のタイミングで壁が何度も現れました。
壁 内容 フロントエンドとバックエンドを分離した開発環境の構築 仮想コンテナの基礎知識やポートフォワーディングでつまりました。初めからdocker-composeを使うことをオススメします。 環境変数の受け渡し ローカル環境、本番環境のインフラ構成に合わせて環境変数の渡し方に工夫が必要でした。 Vuetifyモジュールの導入 Nuxt.jsの初期セットアップでVuetifyを選択するとエラー発生。VuetifyなしでNuxt.jsをセットアップ + 後からVuetifyを導入 + おまじないが必要で詰みました。 SSRモードで動作するNuxt.jsのライフサイクル SSRでSPA用コードを実行するとライフサイクルが一部違うので正常に動作しません。 AxiosモジュールのSSRモードでの挙動 SPAとSSRではAxiosのエンドポイントの設定に違いがあり詰みました。 Firebase Authenticationの導入 SSRモードでの解説記事が少なく、動作させるまでに苦労しました。 SSRモードでは動作しないプラグイン ポートフォリオで利用した『@johmun/vue-tags-input』がSSRモードでは動作しなかったので、特別なおまじないが必要でした。 親コンポーネントと子コンポーネントのデータ受け渡し props, emitの概念の理解に苦戦しました。 Vuexストアの扱い方 断片的な解説記事が多くて理解するのに時間が掛かりました。 終わらないN+1対策 アソシエーションの絡んだデータ取得をするたびに苦労しました。 ECS Fargateでのインフラ構築 FargateはSSHできないのでエラーログの取得やトラブルシュートに手間取りました。CloudWatchなどを利用して確実にログを取れる環境を作らないと、どこに問題があってコンテナが動いていないのか判断できなくて詰みます。 特に厳しかったのがECSデプロイでして、途中何度も諦めそうになりましたが何とかやり終えることができました...。(インフラ難しい)
何とか形になったものの、書いているコードは実務レベルから比べると『その場しのぎ』程度だと思いますし、
プログラミングにおける基礎知識が抜けていると思い知らされる場面も多々ありました。(特に学習初期の理解力)正直なところ、Nuxt.js や Rails という『ツール』を何となく使えている、というのが現時点での実力だと思います。
今後について
ポートフォリオが完成したので就活を始めるために準備を進めています!
就活以外では、課題である基礎力を伸ばすために基本情報技術者やJavaSliverなどの取得を目指して資格勉強も進めていくつもりです。(オススメ教材などあれば教えて欲しいです)
ただのポートフォリオ紹介記事ですが、最後までお付き合い頂きましてありがとうございました!
Twitterもやってますので是非フォローもよろしくお願いします!
関連リンク
お世話になった書籍や教材
ポートフォリオ制作中に何度もお世話になった資料一覧です。
これがなければ完成できていなかったので本当に感謝しています。ありがとうございました!
媒体 資料名 学習内容 Web Progate Ruby, Rails, HTML, CSS, JavaScript...などの基礎を学習 Web Railsチュートリアル いわずもがな、Railsの基礎、Herokuデプロイ、AWS S3、GitHubの扱いなどを学習 Web JavaScript Primer DOM操作以外のJavaScriptの基礎を学習 Web Rails + Nuxt.js + Docker なアプリを作ってみよう 神記事です。めちゃめちゃ参考になりました。ただしSPAモードなのでSSRモードで制作する場合は工夫が必要。 Web 独学プログラマ (Docker+Rails6+Nuxt.js+PostgreSQL)=>Heroku 環境構築~デプロイまでの手順書 神記事です。特に環境構築の手法がめちゃめちゃ参考になります。 Web Pikawaka Railsの基礎はここ見れば大体全部載ってます。マジでわかりやすくて助かりました。 Web SSRモードのNuxtでのFirebase認証 この記事とGitHubリポジトリがなければ実装するの諦めてました。本当にありがとうございます。 書籍 たのしいRuby Rubyの基礎を学習 書籍 Ruby on Rails速習実践ガイド Railsでよく使うコマンドやRailsの基礎を学習 書籍 プロを目指す人のためのRuby入門 実践的なRubyについて学習 書籍 Rubyによるクローラー開発技法 Rubyだけでスクレイピングアプリを作れます。学んだ基礎知識をアウトプットするために使用しました。 書籍 Nuxt.jsビギナーズガイド Nuxt.jsの基礎学習。Vue.jsの経験がないと難しいかも。 書籍 速習Vue.js3 Vue.jsの基礎学習。単一コンポーネント形式のサンプルが少ないので、これだけでは不十分かも。 書籍 実践Terraform AWSにおけるシステム設計とベストプラクティス 神本。これがなければECSデプロイできませんでした。感謝しています。 書籍 はじめての人のためのTerraform for AWS Terraformを全く触ったことない人向けの書籍。サンプルコードがちゃんと動作するので感じを掴むのに最適。 書籍 CircleCI実践入門 CI/CDってなに?って状態で感じを掴むために使用。結局CodePipelineで実装したけど自動テストの雰囲気が掴めて良かった。 書籍 Amazon Web Services 基礎からのネットワーク&サーバー構築 AWSの基礎学習にぴったり。EC2での解説なのでECSについては別途学習する必要あり。 書籍 Docker/Kubernetes実践コンテナ開発入門 Dockerの基礎学習。初心者向けの章節まで読めば十分でした。 書籍 達人に学ぶSQL徹底至難書 実践的なSQLを学習。 書籍 Webを支える技術 ネットワークの概念がイマイチ理解できてないときに使用。 書籍 GitHub実践入門 GitHubの基礎学習
- 投稿日:2021-01-29T23:52:31+09:00
【独学・未経験】Nuxt.js x Railsな完全SPAポートフォリオを制作しました!
概要
プログラミング歴半年(独学)の実務未経験者が完全SPAなポートフォリオを制作しましたので紹介していきたいと思います!
記事の最後には初学者が詰まるであろうポイントもまとめておきましたので、参考になれば幸いです。作者のスペック
年齢は27歳(今年で28歳)で今までにプログラミング経験はありませんでした。
今現在はエンジニアを目指して独学で学習中でして、本記事を執筆している時点でプログラミング学習期間は半年間です。
ポートフォリオに関わる技術のキャッチアップをしながら約4ヶ月程かけてポートフォリオを完成させました。ポートフォリオ制作に着手した時点では、ProgateとRailsチュートリアルを終えた程度の実力。
至る所に詰みポイントがありひたすらググりまくって問題解決してました。(Google先生は神)ポートフォリオ紹介
『ガジェコミ!』はガジェット好きが集まれるSNS型のWebアプリケーションです。
Nuxt.js x Rails という組み合わせて制作した完全SPAアプリケーションなので、同じような構成で制作される方の参考になれば幸いです!
ガジェコミ!
ポートフォリオに使用した技術
フロントエンド
- Nuxt.js(SSR mode) => フロントエンドフレームワーク
- Vuetify => UIコンポーネント
- Firebase Authentication => JWTを用いたログイン・ログアウト
バックエンド
- Rails(API mode) => APIサーバーとして実行
- PostgreSQL => データベース
テストコード
- フロントエンド => Jest(Vuexストアの動作を少しだけ)
- バックエンド => RSpec(バリデーションとアソシエーションのテストのみ)
インフラ
- AWS ECS Fargate => サーバーレスな本番環境、オートスケール
- AWS CodePipeline => CI/CD環境
- AWS RDS => 本番用DB(PostgreSQL)
- AWS S3 => 本番環境の画像データ保存用 x 1, CodePipelineのアーティファクト保存用 x 1
- AWS Route53 => ドメイン取得
- AWS ACM => SSL証明書の発行
- AWS ALB => ロードバランサー
- AWS ECR => 本番用Dockerイメージ管理
- AWS SSM => 本番用環境変数の一括管理
- Docker, Docker-compose => コンテナ環境
- Github => バージョン管理
- Terraform => 本番用インフラをコード管理
インフラ構成図
ER図
技術を選定した理由
- 採用担当者の目に留まるようなポートフォリオを制作するため
- 自分自身がプログラミングが好きかどうかを推し量るために、あえて高難度な技術を選定した
- 主流となりつつある『フロントエンドとバックエンドを切り離したWebアプリケーション』を制作してみたかったから
- 完全SPAアプリケーションの制作を経験してみたかったから
- 未経験な技術のキャッチアップを独学でどのように進めるか身を以て経験したおきたかったから
アプリの機能紹介
一般的なSNS型アプリケーションを意識して制作しているので、複雑なビジネスロジックが絡むような機能は残念ながら実装できておりません...。
とはいえレスポンシブ対応やAjax化に力を入れているので、実際に使ってみてください!
1. ユーザー機能
登録・編集・削除
メールアドレスとパスワードを入力するだけでアカウント作成できます。
メールアドレス確認機能は未実装。VeeValidateを使用してフォームにバリデーションを設定しています。
詳細設定ページで登録情報を編集できます。
ユーザーにはアバターを設定できます。
アバター画像は各コンテンツで表示されます。
2. つぶやき機能
投稿・編集・削除
つぶやき編集は即時反映されます。
つぶやき削除は即時反映されます。
タグ付け
つぶやきにはタグを複数登録できます。
コメント
つぶやきにはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。いいね
つぶやきにいいねできます。
3. 掲示板機能
作成・編集・削除
掲示板作成は即時反映されます。
掲示板編集は即時反映されます。
掲示板削除は即時反映されます。
タグ付け
掲示板にはタグを複数登録できます。
掲示板タイプ
『雑談』『質問』を選択できます。
コメント
掲示板にはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。4. 私物ガジェット機能
登録・編集・削除
自分の私物ガジェットやお気に入りアイテムを登録できます。
登録したガジェットは一覧ページでは『みんなのガジェット』として新着表示されます。ガジェット編集は即時反映されます。
ガジェット削除は即時反映されます。
タグ付け
私物ガジェットにはタグを複数登録できます。
5. フィード機能
つぶやきを一覧表示します。
無限スクロールで順次読み込みます。<タブによって表示する内容が異なります>
- 新着順 => 全てのつぶやきを新着順で表示
- タイムライン => フォロー済みユーザーのつぶやきのみ新着順で表示
- タグフィード => フォロー済みタグを含むつぶやきのみ新着順で表示
6. タグ管理機能
タグをフォローすることができます。
タグフィードに表示したいタグをフォローします。つぶやき・掲示板・私物ガジェットはタグ付けすることができます。
タグをタップするとタグ詳細ページに遷移し、タグを含むつぶやき・掲示板・私物ガジェット・フォローしているユーザーの一覧を表示します。7. 検索機能
検索ページでは、検索フォームに入力した内容に合わせて結果がリアルタイムに表示される機能を実装しております。
8. 通知機能
つぶやき・掲示板・ユーザーフォローにおいて、イベントが発生すると通知が表示されます。
新着通知
新しい通知が発生すると、ヘッダー内のベルアイコンにバッジが表示されます。
タップすると新しい通知がリスト表示され、通知をタップするとイベント発生元へ画面遷移します。通知一覧
通知一覧をタップすると一覧ページへ画面遷移します。
通知一覧ページでは、今まで受け取った全ての通知を表示します。9. ゲストユーザー機能
ユーザー専用機能を簡単に利用できるように、ゲストユーザーログイン機能を実装しています。
『ゲストユーザーとしてログイン』をタップするだけです。
未ログイン状態でユーザー専用機能にアクセスすると、ゲストユーザーログインを促すダイアログを表示するようにしております。
10. God mode
一時的に管理者権限を有効にするモードです。(管理者という概念が存在することを確認して頂くためのモードです。)
DB内 の Userモデルの adminカラム の true/false で 各ユーザーの管理者権限を管理しておりますが、God modeではVuexストアの値を一時的に変更して管理者権限を有効化しています。(DB上には保存されないのでブラウザ再読み込みでfalseに戻ります。)
つぶやき・掲示板・私物ガジェットにはそれぞれ『管理メニュー』が設定されており、作成者本人にのみ表示される仕組みです。
God mode を有効化にすると他のユーザーが作成したコンテンツでも『管理メニュー』を表示するようになり、『削除のみ』実行可能となります。
11. レスポンシブデザイン
Vuetifyを活用したレスポンシブデザインで、スマホでも扱いやすいUI/UXを意識しました。
サブメニューとタブメニューを組み合わせることで、画面遷移ゼロで複数のコンテンツにアクセスできます。
技術面での詳解
フロントエンド
Nuxt.js を採用し、フロントエンドとバックエンドを分離した完全SPAアプリケーションを実現しております。
Nuxt.jsは、SEO面で有効とされるSSRモード(サーバーサイドレンダリング)で実行しております。
Vuetifyコンポーネントを導入することで、スマホ利用を想定したレスポンシブデザインを実現しています。
Vuexストアでステート管理をしており、ほぼ全ての動作をAjax化しました。(一部未完)
個人情報(メールアドレス・パスワード)の安全性については、外部API(Firebase Authentication)に任せることで確保しています。
Firebase Authentication で発行されるJWTをブラウザのSession Storageに保管することで、ログイン・ログアウトができる仕組みです。
未ログイン状態でアクセスして欲しくないページ( /users/editなど )へのアクセス対策には、Nuxt.jsのmiddlewareを活用することで自動的にリダイレクトするようにしました。
ログイン状態でアクセスして欲しくないページ (ログインページ, 新規作成ページ) へのアクセスも同じくリダイレクトします。
バックエンド
Rails(APIモード)で実行しており、フロントエンドコンテナからのリクエストに対してJSONデータを返しています。
個人情報(メールアドレス・パスワード)は外部API(Firebase Authentication)に保存しているので、バックエンド側にセンシティブな情報を保存しない仕組みです。
『N+1問題』への対策を施しているため、アソシエーションがネストしているコンテンツへアクセスしてもレスポンスが遅れません。
一貫性のあるテーブル名称を意識しました。
インフラ
ローカル開発環境から一貫してDockerを使用しており、ECSデプロイまでを想定した開発フローを実践しました。
AWS ECS(Fargate)で本番環境をサーバーレスで運用しております。
ALBを通すことで常時SSL通信を実現しました。
AWS CodePipelineを使用したCI/CDパイプラインを構築しております。
CodePipelineは、 『Sourceステージ => Testステージ => Buildステージ => Deployステージ』の順で実行され、Testステージで問題が発生した場合は当該ソースでのDeployは実行されません。
Terraformを用いて AWSの本番環境は全てコードで管理しています。
本番環境の環境変数については AWS SSM で管理しており、『システム環境変数 => Terraform => SSM => 各AWSサービス』というフローで環境変数を受け渡しています。
使用した教材
Progate
ドットインストール
Railsチュートリアル
書籍
Google先生
Githubのソースコード(チュートリアル用アプリケーションのソースを読んでました。)
バージョン
Ruby 2.7.1
Rails 6.0.3.4
nuxt 2.14.6
@nuxtjs/vuetify 1.11.2
Docker 19.03.13
docker-compose 1.27.4
Terraform 0.14.3
つまったポイント
フロントエンドとバックエンドを分離した開発環境の構築
=> 仮想コンテナの基礎知識やポートフォワーディングでつまりました。
=> 初めからdocker-composeを使うことをオススメします。環境変数の受け渡し
=> 渡せているはずなのに!という場面が何度もありました。各ソフトの仕様を把握していないと詰みます。SSRモードで動作するNuxt.jsのライフサイクル
=> SSRでSPA用コードを実行するとライフサイクルが一部違うので正常に動作しません。SSRモードでは動作しないプラグイン
=> ポートフォリオで利用した『@johmun/vue-tags-input』がSSRモードでは動作しなかったので、特別なおまじないが必要でした。Vuexストアの扱い方
=> 断片的な解説記事が多くて理解するのに時間が掛かりました。親コンポーネントと子コンポーネントのデータ受け渡し
=> Vue.jsの経験がある方であれば問題ないかと思います。Vuetifyモジュールの導入
=> Nuxt.jsの初期セットアップでVuetifyを選択するとエラー発生
=> VuetifyなしでNuxt.jsをセットアップ + 後からVuetifyを導入 + おまじないが必要で詰みました。AxiosモジュールのSSRモードでの挙動
=> SPAとSSRではAxiosのエンドポイントの設定に違いがあり詰みました。ECS Fargateでのインフラ構築
=> FargateはSSHできないのでエラーログの取得やトラブルシュートに手間取りました。
=> CloudWatchなどを利用して確実にログを取れる環境を作らないと、どこに問題があってコンテナが動いていないのか判断できなくて詰みます。制作を終えてみて
ポートフォリオのアップデートは今後も行いますが、いったん区切りがついたのでホッとしています。
特に厳しかったのがECSデプロイでして、途中何度も諦めそうになりましたが何とかやり終えることができました...。(インフラ難しい)
苦労した分だけ喜びも大きくなると言われますが、自作アプリにアクセスできた瞬間は嬉しかったですね!
ただし、書いているコードは実務レベルから比べると『その場しのぎ』程度だと思いますし、
プログラミングにおける基礎知識が抜けていると思い知らされる場面も多々ありました。(特に学習初期の理解力)正直なところ、Nuxt.js や Rails という『ツール』を何となく使えている、というのが現時点での実力だと思います。
今後について
ポートフォリオが完成したので就活を始めていきます。
課題である基礎力を伸ばすために、就活と同時に基本情報技術者やJavaSliverなどの取得を目指して資格勉強も進めていくつもりです。
コロナ渦で未経験からエンジニアになることが難しいと言われてますし、引き続き気合を入れて頑張っていきたいと思います。
ただのポートフォリオ紹介記事ですが、最後までお付き合い頂きましてありがとうございました!
Twitterもやってますので是非フォローもよろしくお願いします!
関連リンク
- 投稿日:2021-01-29T23:52:31+09:00
【独学・未経験】Nuxt.js, Rails, Docker, AWS ECS(Fargate), Terraformな完全SPAポートフォリオを制作しました!
概要
プログラミング歴半年(独学)の実務未経験者が完全SPAなポートフォリオを制作しましたので紹介していきたいと思います!
記事の最後には初学者が詰まるであろうポイントもまとめておきましたので、参考になれば幸いです。作者のスペック
年齢は27歳(今年で28歳)で今までにプログラミング経験はありませんでした。
今現在はエンジニアを目指して独学で学習中でして、本記事を執筆している時点でプログラミング学習期間は半年間です。
ポートフォリオに関わる技術のキャッチアップをしながら約4ヶ月程かけてポートフォリオを完成させました。ポートフォリオ制作に着手した時点では、ProgateとRailsチュートリアルを終えた程度の実力。
至る所に詰みポイントがありひたすらググりまくって問題解決してました。(Google先生は神)ポートフォリオ紹介
『ガジェコミ!』はガジェット好きが集まれるSNS型のWebアプリケーションです。
Nuxt.js x Rails という組み合わせて制作した完全SPAアプリケーションなので、同じような構成で制作される方の参考になれば幸いです!
ガジェコミ!
ポートフォリオに使用した技術
フロントエンド
- Nuxt.js(SSR mode) => フロントエンドフレームワーク
- Vuetify => UIコンポーネント
- Firebase Authentication => JWTを用いたログイン・ログアウト
バックエンド
- Rails(API mode) => APIサーバーとして実行
- PostgreSQL => データベース
テストコード
- フロントエンド => Jest(Vuexストアの動作を少しだけ)
- バックエンド => RSpec(バリデーションとアソシエーションのテストのみ)
インフラ
- AWS ECS Fargate => サーバーレスな本番環境、オートスケール
- AWS CodePipeline => CI/CD環境
- AWS RDS => 本番用DB(PostgreSQL)
- AWS S3 => 本番環境の画像データ保存用 x 1, CodePipelineのアーティファクト保存用 x 1
- AWS Route53 => ドメイン取得
- AWS ACM => SSL証明書の発行
- AWS ALB => ロードバランサー
- AWS ECR => 本番用Dockerイメージ管理
- AWS SSM => 本番用環境変数の一括管理
- Docker, Docker-compose => コンテナ環境
- Github => バージョン管理
- Terraform => 本番用インフラをコード管理
インフラ構成図
ER図
技術を選定した理由
- 採用担当者の目に留まるようなポートフォリオを制作するため
- 自分自身がプログラミングが好きかどうかを推し量るために、あえて高難度な技術を選定した
- 主流となりつつある『フロントエンドとバックエンドを切り離したWebアプリケーション』を制作してみたかったから
- 完全SPAアプリケーションの制作を経験してみたかったから
- 未経験な技術のキャッチアップを独学でどのように進めるか身を以て経験したおきたかったから
技術面での詳解
フロントエンド
Nuxt.js を採用し、フロントエンドとバックエンドを分離した完全SPAアプリケーションを実現しております。
Nuxt.jsは、SEO面で有効とされるSSRモード(サーバーサイドレンダリング)で実行しております。
Vuetifyコンポーネントを導入することで、スマホ利用を想定したレスポンシブデザインを実現しています。
Vuexストアでステート管理をしており、ほぼ全ての動作をAjax化しました。(一部未完)
個人情報(メールアドレス・パスワード)の安全性については、外部API(Firebase Authentication)に任せることで確保しています。
Firebase Authentication で発行されるJWTをブラウザのSession Storageに保管することで、ログイン・ログアウトができる仕組みです。
未ログイン状態でアクセスして欲しくないページ( /users/editなど )へのアクセス対策には、Nuxt.jsのmiddlewareを活用することで自動的にリダイレクトするようにしました。
ログイン状態でアクセスして欲しくないページ (ログインページ, 新規作成ページ) へのアクセスも同じくリダイレクトします。
バックエンド
Rails(APIモード)で実行しており、フロントエンドコンテナからのリクエストに対してJSONデータを返しています。
個人情報(メールアドレス・パスワード)は外部API(Firebase Authentication)に保存しているので、バックエンド側にセンシティブな情報を保存しない仕組みです。
『N+1問題』への対策を施しているため、アソシエーションがネストしているコンテンツへアクセスしてもレスポンスが遅れません。
一貫性のあるテーブル名称を意識しました。
インフラ
ローカル開発環境から一貫してDockerを使用しており、ECSデプロイまでを想定した開発フローを実践しました。
AWS ECS(Fargate)で本番環境をサーバーレスで運用しております。
ALBを通すことで常時SSL通信を実現しました。
AWS CodePipelineを使用したCI/CDパイプラインを構築しております。
CodePipelineは、 『Sourceステージ => Testステージ => Buildステージ => Deployステージ』の順で実行され、Testステージで問題が発生した場合は当該ソースでのDeployは実行されません。
Terraformを用いて AWSの本番環境は全てコードで管理しています。
本番環境の環境変数については AWS SSM で管理しており、『システム環境変数 => Terraform => SSM => 各AWSサービス』というフローで環境変数を受け渡しています。
アプリの機能紹介
一般的なSNS型アプリケーションを意識して制作しているので、複雑なビジネスロジックが絡むような機能は残念ながら実装できておりません...。
とはいえレスポンシブ対応やAjax化に力を入れているので、実際に使ってみてください!
1. ユーザー機能
登録・編集・削除
メールアドレスとパスワードを入力するだけでアカウント作成できます。
メールアドレス確認機能は未実装。VeeValidateを使用してフォームにバリデーションを設定しています。
詳細設定ページで登録情報を編集できます。
ユーザーにはアバターを設定できます。
アバター画像は各コンテンツで表示されます。
2. つぶやき機能
投稿・編集・削除
つぶやき編集は即時反映されます。
つぶやき削除は即時反映されます。
タグ付け
つぶやきにはタグを複数登録できます。
コメント
つぶやきにはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。いいね
つぶやきにいいねできます。
3. 掲示板機能
作成・編集・削除
掲示板作成は即時反映されます。
掲示板編集は即時反映されます。
掲示板削除は即時反映されます。
タグ付け
掲示板にはタグを複数登録できます。
掲示板タイプ
『雑談』『質問』を選択できます。
コメント
掲示板にはコメントすることができます。
コメントに返信できます。
コメントは削除のみ可能です。編集機能はありません。
親コメントを削除すると子コメントも削除されます。4. 私物ガジェット機能
登録・編集・削除
自分の私物ガジェットやお気に入りアイテムを登録できます。
登録したガジェットは一覧ページでは『みんなのガジェット』として新着表示されます。ガジェット編集は即時反映されます。
ガジェット削除は即時反映されます。
タグ付け
私物ガジェットにはタグを複数登録できます。
5. フィード機能
つぶやきを一覧表示します。
無限スクロールで順次読み込みます。<タブによって表示する内容が異なります>
- 新着順 => 全てのつぶやきを新着順で表示
- タイムライン => フォロー済みユーザーのつぶやきのみ新着順で表示
- タグフィード => フォロー済みタグを含むつぶやきのみ新着順で表示
6. タグ管理機能
タグをフォローすることができます。
タグフィードに表示したいタグをフォローします。つぶやき・掲示板・私物ガジェットはタグ付けすることができます。
タグをタップするとタグ詳細ページに遷移し、タグを含むつぶやき・掲示板・私物ガジェット・フォローしているユーザーの一覧を表示します。7. 検索機能
検索ページでは、検索フォームに入力した内容に合わせて結果がリアルタイムに表示される機能を実装しております。
8. 通知機能
つぶやき・掲示板・ユーザーフォローにおいて、イベントが発生すると通知が表示されます。
新着通知
新しい通知が発生すると、ヘッダー内のベルアイコンにバッジが表示されます。
タップすると新しい通知がリスト表示され、通知をタップするとイベント発生元へ画面遷移します。通知一覧
通知一覧をタップすると一覧ページへ画面遷移します。
通知一覧ページでは、今まで受け取った全ての通知を表示します。9. ゲストユーザー機能
ユーザー専用機能を簡単に利用できるように、ゲストユーザーログイン機能を実装しています。
『ゲストユーザーとしてログイン』をタップするだけです。
未ログイン状態でユーザー専用機能にアクセスすると、ゲストユーザーログインを促すダイアログを表示するようにしております。
10. God mode
一時的に管理者権限を有効にするモードです。(管理者という概念が存在することを確認して頂くためのモードです。)
DB内 の Userモデルの adminカラム の true/false で 各ユーザーの管理者権限を管理しておりますが、God modeではVuexストアの値を一時的に変更して管理者権限を有効化しています。(DB上には保存されないのでブラウザ再読み込みでfalseに戻ります。)
つぶやき・掲示板・私物ガジェットにはそれぞれ『管理メニュー』が設定されており、作成者本人にのみ表示される仕組みです。
God mode を有効化にすると他のユーザーが作成したコンテンツでも『管理メニュー』を表示するようになり、『削除のみ』実行可能となります。
11. レスポンシブデザイン
Vuetifyを活用したレスポンシブデザインで、スマホでも扱いやすいUI/UXを意識しました。
サブメニューとタブメニューを組み合わせることで、画面遷移ゼロで複数のコンテンツにアクセスできます。
つまったポイント
フロントエンドとバックエンドを分離した開発環境の構築
=> 仮想コンテナの基礎知識やポートフォワーディングでつまりました。
=> 初めからdocker-composeを使うことをオススメします。環境変数の受け渡し
=> 渡せているはずなのに!という場面が何度もありました。各ソフトの仕様を把握していないと詰みます。SSRモードで動作するNuxt.jsのライフサイクル
=> SSRでSPA用コードを実行するとライフサイクルが一部違うので正常に動作しません。SSRモードでは動作しないプラグイン
=> ポートフォリオで利用した『@johmun/vue-tags-input』がSSRモードでは動作しなかったので、特別なおまじないが必要でした。Vuexストアの扱い方
=> 断片的な解説記事が多くて理解するのに時間が掛かりました。親コンポーネントと子コンポーネントのデータ受け渡し
=> Vue.jsの経験がある方であれば問題ないかと思います。Vuetifyモジュールの導入
=> Nuxt.jsの初期セットアップでVuetifyを選択するとエラー発生
=> VuetifyなしでNuxt.jsをセットアップ + 後からVuetifyを導入 + おまじないが必要で詰みました。AxiosモジュールのSSRモードでの挙動
=> SPAとSSRではAxiosのエンドポイントの設定に違いがあり詰みました。ECS Fargateでのインフラ構築
=> FargateはSSHできないのでエラーログの取得やトラブルシュートに手間取りました。
=> CloudWatchなどを利用して確実にログを取れる環境を作らないと、どこに問題があってコンテナが動いていないのか判断できなくて詰みます。制作を終えてみて
ポートフォリオのアップデートは今後も行いますが、いったん区切りがついたのでホッとしています。
特に厳しかったのがECSデプロイでして、途中何度も諦めそうになりましたが何とかやり終えることができました...。(インフラ難しい)
苦労した分だけ喜びも大きくなると言われますが、自作アプリにアクセスできた瞬間は嬉しかったですね!
ただし、書いているコードは実務レベルから比べると『その場しのぎ』程度だと思いますし、
プログラミングにおける基礎知識が抜けていると思い知らされる場面も多々ありました。(特に学習初期の理解力)正直なところ、Nuxt.js や Rails という『ツール』を何となく使えている、というのが現時点での実力だと思います。
今後について
ポートフォリオが完成したので就活を始めていきます。
課題である基礎力を伸ばすために、就活と同時に基本情報技術者やJavaSliverなどの取得を目指して資格勉強も進めていくつもりです。
コロナ渦で未経験からエンジニアになることが難しいと言われてますし、引き続き気合を入れて頑張っていきたいと思います。
ただのポートフォリオ紹介記事ですが、最後までお付き合い頂きましてありがとうございました!
Twitterもやってますので是非フォローもよろしくお願いします!
関連リンク
使用した教材
Progate
ドットインストール
Railsチュートリアル
書籍
Google先生
Githubのソースコード(チュートリアル用アプリケーションのソースを読んでました。)
バージョン
Ruby 2.7.1
Rails 6.0.3.4
nuxt 2.14.6
@nuxtjs/vuetify 1.11.2
Docker 19.03.13
docker-compose 1.27.4
Terraform 0.14.3
- 投稿日:2021-01-29T22:10:48+09:00
Ruby On Rails でのHP作成メモ
目的
自分用のRailsでHello Worldの表示。今回はmacでの環境設定。
Windowsは次回。。。。開発環境
macOS Big Sur
Visual Studio Codeバージョン
Rails 5.2.3
Ruby 2.7.0
rbenv 1.1.2
Bundler 2.1.4初期設定
1 . Homebrewのインストール
terminal$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"2 . rbenvのインストール
Rubyのバージョン管理ツールrbenvのダウンロード
terminal$ brew install rbenv ruby-build3-1 . Rubyのインストール
最新版(2.7.0)をインストール
terminal$ rbenv install --list $ rbenv install 2.7.0 $ rbenv global 2.7.0 $ ruby -v3-2 . PATHを通す
terminal$ touch ~/.bash_profile # ホームディレクトリに.bash_profileを作成 $ touch ~/.bashrc # ホームディレクトリに.bashrcを作成 $ echo '# rbenv' >> ~/.bash_profile $ echo 'export PATH=~/.rbenv/bin:$PATH' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile $ source ~/.bash_profile4 . Bundleのインストール
Railsのバージョン管理ツールのダウンロード
terminal$ gem install bundler $ bundle -v5 . Railsのインストール
terminal$ gem install -v 5.2.3 rails $ gem list rails *** LOCAL GEMS *** rails (6.0.2.1, 5.2.3) rails-dom-testing (2.0.3) rails-html-sanitizer (1.3.0) sprockets-rails (3.2.1)6 . データベースのインストール
sqlite3
terminal$ gem install sqlite3 @[バージョン]postgresql
terminal$ brew install postgresql @[バージョン]MySQL
terminal$ brew install mysql @[バージョン] $ mysql.server start7 . プロジェクト作成・導入〜起動
workspace$ rails _5.2.3_ new my-appworkspace/my-app$ cd my-app $ bundle install --path vendor/bundle $ rails server→ Gemfileからインストールする方法もある
応用
bootstrap4の導入
Gemfilegem 'bootstrap', '~> 4.3.1' gem 'jquery-rails'my-app$ bundle installapp/assets/javascripts/application.js//= require jquery3 //= require popper //= require bootstrap-sprockets→ 文末には記述しない
app/assets/stylesheets/application.css→application.scss(rename)@import "bootstrap";ルーターとMVC開発
my-app$ rails g controller users indexルーター
my-app/config/routes.rbRails.application.routes.draw do get 'users/index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end①モデル
my-app/app/models$ 省略②コントローラー
my-app/app/controllers/users_controller.rbclass UsersController < ApplicationController def index @message = 'Hello World' end end③ビュー
my-app/app/views/users/index.html.erb<div class="row"> <div class="col-1"> </div> <div class="col-4"> <h1><%= @message %></h1> </div> </div>結果
おまけ
復元するとき
my-app$ bundle updateバックグランドで残っているとき
terminal$ rm ./tmp/pids/server.pid //process idを消す
- 投稿日:2021-01-29T21:02:25+09:00
cannot load such file -- rake (LoadError)やcannot load such file -- bootsnap/setup (LoadError)と出てherokuへのデプロイに失敗する
環境
ruby 2.6.6
rails 6.1.1railsチュートリアルを進める途中、ローカル環境にrubyの開発環境を整えたが、herokuへのデプロイだけがうまく行っていなかった。
ローカル環境に移行した3章の最初のデプロイがうまくいかずつまずいたが、1章のデプロイも同じエラーでできなかった。
エラーメッセージは以下のとおり。remote: ! Could not detect rake tasks remote: ! ensure you can run `$ bundle exec rake -P` against your app remote: ! and using the production group of your Gemfile. remote: ! /tmp/build_08768f79/config/boot.rb:4:in `require': cannot load such file -- bootsnap/setup (LoadError) remote: ! from /tmp/build_08768f79/config/boot.rb:4:in `<top (required)>' remote: ! from /tmp/build_08768f79/bin/rake:3:in `require_relative' remote: ! from /tmp/build_08768f79/bin/rake:3:in `<main>'Gemfileは以下のとおりで、bootsnapがインストールされていないということは考えにくかった。
Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails', '6.1.1' gem 'puma', '4.3.6' gem 'sass-rails', '5.1.0' gem 'webpacker', '4.0.7' gem 'turbolinks', '5.2.0' gem 'jbuilder', '2.9.1' gem 'bootsnap', '1.4.5', require: false group :development, :test do gem 'sqlite3', '1.4.1' gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'web-console', '4.0.1' gem 'listen', '3.1.5' gem 'spring', '2.1.0' gem 'spring-watcher-listen', '2.0.1' end group :test do gem 'capybara', '3.28.0' gem 'selenium-webdriver', '3.142.4' gem 'webdrivers', '4.1.2' gem 'rails-controller-testing', '1.0.4' gem 'minitest', '5.14.3' gem 'minitest-reporters', '1.3.8' gem 'guard', '2.16.2' gem 'guard-minitest', '2.4.6' end group :production do gem 'pg', '1.1.4' end # Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]boot.rbのbootsnap/setupに関わる行をコメントアウトしたが、
boot.rbENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require "bundler/setup" # Set up gems listed in the Gemfile. #require "bootsnap/setup" # Speed up boot time by caching expensive operations.今度はrakeがロードできないと怒られてしまう。
ensure you can run `$ bundle exec rake -P` against your app and using the production group of your Gemfile. /tmp/build_187f57ec/bin/rake:4:in `require': cannot load such file -- rake (LoadError)ひとまずboot.rbのコメントアウトは解除した。
調べている内にこのページにたどり着く。
https://teratail.com/questions/317714herokuではbundlerが2.1.4までのバージョンのものしか使えないという内容だった。
Gemfile.lockを確認すると確かに対応していないバージョンを使っているようだった。Gemfile.lockBUNDLED WITH 2.2.7bundler2.1.4をインストールし、Gemfile.lockを一度削除してからそちらを使うように切り替えたら、デプロイに成功した。
$ rm Gemfile.lock $ gem install bundler -v 2.1.4 $ bundle _2.1.4_ install --without production参考
git push heroku master で`require': cannot load such file -- bootsnap/setup (LoadError)
https://teratail.com/questions/317714
- 投稿日:2021-01-29T19:25:21+09:00
railsで投稿日時を日本時間にする方法
開発環境
Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系前提
記事投稿機能は実装済みです。(postモデル)
application.rbファイルを編集
config内のapplication.rbファイル内に以下の記述を追加します。
config.i18n.default_locale = :ja config.time_zone = 'Tokyo'application.rbmodule Anipho class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. # 以下2行を追加してください config.i18n.default_locale = :ja config.time_zone = 'Tokyo' endja.ymlファイルを編集
config/localesディレクトリ内にja.ymlファイルを作り、その中に以下の記述を追加します。
ja.ymlja: time: formats: default: "%Y/%m/%d %H:%M"自分の場合は既にコードがあったので、以下のような感じになります。
ja.ymlja: activerecord: models: post: 投稿 attributes: post: images: ペットの写真 title: 写真のタイトル category_id: ペットの種類 time: formats: default: "%Y/%m/%d %H:%M"ymlファイルはインデントが合わないだけで正常に作動しないので、繊細に作業する必要があります。
ビューファイルに投稿日時を表示する
最後にビューファイルに投稿日時を表示する記述をします。
記述する際にはlメソッド を使います。
show.html.erb<div class ="time">投稿日時:<%= l @post.created_at %></div>最終的に以下のようなビューになりました。
以上です。参考になれば幸いです。
- 投稿日:2021-01-29T18:55:18+09:00
【備忘録】Railsをインストールした時のエラーについて
Railsを以下のコマンドでインストール
gem install rails -v 6.0.2しかしエラーが発生
ERROR: Error installing rails: invalid gem: package is corrupt, exception while verifying: undefined method `size' for nil:NilClass (NoMethodError)in /Users/[ユーザー名]/.rbenv/versions/2.7.0/ lib/ruby/gems/2.7.0/cache/nokogiri-1.11.1-x86_64-darwin.gem「package is corrupt」と表記されており、パッケージファイルが破損しているとのこと。
どのファイルが破損しているのかはその後に「/Users/[ユーザー名]/.rbenv/versions/2.7.0/
lib/ruby/gems/2.7.0/cache/nokogiri-1.11.1-x86_64-darwin.gem」と記されている。
なのでこのファイルを削除することにした。以下のコマンドrm /Users/[ユーザー名]/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/cache/nokogiri-1.11.1-x86_64-darwin.gemもう一回インストールしてみると、見事成功した。
gem install rails -v 6.0.2
- 投稿日:2021-01-29T18:01:46+09:00
[Rails]投稿機能にyoutube動画の埋め込みを可能にする際にハマったこと。
ユーザーがポートフォリオに記事を投稿する際、youtubeの共有リンクを貼り付け動画を埋め込めるようにする機能を実装しようとして、手こずった事があったので備忘録として残しておきます。
何にハマったのか
既にQiitaに同じような実装をされている方の記事が複数あったため、それを参考にすれば何ら問題なく実装できました。
しかしながら、参考記事は新規投稿に関するもので、投稿の編集(update)の場合については書かれていません。色々と試しましたが自力で実装ができませんでした。新規投稿の場合
createアクション(新規投稿)での実装方法はこのような感じです。
posts_controller.rbdef create @post = Post.new(post_params) url = params[:post][:youtube_url].last(11) @post.youtube_url = url endなぜこのような手段を取っているのか等の詳細は割愛します。以下の記事がとてもわかりやすいです。
railsアプリに投稿されたYouTubeURLを自動的に埋め込み表示させる方法~無理やり編~
編集updateでやってみた(失敗)
新規投稿は上記の方法で実装できましたが、このままでは投稿の編集をする際、
例えば全く別の動画のURLを貼り付けて更新すると、"https://~~~"から始まる全ての文字列をデータベースに保存してしまいます。
当然このままではviewで表示した際に動画が正しく埋め込まれません。
「でも理屈は同じだから、updateアクションの前にURLを削ぎ落とす処理を足せばいいんじゃね?」と思って以下のようにやってみました。posts.controller.rb※駄目な例です※ def update @post = Post.find(params[:id]) # ここから url = params[:post][:youtube_url].last(11) @post.youtube_url = url # ここまで追加 if @post.update(post_params) flash[:success] = '更新しました。' redirect_to @post else flash.now[:danger] = '更新に失敗しました。' render :edit end endただこれだと駄目でした。更新するとyoutube_urlのカラムにはURLがそのまま全部登録されてしまいます。何でや〜!!
結果わからなかったので、「発想の転換」
情けない話ですが、解決できませんでした。
しかしながら、色々調べていく中で"before_save"というコールバック関数に目をつけました。
「コントローラーじゃなく、モデル側でデータベースに保存する時に削ぎ落とせるのでは?」Railsチュートリアルにはbefore_saveが使われている?ようですね。私は通っていないので知りませんでした。
before_saveを使うと、saveのメソッドが実行される直前に、指定した処理を行う事ができるようです。
(ちなみにbefore_updateという関数もあり、この場合はupdateの場合のみに実行される為、個別に指定する事もできます)
今回はcreateでもupdateでも「URLを削ぎ落とす」という処理は同じなので、before_saveで一括することにしました。before_save
post.rbclass Post < ApplicationRecord before_save :slugify def slugify self.youtube_url = youtube_url.last(11) end endslugify()というメソッドを定義し、その中身の処理としてURLを削ぎ落とす内容を記述しました。
(slugifyという言葉の使い方には誤りがあるかもしれないです。それっぽいワードが使いたかった)
ユーザーがformで入力した内容がsaveメソッドによりDBに保存されるまでは、パラメータとしてはyoutube_urlは"https://~~"の形で送られています。(これはターミナルにて確認できます。)
なのでyoutube_url.last(11)として末尾の11文字だけを取り出し、self.youtube_urlへ再度代入します。
そして、"before_save"によってデータベースへの保存の直前にその関数が呼び出されるようにしました。不要な処理は削除します(create, updateアクション両方)
posts_controller.rburl = params[:post][:youtube_url].last(11) @post.youtube_url = urlこの処理はモデルファイルに書いたものと重複するので、削除します。
できた!
無事にURLが削ぎ落とされて、埋め込みが可能になりました。
参考記事
railsアプリに投稿されたYouTubeURLを自動的に埋め込み表示させる方法~無理やり編~
苦しめられてやっと理解できたRailsコールバックの使い方
- 投稿日:2021-01-29T16:49:38+09:00
【Rails】パーシャル(部分テンプレート)の中身をアクションごとに変更する
バージョン
- Rails 6.0.3
- Ruby 2.6.3
やりたい事
ポートフォリオサイトのプロフィール画面をSPAっぽくしているのだが、現状値が入っていないと何も表示されないので画面が遷移しているのかが分かりづらい。
各アクション毎に表示されるメッセージを変えたいのだが、renderでパーシャル化している為、1つのファイル内でメッセージを分岐させなければいけない。
方法
調べてみたところ、action_nameでアクション名を取得する事で同じファイル内のviewを分岐できるそうです。
<% if action_name == 'show' %> <div class="text-center mt-5">投稿はありません</div> <% else %> <div class="text-center mt-5">いいねした投稿はありません</div> <% end %>フォロー中、フォロワーで使用している部分テンプレートも同じような方法で書き換えました。
結果
アクション毎にそれぞれのメッセージが表示されるようになりました。
- 投稿日:2021-01-29T16:47:05+09:00
自分なりのN+1問題へのアプローチ
N+1問題とは
ループ処理の中で大量のSQLクエリが発行され、アプリにおけるパフォーマンス上の問題を引き起こす現象のこと。
バックエンドを主戦場とするエンジニアが最も神経を使う問題の1つである。自分なりのアプローチ
自分の作成したアプリ上でもN+1問題が起きていないかチェックしたいと考え、方法を調べたところ2通りの方法があった。
①Bullet Gemを使用する
このGemを使えば、N+1問題が発生しているページにアクセスするとブラウザ・ログなどに警告を出してくれる。②ログを見る
このアプリはDocker環境で開発しているため、docker-compose up
コマンドでコンテナを起動するとターミナルにログが出力される。ログの中に大量のSQLクエリが発行されていれば,
そのページではN+1問題が発生し、レスポンスが遅くなったり、最悪アプリがサーバーダウンする可能性があることがわかる。今回はこの2通りの方法で自分のアプリをチェックした。
Bullet Gemのセットアップ
まずは以下のドキュメントを参考にBullet Gemの設定を行う。
https://github.com/flyerhzm/bullet①Gemのインストール
Gemfileに以下の様に記述し、ビルドコマンドを実行するGemfilegem 'bullet', group: 'development'ターミナルdocker-compose build※開発環境にDockerを使用されていない方は
bundle install
のなどのコマンドでインストールする。次にBulletを使用可能するためのジェネレートコマンドを実行する。
ターミナルdocker-compose run web bundle exec rails g bullet:installこれでセットアップは完了!
前提
いざチェックをする前に自分がどのようなアプリを使うのか簡単に説明する。
このアプリでは、学校側がアプリ上でアンケート機能が付いたお知らせを作成し、保護者(User)
がお知らせ(Board)
にある『参加する』ボタンを押すことでアンケート(Join)
に返答し、その結果を集約することができる。またその集約の結果を見ることができる。
具体的な使用例を挙げると、まず学校が『除草作業のお知らせ』という題名のお知らせを作成し、保護者に公開する。次にその『除草作業』に参加したい保護者は、お知らせにある『参加する』ボタンを押す。すると同じお知らせにある『アンケート結果』をクリックし、アクセスすると『参加する』ボタンを押した保護者の名前が全て表示される。この一連の機能でアンケート集約を迅速に行うことができる。チェックの結果
全てのページにアクセスした結果、N+1問題、いわゆる大量のSQLクエリが発行されているページが1つだけあった。
そのページは、先ほど紹介した
お知らせ(Board)
のアンケート結果、つまり行事に参加する(Join)保護者(User)
の一覧がループ処理で表示されているページであった。
実際にそのページアクセスするために『アンケート結果』のリンクをクリックすると以下の様にBulletによる警告がブラウザ、ログに表示された。
ターミナル省略・・・ web_1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 4 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 7 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 8 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 9 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 10 LIMIT 1 web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 11 LIMIT 1 [["id", 11], ["LIMIT", 1]] web_1 | ↳ app/views/joins/show.html.erb:10 web_1 | CACHE (0.0ms) SELECT COUNT(*) FROM `joins` WHERE `joins`.`board_id` = 1 web_1 | ↳ app/views/joins/show.html.erb:16 web_1 | Rendered joins/show.html.erb within layouts/application (78.9ms) web_1 | Completed 200 OK in 262ms (Views: 211.1ms | ActiveRecord: 6.7ms) web_1 | web_1 | web_1 | user: root web_1 | GET /boards/1/joins web_1 | USE eager loading detected web_1 | Join => [:user] web_1 | Add to your query: .includes([:user]) web_1 | Call stack web_1 | /app_name/app/views/joins/show.html.erb:10:in `block in _app_views_joins_show_html_erb___278131760983844940_69984784735660' web_1 | /app_name/app/views/joins/show.html.erb:8:in `_app_views_joins_show_html_erb___278131760983844940_69984784735660'
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1
といったユーザー情報を出力するSQLクエリが参加人数の10人分発行されていることが見てわかった。この原因を突き止めるべく、ソースコードを確認することにした。該当コードは以下の通りである。該当コードを見ると…
app/controllers/joins_controller.rbdef show @board = Board.find(params[:board_id]) ←① @joins = @board.joins.page(params[:page]) ←② endapp/views/joins/show.html.erb<div class="container"> <div class="card"> <div class="card-header"> 参加者一覧:参加人数 <%= @board.joins.count %> </div> <table class="table"> <% @joins.each do |join| %> ←③ <tr> <td><%= link_to join.user.name, join.user %></td> </tr> <% end %> </table> </div> <%= paginate @joins %> </div>①お知らせ(Board)のデータを取得し、変数@boardに代入
②Joinsテーブルにあるお知らせ(board_id)が格納されているカラムのデータを全て取得し、変数@joinsに代入
③eachを使った繰り返し処理でアンケートに返答した行事の参加者(join.user)の名前(name)を表示するといった処理の流れになっている。
問題は③の繰り返し処理の時に10回分Joinsテーブルに格納されているuser_idをもとにUsersテーブルからデータを取得するSQLが発行されるようなコードが記述されているところであるとわかった。これを日常生活で例えるなら、買い物をする時にカゴを使わず1個1個商品を売り場から持ってきてレジに通し、ヘトヘトになっている状況と同じである。なんとも非効率的…
改善策
ではどうすればこの状況を改善できるか。ヒントはBullet Gemが警告と共に教えてくれていた。
先程警告が出たログの最下部を見ると…user: root GET /boards/1/joins USE eager loading detected Join => [:user] Add to your query: .includes([:user])指示通りに実装してみた結果、コードは以下の通りになった。
app/controllers/joins_controller.rbdef show @board = Board.find(params[:board_id]) #includesメソッドを使ってuser_idをもとに参加者(user)のデータを取得する @joins = @board.joins.includes([:user]).page(params[:page]) endこれによりJoinsテーブルのデータと一緒にUsersテーブルのデータも取得できるようになった。もう一度警告された参加者一覧ページにアクセスした結果、以下のログが出力された。
ターミナル省略・・・ web_1 | User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11) web_1 | ↳ app/views/joins/show.html.erb:8 web_1 | CACHE (0.0ms) SELECT COUNT(*) FROM `joins` WHERE `joins`.`board_id` = 1 web_1 | ↳ app/views/joins/show.html.erb:16 web_1 | Rendered joins/show.html.erb within layouts/application (39.2ms) web_1 | Completed 200 OK in 215ms (Views: 182.1ms | ActiveRecord: 3.1ms)
User Load (0.6ms) SELECT 'users'.* FROM 'users' WHERE 'users'.'id' IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
というログからUsersテーブルの情報がまとめられ、1回で出力されていることがわかる。警告も出なかったので、これでひとまずN+1問題は解決することができた。includesメソッドについて
今回大活躍してくれたincludesメソッドをもう少し深く調べてみると、実はモデルの関連状況によって
eager_load
メソッドとpreload
メソッドを使い分けていることがわかった。今回のケースでincludesメソッドは、SQLの発行状況からpreload
メソッドの挙動をしていたこともわかった。これからは、このincludesメソッド内での挙動の違いについて詳しく調べて行きたいと思う。最後までお読みいただきありがとうございました。
参考
bulletドキュメント
ActiveRecordのincludesは使わずにpreloadとeager_loadを使い分ける理由
ActiveRecordのincludes, preload, eager_load の個人的な使い分け
- 投稿日:2021-01-29T16:32:09+09:00
rails モデル空の時エラーになるようにする(バリデーション)
1.モデルにvalidatesをかける
自分が空の入力だと困るもの、バリデーションをかけたいものにvalidetesをかける。そこにプラスしてpresence: trueを追加します。
post.rbvalidates :nickname, presence: true validates :title, presence: true2.たくさんあるときの簡単な記述の仕方
このカラム自体の数が多い時、これを一つ一つ記述をするのはRubyの記述として綺麗ではないためwith_optionを用いて記述します。この記述で簡単にまとめることができます。
post.rbwith_options presence: true do validates :nickname validates :title end3.最後に
このようにバリデーションを用いて空の記述を弾くようにします。また同じ記述をするときは一つにまとめることを意識して記述しましょう。
- 投稿日:2021-01-29T16:23:20+09:00
【Ruby】最小最大値を取得する get.to_i .min .maxメソッド
【入力数値】
3 4 5【コード】
n1 = gets.to_i
n2 = gets.to_i
n3 = gets.to_iwrap = [n1, n2, n3]
puts wrap.min【出力結果】
3【解説】
test.rbファイルを作成し、gets.to_iメソッドで数値オブジェクトに変換した値を取得し、変数nに代入します。
配列wrapにn変数をそれぞれ格納し、puts wrap.minを実行すると、配列wrap内の最小値を取得することができます。
.minを.maxにすると最大値を取得することもできます。
- 投稿日:2021-01-29T16:01:31+09:00
簡単!Rails6にBootStrap5を導入する
はじめに
本記事は2021年1月時点の最新環境である、Rails6 に BootStrap5を導入する方法を紹介しています。
また、BootStrapに関してはsassで編集できるようにしています。gemfile
まずは、gemfileに以下の行を追加しましょう
gemfilegem 'bootstrap', '~> 5.0.0.alpha1' gem 'jquery-rails'ターミナル(コマンドプロンプト)にて
ターミナルbundle installを実行しましょう。
application.cssを変更
app/stylesheets/
の中にある
application.css
をapplication.scss
にファイルをリネームしましょう。
それができたら、application.scss
に以下の文を追加して下さい。app/stylesheets/application.scss@import "bootstrap-sprockets"; @import "bootstrap";以上でBootstrapの導入が完了しました。
- 投稿日:2021-01-29T15:50:16+09:00
Ruby on Rails かけ算メソッド
【コード】
def Multiplication (a, b)
puts a * b
endMultiplication(8, 9)
【出力結果】
72【解説】
test.rbファイルを作成し、
メソッドMaltiplication(英語でかけ算の意)を定義して、(仮引数a,b)に数値を渡し、メソッド内の実行したい処理 puts a * bで出力します。実引数Multiplication(数値,数値)の数値に値を入力すると、かけ算の結果を得ることが出来ます。
VSコードのRails環境では、ターミナルにてruby test.rbで実行することが出来ます。
*defはDeginition(定義の略)
- 投稿日:2021-01-29T15:42:21+09:00
つぶやきアプリを作ってみよう
今まで学習して知識を使ってつぶやきアプリを作ってみましょう。
新しいアプリケーションを作ります。
# アプリを作りたいディレクトリに移動 % cd ~/ディレクトリ名 % rails _6.0.0_ new tsubuyaki -d mysql # tsubuyakiアプリのディレクトリに移動 % cd pictweetdatabase.yml
まずデータベースの設定を設定しましょう。
Railsアプリケーションのデータベースの設定ファイルです。
configというディレクトリ内にあります。編集することで、データベースの設定を
運用環境ごとに変更できる仕組みになっています。運用環境
システムをどこで使用するか定められている環境を運用環境と呼びます。
運用環境を使い分けることで効率的に作業を行うことが可能です。
運用環境 通称 概要 development 開発環境 開発をする際に使用する環境 test テスト環境 動作をテストする際に使用する環境 production 本番環境 実際にリリースする際に使用する環境 データの保存形式を変更するため、以下の作業を行います。
encoding: utf8mb4という記述をencoding: utf8へ変更してください。
:config/database.yml default: &default adapter: mysql2 # encoding: utf8mb4 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sockデータベースの作成
% rails db:create拡張機能を使ってみよう
プログラミング言語には様々な拡張機能が用意されておりライブラリといます。
RubyGemsというライブラリを管理するシステムの中に
たくさんのライブラリがあり、それぞれをGem(ジェム)と呼びます。Railsで使用するGemの情報はGemfileというファイルに記載します
Railsで使うライブラリを導入
Gemfileに追加したいGemの名前やバージョンを記述してbundle installを行うことで、
Gemをインストールできます。Gemfile内の一番下に、Gemであるpry-railsを追加
Gemfilegem 'pry-rails'pray-railsはデバックようのGemで処理を任意の箇所で止めることができます。
記入したらインストールするためのコマンドをターミナルで実行しましょう
% bundle installGemファイルを追加、修正した場合はサーバーを再起動しましょう。
その時に設定したデータが読み込まれるためです。% rails sこれで準備ができました。
今回は以上です。
- 投稿日:2021-01-29T15:18:03+09:00
[未経験エンジニア]のオリジナルアプリ制作の反省1.「ビューは先にしっかり作っとけ」
誰なのか
某短期集中エンジニア養成プログラムでエンジニア転職を目指している者です。
エンジニアとして学習を始めて、現在2ヶ月になります。何を書くのか
- オリジナルアプリを制作した過程で「もっとこうしておけばよかった...」と後悔した部分
- 「ここは、実装難しかったな...」と、思った部分
について,1日1つ取り上げ、ネタがなくなるまで投稿します。
基本的には、自分の備忘録としての記録となりますので、至らない点も多々あるかと思います。
それでも、もし僕みたいに「未経験からエンジニアになろう!」と思っている人の参考になれば幸いです。環境
ruby: 2.6.5
Rails: 6.0.3.4今回の結論
タイトルの通りです。ビューをあらかじめ作っておくことの重要性をひしひしと感じました。
ソースコードも交えてお話しします。こうしとけばよかった!
具体的には、開発を始める前の企画段階で
- リセットcssについての知識が足りておらず、とりあえずcssの設定を機能実装の都度ちょこちょこしていたこと
- レスポンシブデザインを考慮しない設計構造になっていたことの2つを課題として考えています。
なぜそうなってしまったのか
要件定義の段階で、実装したい機能は決まりました。しかし、それを支えてくれるページレイアウトに関しては、当初、フロントエンドに対して苦手意識もあり、「機能を開発する度に」html, cssを作成するやり方で進めていたのが、失敗の原因だと思います。一般的な開発ではどうなのか分かりませんが、これは自分にとって最終的に悪い形で帰ってきました。
今後に向けて考えたこと
個人開発、ポートフォリオ向けの開発には、webフレームワーク(有名なのはboostrap)などを用いて大体のレイアウトを整えるのも一つの手段だと思います。
しかし、今回あげた課題に共通するのは、そもそもwebアプリケーション全体としてのゴールを見通せてなかったことにあると思います。ゴールは、最初に決めておいた方がいいですよね。すなわち、要件定義の際に、ページレイアウト、画面遷移図などを、メモの時点でほとんど完成系に近づけるべきだということです。
このように提案する理由は、サーバーサイドとフロントエンドがお互いに影響を及ぼす可能性を排除できないことにあります。一つ例を挙げます。form_with, link_toのようなヘルパーメソッドのブラウザ上の表示は、html, cssの言語に変換されて表示されます。
例えば、form_withは、フォームを作成することができるヘルパーメソッドです。
<%= form_with url: "/posts", method: :post, local: true do |f| %> <%= f.text_field :title %> <%= f.submit '投稿する' %> <% end %>このように記述された場合、ブラウザ上では
<form action="/main" method="post"> <input type="text" name="title"> <input type="submit" value="投稿する"> </form>と表示されます。
以上から、サーバーサイドとフロントエンドは密接に関わり合っていることがわかってもらえたと思います。
アクションプラン
- ページレイアウト(ビュー)は、サーバーサイドやJavaScriptなど、レイアウトに影響を及ぼす要素を踏まえて完成度60~70%くらいまで企画で決めておき、先に実装を進めておく。
- リセットcssは、レイアウト全体(物によっては字体まで!)に影響を及ぼすので、一番最初に導入し、読み込ませる。
- レスポンシブなサイトになるように、企画の時点で、少なくとも「その箱の中でどのくらいの存在感を持たせるのかという割合(%」)」を決めておく。できるなら、もっと細かくpx単位で考えぬく。
- もしサーバーサイドとして活躍したいなら、時にはboostarpなどのフレームワークも用いる。
最後に
最後まで読んでいただいた皆様、ありがとうございます。
ソースコード、記事の書き方について「もっとこうしたほうがいいよ!」というご意見、「そこどうなっているの?」というご質問など、お待ちしております。参考文献
・【Rails】form_withの使い方を徹底解説!
https://pikawaka.com/rails/form_with・【個人開発・ポートフォリオに】簡単にいい感じのデザインにできるサービスまとめ
https://qiita.com/aiandrox/items/4196c8f5b564d29fdce7
- 投稿日:2021-01-29T14:49:13+09:00
【銀座Rails#28 LT登壇】「モチベーションクラウドを支える非同期処理の変遷」
はじめに 【銀座Rails】とは?
銀座Railsは、リンクアンドモチベーションがスポンサーをさせて頂いている地域Rubyコミュニティの一つです。リンクアンドモチベーションでは、技術コミュニティの支援を通じて、エンジニアのコミュニティ形成やコミュニケーションの活性化、及びエンジニアの技術力向上に貢献したいと考えています。今回は銀座Rails#28に登壇したテックリード江上のスポンサートーク、伊藤のLTをレポートします!
江上 「従業員のエンゲージメントで、良い会社の定義を変える」
江上 真人
株式会社リンクアンドモチベーション
テックリード私たちは、技術コミュニティ「銀座Rails」が始まった頃からスポンサーとしてご支援しています。今回、スポンサートークという貴重な機会をいただきましたので、普段私たちがどのような想いで「モチベーションクラウド」を開発しているかを中心にご紹介できればと思います。
リンクアンドモチベーションは、「良い会社の定義を変える」というミッションを掲げています。これまで、良い会社とは、P/LやB/Sなど事業面の数字により評価されていました。それゆえ、企業によっては組織面が蔑ろにされることもあり、働く個人が人間関係や組織問題に苦しむことも多く見受けられます。
私たちは、良い会社の定義を事業面のみが良い会社だけではなく、組織面も良い会社が評価される社会をつくっていきたいと思います。組織面を定量的に評価するために、弊社はモチベーションクラウドという組織診断プロダクトを2016年にリリースし、2019年には診断結果をもとに、組織を改善するためのプロダクトを2つリリースしています。
組織診断をおこなうプロダクト「モチベーションクラウド」は、従業員の方にアンケートを実施し、組織のエンゲージメント(※1)状態を診断します。診断されたエンゲージメントの数値や組織課題をもとに改善するプロダクトが、「コミュニケーションクラウド」と「チームワーククラウド」になります。現在、モチベーションクラウドシリーズは順調に成長しており、月間2億程度の売上推移になります。
※1)エンゲージメント:会社と従業員の相思相愛度合い? スポンサーからのお知らせ
リンクアンドモチベーションでは、開発組織の内製化に踏み切った2018年から、毎年Qiita Advent Calendarに参加しています。2018年は惜しくも企業ランキング4位でしたが、2019年/2020年は2年連続で全体ランキング1位を獲得しました?また、個人でQiita賞を獲得するエンジニアも現れたりと、AdventCalenderは社内で盛り上がる毎年恒例のイベントになっています!? Qiita AdventCalender バックナンバー
2018年 リンクアンドモチベーションシリーズAdventCalender
2019年 リンクアンドモチベーションシリーズAdventCalender
2020年 リンクアンドモチベーションシリーズAdventCalender
皆さん、良い記事があれば是非いいねくださいね!伊藤「モチベーションクラウドを支える非同期処理の変遷」
伊藤 遼
株式会社リンクアンドモチベーション
アプリケーションエンジニア課題1:処理負荷の増大
本日は、モチベーションクラウドの非同期処理を刷新したお話をしたいと思います。ありがたいことに弊社のプロダクトは順調に成長していますが、一方で、処理負荷の増大と同時処理数の増加という2つの課題が発生していました。
スライドに「鳴り響くサーバアラート」と書きましたが、当社はバッチサーバ1台でcronを回し非同期処理を行っていました。最初はキャパシティを超えることはありませんでしたが、導入企業の規模が大きくなるにつれて、処理負荷が増大するようになりました。1つ1つの処理が肥大化するため、徐々にサーバの限界を超えていきます。
もともとの処理では、単一のサーバで大量のcronが登録されていました。当時は毎分20を超えるcronにさらに1時間ごとに10を超えるRakeタスクが起動しており、cronファイルがびっちり書かれている状態でした。今後は、可用性を高めてスケーラブルな構成にしていく必要があります。
課題2:同時処理数の増加
お客さまから「集計が終わらない」という声をいただきました。法人向けのSaaSでは企業ごとに処理するため、導入企業数が増加するにつれて、待ち時間が長くなってしまいます。
具体的には、お客さまのメール送信やレポート集計といった作業を1社ずつ実行していました。シンプルでわかりやすいコードなのですが、導入企業数が増加すると、ループの数が上がったり、メール送信やレポート集計の処理が重くなったりすることで、後続の処理が遅延する問題が起きつつありました。さらに、例外発生時にも後続の処理が遅れる、もしくは処理されない問題が発生していました。今後は、企業ごとに処理を並列化する必要があります。
非同期処理ができるgemとアーキテクチャを採用
この2つの課題を解決するために、非同期処理ができるgemとアーキテクチャを採用しました。表に記載されているものは候補として挙がっていたgemです。「sidekiq」や「resque」はGitHub(ギットハブ)でも人気で、使われている方も多いと思いますが、キューをメモリにのせるので、キューが消失した場合の対応で懸念があったため、今回は保留としました。
DBでキューを管理する「delayed_job」も人気がありますが、弊社であまり運用実績がないのと負荷耐性を重視していたため、今回は運用実績のあった「active elastic job」、つまりSQSとElastic Beanstalkのworkerを使う構成にしました。最終的には、もともと「Amazon EC2」が1台立っていたところを、間にSQSを挟み「AWS Elastic Beanstalk」のworkerをオートスケールできるような形でアーキテクチャの構成を変更しました。
「active elastic job」の運用で苦労した点
ここからは「active elastic job」の運用で苦労した点についてお話しします。一つ目が重複配信されてしまうことです。SQSは必ず1回の配信を保証してくれますが、重複配信される可能性もあります。つまり、「処理Aをしてね」とSQSにメッセージを投げるとworkerには処理の依頼が2回きてしまう可能性があります。
ただ一方で、もともとの処理プロセスは冪等性(べきとうせい)が担保されていない、つまり重複実行されているとデータの不整合が発生したり、もともと1つのタスクしか動かないように設計していたので、重複実行されないように実装する必要が出てきました。
二つ目が、エラー発生時のリトライ処理の実装です。SQSはvisibility timeoutやDead letter queueなどの失敗時の挙動を持っていますが、もともとの処理では、お客様にメールを送る機能と、社内で使うレポートでは重要度が変わるため、処理時間がばらばらでした。
そのため、5回リトライしたら通知する「処理A」と、50回リトライしたら通知する「処理B」と、処理ごとにリトライ待機時間や回数制限を定義する必要がありました。
三つ目は、もともとの処理だと、依存関係のあるタスクが多く、処理の実行可能条件が複雑に書かれていたことです。つまり実行条件の処理がバラバラになっていて、たとえば処理Aは「集計処理が完了していたら動いてほしい」、処理Bは「一括の処理が完了していたら動いてほしい」、それ以外は「もう1回リトライしてね」というのを自分たちで実装しました。そのため、処理ごとに実行可能かを定義し、それをさらにリトライさせるところまで作りこまなければいけませんでした。
待ち時間が大幅に減少。今後の開発生産性にもつながった
このあたりの苦難を乗り越え、無事リリースができました。結果、これまでのような待ち時間を減少させることができました。また、導入社数も増えているため、負荷が集中するタイミングもありますが、そこに対しても自動で対応できるようになりました。モチベーションクラウド過去最大社数の同時サーベイ開始という大規模なイベントもありましたが、こちらも問題なく乗り越えることができました。
もともと私は開発チームの中でデッドコードの整理を推進していました。しかし、この経験を通して、複数の責任を持っていた大きい処理を適切に分割できたり、非同期処理をするだけのテーブルを削除したりと、今後の開発生産性にも寄与できたと感じています。今後はすべての非同期処理を刷新していき、cronをやめて新しいジョブスケジューラーの導入し、同時にECS化も進めていこうと計画しています。
? 登壇資料はSpeakerdeckにて公開中
モチベーションクラウドを支える非同期処理の変遷
- 投稿日:2021-01-29T14:27:31+09:00
Railsでrails sしてもサーバーが立ち上がらない
起きた現象
ターミナルでいつもどおり「rails s」コマンドでローカルサーバーを立ち上げようと思ったら以下のエラーが出た。
A server is already running. Check /Users/terashimatakaya/projects/chat-app/tmp/pids/server.pid.「サーバーはすでに動いています」と指摘されました。
解決①
ご指摘のパスにあるserver.pidファイルを削除し、もう一度「rails s」を実行。
すると、、、(略) Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE)またエラー。
どうやら http://localhost:3000 のアドレスで使っているポート番号がすでに使われているということらしい。ポート番号を3000以外の3001とかに指定する方法も見つかりましたが、根本解決にならないので別の方法で解決します。
解決②
ググって以下のコマンドを見つけたので実行。
terashimatakaya@MacBook-Air chat-app % ps ax | grep rails 49845 s003 S+ 0:00.01 grep rails terashimatakaya@MacBook-Air chat-app % kill -9 49845 kill: kill 49845 failed: no such process1つ目のコマンドで実行中のプロセスを検索し、処理を止めるようkillコマンドを実行。
→「そんなプロセスはないぜ?」と指摘されました。解決③
もう少しググって以下のコマンドを実行したら無事にサーバーが動き出しました!
terashimatakaya@MacBook-Air chat-app % lsof -i :3000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ruby 48924 terashimatakaya 12u IPv4 0xedd14d7d782588d1 0t0 TCP localhost:hbci (LISTEN) ruby 48924 terashimatakaya 13u IPv6 0xedd14d7d73022519 0t0 TCP localhost:hbci (LISTEN) ruby 48924 terashimatakaya 25u IPv6 0xedd14d7d77b10b79 0t0 TCP localhost:hbci->localhost:58238 (CLOSE_WAIT) terashimatakaya@MacBook-Air chat-app % kill -QUIT 48924今の時点では「実行中のプロセスのPID番号を指定して処理を停止した」くらいの理解ですが、そもそものエラーの原因はサーバーが起動したままターミナルを終了したことだと思います。
今後はサーバーは必ず「Ctr + C」でストップさせてからターミナルを終了するようにします!
- 投稿日:2021-01-29T14:03:20+09:00
Active Strageを用いた複数画像の投稿
1.Active Strageのインストール
まずターミナルでactive strageのインストールの実行、そしてrails db:migrateの実行をします。
% rails active_storage:install% rails db:migrate2.モデルにてアソシエーション
次に紐付けをしたいテーブルにアソシエーションをします。この時に使うのがhas_one_attachedです。has_one_attachedというのは各レコードとファイルを紐付けます。複数の画像を投稿する場合この数を増やせば良いです。
post.rbhas_one_attached :image1 has_one_attached :image23.コントローラーにてストロングパラメターの追加
コントロラーのファイルにて画像のストロングパラメターの記述をします。これを追加したらバックエンドの記述は終了です。
post_controller.rb#中略 private def post_params params.require(:post).permit(:image1, :image2).merge(user_id: current_user.id) end4.自分の好きなようにビューファイルに記述をする
後はビューファイルにて画像の記述を名前通りに記述して反映します。
5.最後に
大体は画像は一枚で終わらしてしまうような作りにしてしまいますが、今回は2枚以上貼り付けつような仕組みを紹介いたしました。記事を読んでいただきありがとうございました。
- 投稿日:2021-01-29T13:42:49+09:00
railsのadmin画面は本サイトとは切り離して今どきの構築に乗せないほうが爆アドだという話のポエム
はじめに
ポエムなので一エンジニアの感想であり、これに該当しないケースもあると思うのであしからず。
今どきの構築にしない、とは?
本サイトの構築にvue.jsなどのフロントエンドFW(フレームワーク)を導入している場合の話で、
その場合に、adminのリポジトリは別で管理してこちらではjQuery,bootstrapを有効にする。
※ここでいうadminというのは、本サイトのデータを管理するためにエンジニアまたは社内の人間が使用する画面のことscaffold, asset pipeline, ransack, kaminariのような、フロントエンドFWが入っていると入れにくい便利コマンド・便利ツールを使えるようにする
メリット
- 圧倒的爆速で開発できる
- 上記のようなツールを使ったことがある人ならわかりますよね?
- コード量を減らせる
- adminだけならよっぽど複雑な機能でなければjs/cssがそこまでごちゃごちゃになることはない
- フロントエンド側との影響を考える必要がなく、viewを楽に実装したりツールを自由に導入できる
- slim/hamlが使える
- confirm, モーダルウィンドウ, tooltip, タブ型レイアウト, 折りたたみ, カレンダー
デメリット
・今どきの構築ではないこと。vue.jsからjsを勉強し始めたような、上記のようなツールを使ったことがない人達にとっては逆に学習コストが掛かる
・今更そんな技術学びたくね-よという人もいるかも
・本サイトとの共通処理を実装したい場合に、二重管理にならないように少し考慮が必要ポエム
新しいツールも古いツールもメリット・デメリットを理解して使い分けることが大事
- 投稿日:2021-01-29T13:15:46+09:00
GitHub ActionsでPDFをPNGに変換できなくなったときの対処法
発生した問題
CarrierWave + MiniMagickを使ってPDFをPNGに変換する処理を書いており、今までGitHub Actionsでそのテストを回していたが、昨日(2021-1-28)から以下のようなエラーが出てテストが落ちるようになった。
Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: `convert /home/runner/work/project-name/tmp/1611830955-943232923084175-0016-3156/thumb/sample.pdf -auto-orient /tmp/image_processing20210128-5864-128hawr.png` failed with error: convert-im6.q16: no images defined `/tmp/image_processing20210128-5864-128hawr.png' @ error/convert.c/ConvertImageCommand/3258.原因
ghostscriptがデフォルトでインストールされなくなっていたため。
UbuntuのVirtual Environmentのバージョンが変わったのでインストールされなくなった?(詳細未確認)
対応
ghostscriptをインストールするようにした。
- name: Install packages run: | - sudo apt-get -yqq install libgbm1 + sudo apt-get -yqq install libgbm1 ghostscriptその他メモ
- 冒頭に挙げたエラーメッセージでネットを検索すると、
/etc/ImageMagick-6/policy.xml
で<policy domain="coder" rights="none" pattern="PDF" />
を<policy domain="coder" rights="read|write" pattern="PDF" />
に変更しろ、という話がよく載っているが(参考)、これはすでに対応済みだったので今回の件には関係なかった。- ghostscriptがインストールされていないのでは?という観点はこのQ&Aを見て思いついた。RSpecのテストコード内で
puts `which gs`
のように書いてGitHub Actions上でgsコマンドが見つからないことを確認した。- ImageMagickのconvertコマンドに
-debug All
というオプションを渡すと、convert実行時の詳細情報が表示されるので、この情報をGitHub Actions上で出力させると詳細な原因がわかるかもしれない。(例puts `convert -debug All #{Rails.root.join('path/to/your.pdf')} /tmp/foo.png`
)- rspecコマンドに
-b
オプションを渡すと詳細なバックトレースを出力してくれるので、GitHub Actionsのコマンドに付けておくとヒントが得られるかもしれない。(例run: bundle exec rspec -b
)
- 投稿日:2021-01-29T12:30:17+09:00
【Rails】EC2上でのbundle install時のメモリ不足のエラーと解消方法
DockerでRailsのコンテナを
Run
する際に、bundle install
とyarn install
を起動させているのですが、Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
といったエラーが発生しました。web_1 | Gem::Ext::BuildError: ERROR: Failed to build gem native extension. web_1 | web_1 | current directory: web_1 | /app/vendor/bundle/ruby/2.7.0/gems/racc-1.5.2/ext/racc/cparse web_1 | /usr/local/bin/ruby -I /usr/local/lib/ruby/2.7.0 -r web_1 | ./siteconf20210129-9-ef1ov1.rb extconf.rb web_1 | extconf failedOut of memory - /usr/local/bin/ruby web_1 | web_1 | Gem files will remain installed in /app/vendor/bundle/ruby/2.7.0/gems/racc-1.5.2 web_1 | for inspection. web_1 | Results logged to web_1 | /app/vendor/bundle/ruby/2.7.0/extensions/x86_64-linux-musl/2.7.0/racc-1.5.2/gem_make.out web_1 | web_1 | An error occurred while installing racc (1.5.2), and Bundler cannot continue. web_1 | Make sure that `gem install racc -v '1.5.2' --source 'https://rubygems.org/'` web_1 | succeeds before bundling. web_1 | web_1 | In Gemfile: web_1 | rails was resolved to 6.0.3.4, which depends on web_1 | actioncable was resolved to 6.0.3.4, which depends on web_1 | actionpack was resolved to 6.0.3.4, which depends on web_1 | actionview was resolved to 6.0.3.4, which depends on web_1 | rails-dom-testing was resolved to 2.0.3, which depends on web_1 | nokogiri was resolved to 1.11.0, which depends on web_1 | racc web_1 | web_1 | web_1 | Gem::Ext::BuildError: ERROR: Failed to build gem native extension. web_1 | 〜〜〜 以下、他のGemでも同様のエラー 〜〜〜 web_1 | web_1 | In Gemfile: web_1 | mysql2 web_1 | !! exit status 5原因
EC2上でのメモリ不足でした。
AWSの無料利用枠の対象であるt2.microはRAMが1GBしかなく、そりゃ足りないよねといった状態でした。解決方法
以下の通りメモリスワップさせ、記憶できる容量を1GB→3GBに増やしたところ、
bundle install
とyarn install
が正常に完了しました。SSHでEC2にログイン$ sudo dd if=/dev/zero of=/swapfile bs=16M count=128 #スワップ用のファイル作成 $ sudo chmod 600 /swapfile $ sudo mkswap /swapfile # スワップ領域の作成 スワップ空間バージョン 1 を設定します。サイズ = 2 GiB (2147479552 バイト) ラベルはありません, UUID=a0628f7c-7088-4829-9258-e533a2b067f6 $ sudo swapon /swapfile # スワップ領域を有効化する $ sudo swapon -s # スワップ設定内容の確認 ファイル名 タイプ サイズ 使用済み 優先順位 /swapfile file 2097148 0 -2 $ sudo vi /etc/fstabfstabにvimで新たな行として追記/swapfile swap swap defaults 0 0ddコマンド
ファイルを変換してコピーする
if=/dev/zero
:0埋め
of=/swapfile
:作成対象のファイル名
bs=16M
:1ブロックあたり16MB
count=128
:128ブロック
→約2GBをスワップ領域として確保。(最適な分け方かは不明。)スワップ領域の推奨サイズ
一般的な推奨されるスワップ領域は以下の通りです。
t2.microは1GBのため、2GB分作成しました。
物理 RAM の量 推奨されるスワップ領域 2 GB 以下の RAM RAM 容量の 2 倍、ただし常に 32 MB を超える 2 GB 以上の RAM、ただし 32 GB 未満 4 GB + (RAM – 2 GB) 32 GB 以上の RAM RAM 容量の 1 倍 スワップ領域の無効化
スワップ領域を無効化したい場合は、
swapoff
で可能です。SSHでEC2にログイン$ swapon -s ファイル名 タイプ サイズ 使用済み 優先順位 /swapfile file 2097148 90368 -2 $ sudo swapoff /swapfile $ swapon -s 〜〜〜 何も表示されない 〜〜〜参考記事
・スワップファイルを使用して Amazon EC2 インスタンスでスワップ領域としてメモリを割り当てる
・linux スワップ(swap)領域の作成
- 投稿日:2021-01-29T11:56:44+09:00
【Cloud9】Ruby on Railsでrails sしたときにBlocked hostが表示された時の対処法
環境
- Ruby on Rails6.×
- Cloud9
エラー概要
以下のコマンドを実行した際に、
Blocked host
と表示され、Railsのデフォルトページが表示されない場合があります。rails server結論
config/environments/development.rb
を以下のように修正してください。config/environments/development.rbRails.application.configure do config.hosts << "<許可したいホスト名>" #=====中略===== end
<許可したい表示名>
には、「Blocked host:」のあとに表示されている「~.vfs.cloud9.us-east-2.amazonaws.com」で終わる文字列を入力してください。動作確認
再度アプリケーションを起動します。
rails server上記のような画面が表示されたら、右上の「Pop Out New Windows」をクリックして下さい。
以下のようにRailsのデフォルトページが表示されます。
※本記事はTechpitの記事を一部修正したものです。
- 投稿日:2021-01-29T11:27:21+09:00
[Ruby on Rails]悪魔的に簡単なゲストログイン実装方法(ポートフォリオ作成)
はじめに
就職・転職用にポートフォリオを作成している人に向けて。
ポートフォリオ必須といわれているゲストログイン。
- メールアドレスとパスワードだけで新規登録できるように設計したからいらないと思っている人
- 難しそうだから辞めておこうと思っている人
この方法はめちゃめちゃ簡単です。参考にしてください。
(ただしデメリットもあるので、あくまで参考程度に)前提の環境
- Ruby on Rails
- gem deviseでのログイン機能実装
Userテーブルの中身は下記として説明します。
User name string string password string created_at datetime updated_at datetime 今回の実装イメージ
きたない図でごめんなさい。
通常のログインでは利用者に情報を入力してもらい、そのデータをもとにUserを新しく作成しています。それとは異なり今回のゲストログインはあらかじめ設定されたデータでUserを新しく作成します。
なので「ゲストログイン」より「簡単新規作成」の方が言葉的に近いかもしれません。あらかじめデータを作成しておく必要があるのでdeviseで生成されるルーティングとコントローラは使用できません。
ゲストログイン用のものを自作していきます。実装方法
1.ルーティングの設定
config/routes.rbdevise_for :users # デバイスで生成されるルーティング post '/guest_sign_in' => 'homes#guest' # 今回自作したルーティングHTTPメソッドは
post
で書きます。
URL・コントローラ・アクションは代替可能です。(今回はHomesコントローラのguestアクションで説明を続けます)2.コントローラの設定
app/controllers/homes_controller.rbdef guest user = User.new(user_params) user.name = "ゲストユーザー" user.email = SecureRandom.alphanumeric(15) + "@email.com" user.password = SecureRandom.alphanumeric(10) user.save sign_in user redirect_to how_to_path end private def user_params params.permit(:name, :email, :password) endファットなコントローラかもしれないですが、見た目の分かりやすさ重視で書きました。
SecureRandom.alphanumeric(15)
で15文字のランダムな英数字を自動で生成してくれます。
これだけだと「@がない」というバリデーションに引っかかるので、+@email.com
を末尾につけています。
余談
deviseの一意性のバリデーションを考慮して今回は15桁で設定しました。
1桁で英(26文字)+数(10文字)=36通り。(英語の大文字小文字の区別は無しとして)
2桁で36x36=1,296通り
3桁で36x36x36=46,656通り
・
・
15桁で221,073,919,720,733,357,899,776通り(約2000垓通り)
2020年版の世界保健統計によると、全世界の総人口は約76億人なので
単純計算で全世界の人が1人約30兆回ゲストログインしてやっと一意性に引っかかることになります。
(米津玄師さんの代表曲「lemon」のYoutube再生回数が現時点で6.5億回なので、その4000倍以上です笑)
あくまでランダム生成なので単純計算はできませんが、10桁くらいでいいと思います。
余談終わり。
他にもありますので詳しくはRuby 3.0.0 リファレンスマニュアル を参照してください。
Userテーブルに他のカラムがあり、バリデーションを設定している場合は別途追加する必要があります。
例えば電話番号を設定している場合は、
user.tel = 09012345678
のようにしてuser.save
より前に追加してください。
下のpermit
に追加することも忘れずに。データベースに保存後、ログイン状態にする必要があるのでdeviseの機能を借りて
sign_in user
としリダイレクトさせます。3.ビューの設定
最後にビューにゲストログイン用のリンクを作成して完成です。
<%= link_to 'ゲストログイン', '/guest_sign_in', method: :post %>この方法のメリット・デメリット
メリット
- 記述も少なく難しい技術も使っていないのですごく簡単
上記の場合15行だけ。難しい設定も理解が必要なメソッドもなし。
- ゲストユーザーの情報を編集されても動作に影響ない
ここは大事。編集されないようにアクセス制限の設定したり、編集権限を考えたりする必要もない。
デメリット
- ゲストログインした分だけUserが増える(データベース圧迫)
別途管理者ページを作って管理したほうが良いかも。(私は作成しています)
- seedsでゲストログイン用のデータを作成することはできない
ゲストが投稿した記事等をあらかじめ作成しておくことはできないです。
ただ、他のユーザー投稿は問題なくseedsで作成できます。私はデメリットの影響がないPFを制作する予定だったためこの方法で実装しました。
制作物によっては2つ目のデメリットが痛い場合があるので気を付けてください。参考
簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)
他の方法を試す場合は、この方の記事がとても分かりやすいのでこちらを参考にしてみてください。
(私の方法とその1は似ていますが、若干違います。私が簡単新規作成なのに対し、この方はよりゲストログインに近い実装を紹介されています。お好みの方法で試してください。)
- 投稿日:2021-01-29T09:59:31+09:00
[Rails] Errno::ENOENT - No such file or directory - bs_fetch:atomic_write_cache_file:rename
キャッシュが残っているのが原因っぽい。だから、それを消したらok
docker-compose down rm -rf tmp/cache/bootsnap-* docker-compose up -d参考
https://github.com/Shopify/bootsnap/issues/177#issuecomment-604981988
- 投稿日:2021-01-29T09:54:33+09:00
GraphQL-RubyでInterfaceTypeを活用する
Graphql-RubyでInterfaceTypeを使う方法を説明します。
Interface Typeって?
Interface TypeはObjectTypeから呼び出して継承させることができます。
例
と言ってもよくわからんので、実際にActiveRecordInterfaceを作ってみます。
active_record_interface.rbmodule InterfaceTypes module ActiveRecordInterface include InterfaceTypes::BaseInterface description 'Active Record Interface' field :id, ID, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false end end上記はactive recordを利用すると必ず生成されるカラムを定義しています。
ここで定義したInterfaceをObject Typeで継承することができます。object_type.rb# frozen_string_literal: true module ObjectTypes class UserType < ObjectTypes::BaseObject implements InterfaceTypes::ActiveRecordInterface field :name, String, null: false end endinterfaceをimplementsすることで、idやupdate_atをわざわざ書かなくてもfieldとして定義してくれるので、非常に便利です。
interface typeに他にもいろいろ使い道がありそうですが、まだ使ってないのでこれからいろいろ試したいと思います。
- 投稿日:2021-01-29T09:23:17+09:00
rubocopの「The use of eval is a serious security risk.」の私的解釈
evalを使うとrubocopから以下の警告が出る
C: Security/Eval: The use of eval is a serious security risk.evalの動作
evalとはそもそもどんな動作をするのか?
文字列 expr を Ruby プログラムとして評価してその結果を返します。
module function Kernel.#eval より
https://docs.ruby-lang.org/ja/latest/method/Kernel/m/eval.htmlevalの引数に与えた文字列をrubyプログラムとして実行してくれる。
a = nil eval('a = RUBY_RELEASE_DATE') p a #=> "2007-03-13"便利。
なぜevalを使うことがリスクなのか(私的解釈)
rubocopから「The use of eval is a serious security risk.」の警告をもらった時は、なにがリスクなのか分からなかった。
しかし、よくよく考えてみると以下のことをリスクと言っているのではないかと思ったのでまとめる。読みにくい
evalを使う場合と使わない場合を比較してみる。
def fuga p 777 end # 実行 fuga => 777eval('def fuga;p 777 end') fuga #=> 777普通に書いてある方が読みやすいように思う。
悪意あるコードが生成される可能性がある
外部のファイルを読み込むeval
eval('raise RuntimeError', binding, 'XXX.rb', 4) #=> XXX.rb:4: RuntimeError (RuntimeError) # from ..:9外部のデータからコードを生成し実行する場合、悪意あるコードが生成されないように細心の注意を払う必要がある。
上記は外部のファイルを読み込んでいるが、他にもDBとかユーザの入力とか、外部のデータからコードを生成して実行することもできる。
しかし、evalを使わないで書く場合と比べて読みにくいので、どのデータがどのように加工されるか覚えておいたり理解したりは難しい。なので悪意あるコードが実行される可能性が高くなる。まとめ
evalを使ったコードは、多彩な動きができるためその動きを把握しにくいので悪意あるコードが生成される可能性があること、また読みにくいのでそれに気付きにくいということが「リスク」なんだと考えた。
evalを使わずに済むなら使わないで実装を行いたい。
- 投稿日:2021-01-29T08:15:55+09:00
【Rails】日時の文字列をTime.zone.parseしてDBのレコードと一致させる方法
Time.zoneが一致しなくて検索できなかった問題
筆者がRailsのアプリを開発している時に、
日付を一致させる時にひと悶着あったので、備忘録として残しておきます。今回使用する例
productsテーブル
id date (datetime型) 1 2020-11-01 15:00:00 2 2020-11-02 00:00:00 DBの時刻は
UTC
とします。問題
下記の例題で、
true
orfalse
の値と、なぜそれを返す理由が説明できるでしょうか?
RailsアプリのTime.zoneは次の状態だとします。[*] pry(#<ProductsController>)> Time.zone => #<ActiveSupport::TimeZone:0x000055cc45b31408 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil>例題
[1] pry(#<ProductsController>)> Product.find(1).date == "2020-11-01 15:00:00" [2] pry(#<ProductsController>)> Product.find(1).date == "2020-11-02 00:00:00" [3] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00") [4] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00") [5] pry(#<ProductsController>)> Product.find(2).date == "2020-11-01 15:00:00" [6] pry(#<ProductsController>)> Product.find(2).date == "2020-11-02 00:00:00" [7] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00") [8] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00") [9] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00")回答
[1] pry(#<ProductsController>)> Product.find(1).date == "2020-11-01 15:00:00" => true # 文字列で検索した場合、DBのレコードと一致するものを検索します。 # つまり、2020-11-01 15:00:00という値が一致するのでtrueとなります。 [2] pry(#<ProductsController>)> Product.find(1).date == "2020-11-02 00:00:00" => false # 純粋な文字列を比較してるので、 # 2020-11-01 15:00:00 == 2020-11-02 00:00:00はfalseとなります。 [3] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00") => false # 今回のRailsアプリのTime.zoneはTokyoでした。 # Time.zone.parseとは、文字通りアプリのタイムゾーンに合わせて変換することです。 # UTCとTokyoの時間は9時間ずれてる(Tokyoが遅れてる) # DBにsaveする時、2020-11-02 00:00:00 ➡️ 2020-11-01 15:00:00に変換されます。 # RailsがDBからレコードを取り出す時、+9時間して返します。 [4] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00") => true # 上記で説明したとおり、Railsのタイムゾーンと同じ値を検索&parseしたので、trueを返します。 [5] pry(#<ProductsController>)> Product.find(2).date == "2020-11-01 15:00:00" => false # 文字列で検索した場合、DBと一致する値を返すため [6] pry(#<ProductsController>)> Product.find(2).date == "2020-11-02 00:00:00" => true # 文字列で検索した場合、DBと一致する値を返すため [7] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00") => false # parseした後はDBの値 - 9 されるためfalse # parseした後は、2020-11-01 06:00:00になる [8] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00") => false # parseした後はDBの値 - 9 されるためfalse # parseした後は、2020-11-01 15:00:00になる [9] pry(#<ProductsController>)> Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00") => true # parseした後は、2020-11-02 00:00:00になるのでtrue言いたかったこと
今回のRailsのTime.zoneはTokyoなので、
バックエンドのデータに-9時間してsaveしてる。それに合わせて、Time.zoneをTokyoにparseしたら、
欲しかった情報を取得できました!参考
ようやく:こうしきよめ
https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html助けられたteratail
https://teratail.com/questions/148819RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2cおまけ
ふと疑問に思ったので、
ransackのコード見たら、Time.zone.parseしてました。def cast_to_time(val) if val.is_a?(Array) Time.zone.local(*val) rescue nil else unless val.acts_like?(:time) val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val end val.in_time_zone rescue nil end end
- 投稿日:2021-01-29T04:50:10+09:00
Rails デプロイ後 iPhoneでの閲覧時のみページ遷移の挙動がおかしい
はじめに
Qiita初投稿です。
間違っている点、補足等あればご教授いただけますと幸いです。環境
Rails 5.2.4.4
起こったこと
デプロイ後、PCでは問題なく動くのに、iPhoneを使うと挙動がおかしくなりました。
原因発見にかなり時間がかかりましたので共有します。
まずは、具体的な事象を記載します。
- Deviceで実装したユーザー登録画面にiPhoneでアクセスすると画面が真っ白に
- 標準出力を見ると何度も同ページでリロードが行われている
- エラーメッセージは出力されず
- リンクからのアクセスではなく、URL直打ちの場合はアクセスできる ※ここが原因発見の鍵になりました。
原因
結論から申し上げますと、ターボリンクスが影響していました。
ページ遷移の高速化のため、Rails4から標準で搭載されています(application.jsに記載があります)。
JQueryの挙動に影響があるという記事も多いですが、
スマートフォンで、入力フォーム等のページへ遷移する際にエラーが起きる場合があるようです。解決策
_header.html.erb<%= link_to "新規登録", new_user_registration_path, data: {"turbolinks" => false} %>該当のリンクにターボリンクス無効化の記載をすれば問題なくアクセスできるようになりました。
念のためログインページ等フォームが絡むページにも記載。
原因が分かれば一瞬でした、、!
- 投稿日:2021-01-29T01:28:50+09:00
条件分岐の条件分岐
ifの中にif文があって...!?!?
if文に関しては以前も紹介しているので割愛
気になる方はこちらからなかなかややこしかった印象。
現状の私には時間がかかりました。実際のコード
<% if user_signed_in? && @item.record.nil?%> <% if current_user.id == @item.user_id %> <%= link_to '商品の編集', **_path, method: :get, class: "item-red-btn" %> <p class='or-text'>or</p> <%= link_to '削除', **_path, method: :delete, class:'item-destroy' %> <%else%> <%= link_to '購入画面に進む', item_records_path(@item.id),class:"item-red-btn"%> <% end %> <% end %>まずは実現したいことを整理
商品の編集 と 削除 と 購入画面に進む
上記3つのボタンの表示条件を整理していく。実際に整理していった流れもせっかくなので記録してみます。
- ログインしているユーザーなのか
- itemは購入されているのか
- ページを訪れたユーザーはitemの出品者か
3つか?そんな単純じゃないだろ...
- 商品の編集と削除ボタン
- ログインしている出品者本人限定の表示
- 商品が売れて切れていれば表示はされなくても良い
- 購入画面へのボタン
- ログインしている出品者本人以外にのみ表示
- 商品が売れて切れていれば表示はされなくても良い
共通のものがあるか...
共通条件
ログインしていなくてはいけない・商品が売れて切れていれば表示はされないパターン1
ログインしている出品者本人限定の表示パターン2
ログインしている出品者本人以外にのみ表示あ、きたかも...
インデントを下げる/字下げする/ネストする/入れ子にする
言い方は色々あるようですが、要は...
- 親子関係がわかりやすいように
- 読みやすいように
主に上記の2点
可読性にもつながるのでチーム開発の際には特に注意したい所になるかな?今回もまさにそれを使って上記で整理した内容を表現していきます。
本題の解説
<% if user_signed_in? && @item.record.nil?%> <% if current_user.id == @item.user_id %> <%= link_to '商品の編集', **_path, method: :get, class: "item-red-btn" %> <p class='or-text'>or</p> <%= link_to '削除', **_path, method: :delete, class:'item-destroy' %> <%else%> <%= link_to '購入画面に進む', item_records_path(@item.id),class:"item-red-btn"%> <% end %> <% end %>親:
if user_signed_in? && @item.record.nil?
この条件がtrueでなかったら以下(子)の表示はされない
&&
は現状尚且
といった捉え方をしている
- サインインしているユーザーであれば
- 現状、購入情報テーブルにデータが無いのであれば
子 1:
if current_user.id == @item.user_id
- このページに訪れているユーザーのidと出品者のidが一致しているのであれば
子 2:
else
- 上記(子 1)の条件がtrueでないならば
注意点
- controller内のアクションに記載している変数名
- データベースの名称
- データベースのカラム名
などとも関係してくる