- 投稿日: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-29T22:54:48+09:00
もう puts/p をデバッグに使わない! デバッグライブラリ IceCream の Ruby 版
先日、Python 向けのデバッグライブラリ IceCream が話題になっていました。
Python 以外にもいくつかの言語版があるようですが、残念ながら Ruby 版が無い。というわけで作ってしまいました。
はじめに
まず、このライブラリはいわゆる「print デバッグ」1の代替となるものです。
そもそも本格的にデバッグを行う場合、print デバッグではなく Byebug 等のデバッガを用いるべきです。
参考:printデバッグにさようなら!Ruby初心者のためのByebugチュートリアル
とはいえ print デバッグが非常に手軽なものであることは間違いなく、ちょっとしたデバッグにはいちいちデバッガを使うより print デバッグで済ませてしまうという方も多いでしょう。
しかし print デバッグは手軽な分そのままでは困ることも少なくありません。 print デバッグの手軽さはそのままに、もう少し便利にしてくれるライブラリが IceCream (そしてこの Ricecream )だと思います。
概要
print デバッグの困るところ
例えば、変数
foo
の値を調べたいとします。print デバッグの場合、以下のようなコードを追加することになります。p fooこの場合、
123
のような形でfoo
の値が表示されます。print デバッグをする箇所が 1 箇所であればこれで困りませんが、複数の変数をさまざまな場所で print デバッグしようとすると、先のコードでは出力されたのがどの変数なのか判りません。また、もともとさまざまな標準出力を行うプログラムの場合、print デバッグの出力値と本来の出力値が混ざってしまい見づらくなります。
以下のように書けば、そうした問題点を解決できます。
puts "[debug] foo: #{foo}" # => [debug] foo: 123ですが、こんなのを入力するのは面倒です。2 これではせっかくの print デバッグの手軽さを台無しにしてしまいます。
Ricecream の場合
Ricecream を使えば、前者のような手軽な
ic fooというコード 3 で、
ic| foo: 123と表示することができるようになります。
変数等の表示
上の例では単に変数の値をそのまま表示させただけですが、
ic
は任意の式を取ることができます。また、複数の引数も受け取れますし、引数が複数行にまたがっていても問題ありません。require "ricecream" ic foo * 2, Integer.sqrt(foo) # => ic| foo * 2: 246, Integer.sqrt(foo): 11また、
ic
は引数をそのまま戻り値にするので、既存のコードに簡単に追加できます。例えば以下のコードにおいてfoo.bar
の値を確認したいとしましょう。return "result = #{foo.bar}"
puts
を使う場合、puts foo.bar return "result = #{foo.bar}"としてしまうと
foo.bar
が副作用を伴うようなメソッドだった場合、2 回呼び出されてしまうことで予期せぬ動作になる可能性があります。ですのでfoobar = foo.bar puts foobar return "result = #{foobar}"のように書くことになるでしょう。書くのも面倒ですし、デバッグ後の修正作業も面倒です。
一方、
ic
を使えばreturn "result = #{ic foo.bar}"と簡単に書けます。
…とはいえ
p
やpp
も同様に引数を戻り値にするので、この点に関してはあまり Ricecream の優位性はありません。実行の流れを調べる
以下のようなメソッドが、
foo
に到達しなかったとします。def mymethod return if cond1? return if cond2? return if cond3? foo endこのとき print デバッグでは以下のように様々な出力値の
p
をあちこちに追加することで、どこで return したのかを調べることになるでしょう。例えば下の例で「1」「2」が出力されたのであれば、return if cond2?
のところで return したことが分かります。…しかし、出力値を一つ一つ変えて入力する手間がかかりますし、どこで出力された値なのかが直感的には判りません。def mymethod p 1 return if cond1? p 2 return if cond2? p 3 return if cond3? p 4 foo end一方、以下のように引数無しで
ic
を使えばdef mymethod ic return if cond1? ic return if cond2? ic return if cond3? ic foo endoutputic| script.rb:2 in mymethod at 23:59:52.822 ic| script.rb:4 in mymethod at 23:59:52.822のように、どのスクリプトの何行目が実行されたのかが判りやすく表示されます。
限定されたスコープで ic を使う
Ricecream は Kernel を拡張してしまうので、使うのが不安だという方もいるかもしれません。
そんな方のために、Refinements 版もあります。
require "ricecream/refine" using Ricecream icまた、
using Ricecream::P
すればp
をオーバーライドできます。require "ricecream/refine" using Ricecream::P foo = 123 p foo # => ic| foo: 123ic の表示を消す
print デバッグが終わった際に、うっかり追加した
p
を消し忘れてしまうことってありませんか?特に普段は実行されない部分に追加したところとか。もちろん Ricecream を使った場合も
ic
を消し忘れる可能性はあります。ですがRicecream.disable
を実行しておけば、万が一消し忘れたとしても出力がされなくなるので安心です。require "ricecream" ic # => 出力される Ricecream.disable ic # => 出力されない Ricecream.enable ic # => 出力されるその他いろいろ
prefix を変更
prefix (デフォルトでは
ic|
)を変更できます。Ricecream.prefix = "ricecream| "メソッド
Ricecream.prefix
を定義することで、より柔軟な変更ができます。def Ricecream.prefix(location) "ic #{Time.now}| " end出力先の変更
デフォルトでは出力先は STDERR ですが、これも変更できます。
Ricecream.output = STDOUTdef Ricecream.output(str) @log ||= Logger.new("logfile.log") @log.debug(str) end出力方法の変更
デフォルトでは
Object#inspect
(つまりp
相当)を用いて値を出力しますが、Ricecream.arg_to_s
を定義することで変更できます。def Ricecream.arg_to_s(arg) PP.pp(arg, +"", 60) end呼び出し元情報を表示
Ricecream.include_context = true
すると、ic
を呼び出した場所の情報を付けて表示します。ic foo # => ic| foo: 123 Ricecream.include_context = true ic foo # => ic| script.rb:10 in <main>- foo: 123色付け(実験的機能)
Ricecream.colorize = true
すると、色付きで表示します。ただし irb 1.2 以上が必要です。対応できない場合
以下のような場合、
ic
は適切な表示ができません。同じ行に、引数の数が同じ ic
ic foo; ic bar # => ic| foo: 123 # => ic| foo: 456 # ↑ bar なのに…のように同じ行に引数の数が同じ ic があると、正しい表示ができません。
引数の数が異なれば問題ないので、無理やり引数を増やしてしまえば大丈夫です。
ic foo; ic bar, 1 # => ic| foo: 123 # => ic| bar: 456, 1eval 中で ic
eval
中のic
には対応できません。以下のように、とりあえず引数を<arg1>
のように表示します。eval("ic foo") # => ic| (eval):1 in <main>- <arg1>: 123引数展開
ic
に引数を展開して与えると対応できません。しかし、そもそもそんな行為に何の意味があるのでしょうか?ic *[1, 2, 3] # => ic| script.rb:4 in <main>- <arg1>: 1, <arg2>: 2, <arg3>: 3以上のように対応できない場合はありますが、レアケースだと思うので大抵の状況には対応できるはずです。
まとめ
デバッガの使い方を覚えようと思いつつ ついつい面倒で print デバッグしてしまう方は、この Ricecream を使ってみてはいかがでしょうか?
- 投稿日:2021-01-29T22:48:47+09:00
RubyでC言語を実行してみたった(Ruby拡張ライブラリことはじめ)
はじめに
こんにちは。RubyでC拡張ライブラリを作らなければならないことになりそうです。
ここではその最初の一歩を踏み出します。やってみよう
まずは hello world を作ります。Hello world をC言語で書くと
#include <stdio.h> int main(void) { printf("Hello world\n"); }みたいな感じになりますね。これをRubyで再現するのを目標にします。
mkmfファイルを書く
Ruby の拡張ライブラリのための Makefile を作成するライブラリだそうです。
extconf.rb
という名前にするようです。今回は以下のような感じにします。extconf.rbrequire 'mkmf' create_makefile("hello")C言語のヘッダーファイルを作成する
ruby.h
というファイルをincludeするように設定します。hello.h#include "ruby.h"
C言語のソースコードを書く
hello.c#include "hello.h" #include <stdio.h> void Init_hello(void) { printf("Hello world\n"); }これらは同じディレクトリに配置します。
. ├── extconf.rb ├── hello.c └── hello.h作ったる
それでは実行していきます。
ruby extconf.rbMakefileが作成されたはずです。
. ├── extconf.rb ├── hello.c ├── hello.h └── Makefilemakeします
make
hello.o
とhello.so
が作成されました。. ├── extconf.rb ├── hello.c ├── hello.h ├── hello.o ├── hello.so └── Makefileできた
この
hello.so
が作成されたRuby拡張ライブラリということになります。irb
から呼び出してみましょう。irb(main):001:0> irb irb#1(main):001:0> require './hello.so' Hello world => true無事にHello worldが出力されました。成功です。
irb -r ./hello.so
でも実行できます。Ruby拡張ライブラリを作るために必要な知識と資料
下記の資料が参考になりそうです。
- 公式資料 https://ruby-doc.org/core-3.0.0/doc/extension_ja_rdoc.html
- 英語の資料 https://github.com/silverhammermba/emberb
- mkmfリファレンス https://docs.ruby-lang.org/ja/latest/library/mkmf.html
おまけ
せっかく初めての拡張ライブラリ(バイナリファイル)を作成したので、少し中身を観察してみましょう。
lddで共有ライブラリの依存関係をチェックする
ldd hello.solinux-vdso.so.1 (0x00007ffc0eb97000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2404384000) /lib64/ld-linux-x86-64.so.2 (0x00007f240459e000)逆アセンブルしてみる
objdump -d -Sl hello.so今回、逆アセンブルというものを初めてやりましたが、思ったよりも元のコードの情報が残っているのですね。
〜中略〜 0000000000001120 <Init_hello>: Init_hello(): /home/kojix2/Ruby/tmp/hello/hello.c:5 #include "hello.h" #include <stdio.h> void Init_hello(void) { 1120: f3 0f 1e fa endbr64 printf(): /usr/include/x86_64-linux-gnu/bits/stdio2.h:110 } __fortify_function int printf (const char *__restrict __fmt, ...) { return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ()); 1124: 48 8d 3d d5 0e 00 00 lea 0xed5(%rip),%rdi # 2000 <_fini+0xed0> 112b: e9 20 ff ff ff jmpq 1050 <puts@plt>シンボルをチェックする
nm -D hello.so
これは、ffiを使ってバインディグを作る時は、ときどき使いますね。一番最初に Init_helloが見えます。
0000000000001120 T Init_hello w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable w __cxa_finalize@@GLIBC_2.2.5 w __gmon_start__ U puts@@GLIBC_2.2.5この記事は以上です。
- 投稿日: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:58:56+09:00
Ruby on Rails 6でFaceBook APIを本番環境でも使用する
初めまして
私は、現在オリアプ制作中で、FacebookのAPIを使用し、ログインできるようにしたいと思い実装しました。
ローカル環境で動作していたものの本番環境(heroku)ではうまく動作せず、ハマっていたので解決した方法を備忘録として残したいと思います!
APIの詳細設定や導入方法を割愛させていただきます。開発環境
Ruby on rails6
heroku/7.47.11 darwin-x64 node-v-12.16.2解決方法
https://developers.facebook.com/
↑にて
①ログインしていただき、Facebookで作成したアプリ名をクリックします。
②左側のサイドメニューの欄に「FaceBookログイン」→「設定」をクリックします。
③クライアントOAuth設定の画面に飛びますので、「有効なOAuthリダイレクトURL」の中に下記を記述をします。https://ご自身のアプリ名/users/auth/facebook/callback④変更を保存をクリックします。
これで本番環境でも正常に動作することが確認できました。最後に
まだまだ、知識不足ですので、説明足らずな箇所があると思いますがご容赦ください。
ここまで読んでいただきありがとうございました!
- 投稿日: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:21:22+09:00
Ruby学習者がPHPを最新版(v8)にアップデートしてみた
プログラミングでRubyおよびRuby on Railsをメインに学習中ですが、Rubyの復習も兼ねてPHPの基礎も学習することにしました。
パッと見だと通じる部分も多いようなので、PHPを勉強することでRubyの復習にもなるかな…と考えたからです。
今回は、Macに標準でインストール済みのPHPをバージョンアップします。
対象&前提
・Ruby学習者でPHPもかじってみたい人
・Macユーザー(私はMacOS Catalina(10.15.7)使ってます)
・シェルはzshを使用まずは現在のバージョンを確認
ターミナルを起動して、PHPのバージョンを確認するコマンドを叩きます。
% php -v PHP 7.3.11
どうやら、私のPCにはPHP7.3.11というバージョンが入っているようです。
PHP公式 によると、2021年1月現在は「v8.0.1」が最新とのこと。
早速、バージョンアップしていきます。Homebrewを使ってインストール
Macのパッケージ管理ツールであるHomebrewを使ってインストールしました。
※Homebrew導入済みの前提で書きます。Homebrewのインストール方法は一番下の補足を参照。% brew search php@8 # Homebrewに存在するPHPv8のパッケージを確認 ==> Formulae php@8.0 ✔ ==> Casks homebrew/cask/eclipse-php homebrew/cask/netbeans-phpHomebrewにv8があるようなので、インストールコマンド実行。
% brew install php@8.0
インストールにはしばし時間がかかります。
インストール中にたびたび出てくるビールマークの誘惑。インストールが完了したら、バージョンアップされているか確認します。
% php -v PHP 7.3.11
…おや?バージョンアップされてない??
% cd /usr/local/opt/ # いろいろ調べて辿りついたこのディレクトリにはv8いるっぽいが… % ls : php php@8.0 python@3.9 # 余談:あらMacにはデフォルトでPythonも入ってるのね! :これまでの数少ない環境構築の経験から、PHPの古い方を参照し続けているのかと仮定。
設定ファイルにパスを追記することにしました。ターミナルからviエディタを起動して、以下の1行を書きこみ。
export PATH="/usr/local/opt/php@8.0/bin:$PATH"escして:wq(書き込み保存してから終了)。
設定の再読み込みのため、以下のコマンドを実行。% source ~/.zshrc # 設定再読み込み再度、PHPのバージョン確認コマンドを打ってみる。
% php -v PHP 8.0.1 % which php /usr/local/opt/php@8.0/bin/php
PHP v8.0.1に変わっていることを確認できました!
以上で、PHPのアップデートは完了。Rubyの復習
そういや今回はRubyの復習がほぼない。
しいて言えば、バージョン確認コマンドくらい。
Rubyもバージョン3が出たようなのでアップデートしなきゃ。% ruby -v # Rubyのバージョンを確認する ruby 2.6.5補足: Homebrewをインストール
そもそもHomebrewを導入しておらず、インストールする場合の方法を備忘録として残します。
インストールするためのコマンドは Homebrew公式 のトップページに明記されてます。
ページ見るたびビール飲みたくなる。% cd # ホームディレクトリに移動する % pwd # 本当にホームディレクトリにいるかどうか念のため確認 # Homebrew公式にも載ってるコマンドを叩く % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" % brew -v # インストールできたか、バージョン確認コマンドを使って確認 Homebrew 2.5.1 # バージョンが表示されたのでインストール完了 % brew update # 念のためアップデート % sudo chown -R `whoami`:admin /usr/local/bin # Homebrewの権限変更を実施
- 投稿日:2021-01-29T18:13:20+09:00
「JavaScript」基本1
記述を残そうと思った理由
はじめ苦手意識がありなかなか手を付けようとしなかったせいで、だいぶ内容が飛んでしまったのでさらっと復習がてらこちらに残そうと思い記述しています。
目標15分でまとめて行きたいので雑になりますJavaScriptとは何か?
JavaScript(ジャバスクリプト)とはプログラミング言語の1つです。Rubyはサーバーサイドを担う言語でしたが、JavaScriptは主にクライアントサイドにおいて力を発揮します。すなわち、「ブラウザ上でのアプリケーションの使いやすさ」や「リクエストの送り方の工夫」をJavaScriptは担っています。
JavaScriptによって、より使い勝手の良いアプリケーションの作成ができます。Ruby on RailsのWebアプリケーションにJavaScriptを適用する方法はこのあとのLESSONで学びます。本LESSONではJavaScriptのみを学び、より使い勝手の良いアプリケーションを作成するための基礎を身に着けましょう。
JavaScriptは多くの開発現場でも用いられているプログラミング言語です。
JavaScriptは画面を更新せずとも、サーバーと通信ができるためページ遷移なしで、画面の表示を切り替えられる。
動的動きをつけることができる。つまり
かっこよくできるし、便利にできるし、いろいろなことができるよって解釈
特徴は?
デベロッパーツール
コンソールでデバック
コマンドは⌘ + option + C基礎構文
JavaScriptでは変数定義の様式は、var、const、letと3つ存在します。
var
古い書き方
もう使わないconst
再代入ができない書き方
let
再代入ができる書き方
再定義は不可要は
let は再代入する予定のある変数を定義する際に使用する
const は再定義する予定のない変数を定義する際に使用するテンプレートリテラル
テンプレートリテラルは、JavaScriptの構文
ダブルクォートやシングルクォートの代わりにバッククォートで囲むことで、文字列内に挿入することができる。
要は
文字列の中に変数を埋め込むことができる
改行を入れることができる
HTMLの要素を作成することができるconst name = "Tom" const age = 25 const introduction = `私の名前は${name}、${age}歳です` console.log(introduction) // => 私の名前はTom、25歳です と出力される条件分岐
条件を満たしているかどうかで実行内容を分岐する処理
if (条件式1) { // 条件式1がtrueのときの処理 } else if (条件式2) { // 条件式1がfalseで条件式2がtrueのときの処理 } else { // 条件式1も条件式2もfalseのときの処理 }for文
繰り返し処理を行う
for ([①初期化式]; [②条件式]; [③加算式]) { // 繰り返す処理の内容 }forEach関数
配列に対する繰り返し処理
配列.forEach( function(value){ // 処理の記述 })
- 投稿日: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-29T17:13:52+09:00
[Ruby]Twitter API(premium)で死ぬほど苦労した
初記事です。
今回、素人の自分がプレミアム版のTwitterAPIを利用するも、うまくいかず失敗。
ほとんどがスタンダード版についてや古い記事などで、あってもPythonがほとんどでRubyがない……と、参考になる記事もなかったので、他の方のために。
通常版での検索は、他の方が記事にしているので、そちらを参考にしてください。いつもお世話になっているので、困っている方の一助になれたらと。回れ善意!
※当方、素人なので何が最適かとか一切わかりません。試行錯誤でなんとかうまく行った形になります。むしろ改善案求む!1、TwitterAPIのプレミアム版(sandbox)をRailsで利用しようとするができない
まず困ったのが、通常版でよく利用されているTwitter gemを利用した場合、standard版に対してリクエストが送られてしまって、プレミアム版での検索方法が分からなかった点。
自分は素人なので、gemをカスタマイズとかできない!!
ので、oauth認証などを地道にした上で叩くことに。参考にしたのがこちら
「OAuth gemだけでTwitter APIを使ってみる」
https://qiita.com/riocampos/items/5aaa636866af885ef1ac2、プレミアム版で検索できるようになったが、キーワード検索しかできない
HTTPリクエストとかなんとなくでしか理解していない自分。
スタンダード版と同じようにURLのリクエストを作成して送るも
「could not authenticate you.」と表示。認証できない?
でもquery=hogehogeをURL末尾に入れたときは、きちんと動作する。
from:とかmaxResultとか他のパラメーター入れると、これが出てくる。で、原因は2つ。
①sandboxは全てのパラメーターを使える訳ではない
例えば「is:retweet」みたいなパラメーターはプレミアで使えても、無料のsandboxは使えない。日本語の記事のほとんどがスタンダード版についての説明だったので、この内容は英語の質問サイトで発覚。利用できるのは下記URLより。公式ヘルプ大事。
https://developer.twitter.com/en/docs/twitter-api/enterprise/rules-and-filtering/operators-by-product②パラメーターはjson形式&ポストリクエストで作らないとダメ!
ここが一番時間がかかったところ。
というのも、スタンダード版はgetだったことと、URLを書き換えるだけで検索が可能だったので気が付かなかった。
公式ヘルプの例にもcURL式のリクエストしかなく、rubyしかわからない素人の自分にはチンプンカンプン。なのでhttps://jhawthorn.github.io/curl-to-ruby/
を使って、公式のリクエスト例をrubyに変換。
これでようやく気が付きました。横断的に言語学ぶの大事!
※というか英語分かってれば、すぐ気がついたんじゃ……英語も大事!ということで、ようやく検索がうまくいったコードがこちら
require 'oauth' #credentialから呼び出し consumer_key = Rails.application.credentials.twitter[:twitter_api_key] consumer_secret = Rails.application.credentials.twitter[:twitter_api_secret] access_token = Rails.application.credentials.twitter[:twitter_api_access_token] access_token_secret = Rails.application.credentials.twitter[:twitter_api_access_token_secret] consumer = OAuth::Consumer.new( consumer_key, consumer_secret, site:'https://api.twitter.com/' ) endpoint = OAuth::AccessToken.new(consumer,access_token,access_token_secret) #上記で認証完了 require 'json' url = 'https://api.twitter.com/1.1/tweets/search/fullarchive/dev(登録名).json' segment = {"query":"#hogehoge from:hogehoge","fromDate":"201601010000"} segment = segment.to_json enc_str = URI.encode(url) response = endpoint.post(enc_str, segment, {"Content-Type" => "application/json"}) @uri = segment @result = JSON.parse(response.body)以上。
いやあ、きつかった……
- 投稿日: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: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-29T12:47:05+09:00
ransackを使った検索結果を表示する
ransackとは
シンプルな検索機能から複雑な検索機能まで幅広く使える検索に特化したgem
<%= search_form_for @p, url: "検索結果を表示したいパス" do |f| %> <%= f.label :"カラム名", '表示される文字' %> <%= f.collection_select :"カラム名", "配列のデータ", :"参照してくるDBのカラム名", :"表示されるカラム名", "オプション設定" %> <br> <%= f.submit '検索' %> <% end %>こちらが検索機能の形となっています。
search_form_for
を使うことでransack特有の検索フォームを生成できます。下準備
gemfilegem 'ransack'ターミナルbundle install表現したかったこと
1:編集するというボタンを押すと、どのアイテムを編集するか?という検索ページへ飛ぶ
2:検索結果にマッチしたものを表示するページへ遷移
3:編集したいアイテム(画像)をクリックするとそのアイテムの編集ページへ遷移実践
1:ルーティングの設定
config/routesresources :posts do collection do get 'search_edit' #どのアイテムを編集するかの検索ページへ get 'search_edit_result' #検索結果へのページへ end end
seach_edit
とsearch_edit_result
というルーティングを新しく定義します。
(私の場合は、可読性の考慮と詳細設定、削除設定も同じ流れにしたかったので、このような名前で定義してます)2.コントローラーへ定義
postscontrollerbefore_action :search_post, only: [:search_edit, :search_edit_result] def edit @post = Post.find(params[:id]) end def update @post = Post.find(params[:id]) if @post.update(post_params) redirect_to root_path else render :edit end end def search_edit @post = Post.find_by_id(params[:id]) end def search_edit_result @post = Post.find_by_id(params[:id]) @results = @p.result end private def post_params #省略 end def search_post @p = Post.ransack(params[:q]) endこの後作るビューで検索機能を入れます。
その検索結果を@p
として変数にして渡しています。
:q
とは、検索する際に入力した内容である検索パラメータのキーです3.ビューを作成
#省略 <%= link_to 'スタイルを編集', search_edit_posts_path %> #省略まずはこれで
1:編集するというボタンを押すと、どのアイテムを編集するか?という検索ページへ飛ぶ
を実行できます。検索ページ#省略 <%= search_form_for @p, url: search_edit_result_posts_path do |f| %> #条件を入力 <%= f.submit '検索' %> <% end %> #省略こちらで条件を検索します。
url: search_edit_result_posts_path
とすることで、検索結果のページへ遷移することができます。
よって、
2:検索結果にマッチしたものを表示するページへ遷移
を実行できます。検索結果ページ#省略 <% @results.each do |result| %> <%= link_to edit_post_path(result) do %> <%= image_tag result.image.variant(resize: '100x100') %> <% end%> <% end %> #省略これで検索の結果が出力されます。
<%= image_tag result.image.variant(resize: '100x100') %>
こちらは投稿の時に画像を投稿していた場合は、その画像が出力されます。
(ここでは例として、画像の大きさを100x100にしていますが大きさはお好みで)
edit_post_path(result)
と指定することで、その画像をクリックすると編集画面へ遷移できるようになります。これで
3:編集したいアイテム(画像)をクリックするとそのアイテムの編集ページへ遷移
が実行できます。終わりに
今回は編集画面への遷移までの流れでしたが、これに詳細、削除の機能を落とし込もうとすると、単純にルーティングとコントローラーの記載が3倍になってしまいます。
もう少し上手くまとめられないか模索中です。
- 投稿日:2021-01-29T12:24:01+09:00
「 :~ 」と「 .~ 」の使い分け
はじめに
Rubyの学習中よく「 :~ 」と「 .~ 」の使い方の違いで混乱していたので、
今回 「 :id 」と「 .id 」を例にまとめていきたいと思います。
「 :~ 」
「 :~ 」はparamsのキーを指しており、
そのキーの値(バリュー)を格納するために記述する。また、「 :~ 」はDBのカラムと「一致させる」ハッシュのキー(一致するとDBに保存可)で、
これはビューファイルでハッシュのキーを指定する例1)コントローラーで「 :~ 」を記述する場合
def show @post = Post.find(params[:id]) end↑
[ :id ]はparams = { :id => “バリュー”}の:id(キー)の部分を指している。params[:id]は「id」というキーの、値を格納するための記述!
例2)ビューファイルで「 :~ 」を記述する場合
html.erb<%= f.text_area :first_name_kana, class: "input-name", id: "first-name-kana", placeholder: "例)サトウ " %>↑
「:first_name_kana」はハッシュのキーを指しており、ここに「 f.text_area」によって入力されたデータ(バリュー)が入る!params = { :first_name_kana => 入力されたデータ}
キー バリュー例3)レコード{text : ”こんにちは”}があると仮定
params[:text]
DBカラム textカラムがある場合 保存可能
messageカラムがない場合 保存できない※viewファイルからparams(キー: バリュー)は送られてくる。
「.~ 」
「 . 」はほぼ全てメソッド
例)
user.iduser.idは「userオブジェクトのidメソッドの呼び出し」です。
スクエアブラケット
・[ ]のこと
おわりに
ちなみに「 :id 」と「 .id 」と混合しやすい「 _id 」は外部キーだそうです。
今後はparamsについての理解をもっと深めていきたいです。
- 投稿日:2021-01-29T12:12:30+09:00
【Ruby】 find_indexを複数返す方法
find_indexの複数のインデックスが欲しい
find_indexだとマッチした初めのインデックスしか取得できない。
そのため、複数取得できるように備忘録としてまとめました。バージョン
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin19]方法
module ArrayExtension refine Array do def find_indexes(num) map.with_index {|element, i| i if element == num}.compact end end end using ArrayExtension array = [1, 3, 2, 1, 4, 1, 7, 9, 4, 1, 3, 1] array.find_indexes(1) #=> [0, 3, 5, 9, 11]モジュールを定義する方法
- 投稿日: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: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-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-29T07:55:48+09:00
ブログ始めました!
今日からプログラミングのブログを書くことにしました!
Rubyの勉強をしているため、前日勉強したことと今日勉強することをつらつら書いていこうと思います!
プログラミングもブログも初心者なので日々成長していきたいと思います!
間違った認識をしていたらご指摘ください!<前日勉強したこと>
・トロングパラメータ
指定したキーを持つパラメータのみを受け取るように制限するもので、意図しないデータの更新を防ぐことができる。・requireメソッド
送信されたパラメータの情報を持つparamsが使用できるメソッドで、どの情報を取得するかを選択することができる。ストロングパラメータとして使用する際は、主にモデル名を記述する。params.require(:モデル名)・permitメソッド
取得したいキーを指定でき、指定したキーと値のセットのみデータ取得ができる。params.require(:モデル名).permit.(キー名:, :キー名)<今日勉強すること>
・HTTPメッドのPATCH
・update
・before_action今日も頑張ろう!
- 投稿日: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} %>該当のリンクにターボリンクス無効化の記載をすれば問題なくアクセスできるようになりました。
念のためログインページ等フォームが絡むページにも記載。
原因が分かれば一瞬でした、、!