こんにちは。Tomoyuki(@tomoyuki65)です。
最近、Web系エンジニアへ向けてRailsチュートリアル(第6版)の学習を進めていました。
その中で、いくつか躓くポイントがあったので、その点について解決方法などの記録を残しておきます。
※以降の内容は第6版のRailsチュートリアルで学習した際の記録のため、Railsチュートリアルがアップデートされた場合(第7版以降になった際)は内容が変わっている可能性があります。その場合は参考程度にご利用下さい。
目次
- 1 Railsチュートリアル(第6版)を進めるための参考資料
- 2 第1章でローカル環境に開発環境を構築する場合のやり方
- 3 第1章の1.4でGitで使うデフォルトのエディタをVSCodeに変更する方法
- 4 第1章の1.4でGitのデフォルトのブランチ名「master」を「main」に変更する方法
- 5 第1章の1.5でHerokuにデプロイする方法
- 6 第2章の2.2.1で「/users」を開くとエラーが出る
- 7 Herokuに作成したアプリの確認方法と削除する方法
- 8 第3章以降のGitでのブランチ作成から本格的な作業開始までの流れ
- 9 開発を進めるにあたってVSCodeに追加した拡張機能まとめ
- 10 作業完了後からGitHubのmainブランチを更新するまでの流れ
- 11 ローカルのmainブランチをGitHubのmainブランチで強制的に上書きしたい場合
- 12 第6章でSQLite3::BusyException: database is lockedが発生した場合の解決方法
- 13 Railsのタイムゾーンを日本にする方法
- 14 モデルの一意性の検証における注意点
- 15 第6章リスト6.23の正規表現について簡単に解説
- 16 Railsにおけるbcrypt(ビークリプト)の使い方
- 17 「bundle update」と「bundle install」の違いについて
- 18 Railsにおけるデバッグ方法について
- 19 Strong Parametersについて
- 20 本番環境における設定について
- 21 Railsで対象のリソースに対するルーティングを調べる方法
- 22 ブラウザ「Chrome」でcookiesの情報を調べる方法
- 23 「||=(自己代入演算子)」と「&.(ぼっち演算子)」の例
- 24 第8章のBootstrap用にWebpackにjQueryを追加して読み込む方法
- 25 第10章のポイントまとめ
- 26 第11章のポイントまとめ
- 27 第11章の本番環境でのメール送信をGmailから行う方法
- 28 第12章のポイントまとめ
- 29 第13章のポイントまとめ
- 29.1 モデル作成時に指定できる型「references」について
- 29.2 演習問題でMicropost.new実行時にエラーがでた場合の対処方法
- 29.3 default_scopeについて
- 29.4 「dependent: destroy」について
- 29.5 <ol>タグについて
- 29.6 countメソッドについて
- 29.7 ヘルパーメソッド「time_ago_in_words」について
- 29.8 テストの「assert_select ‘h1>img.gravatar’」について
- 29.9 テストの「response.body」について
- 29.10 パーシャルのオプション「object」について
- 29.11 パーシャルの書き方について
- 29.12 renderとredirect_toの違いについて
- 29.13 will_paginateのオプション指定について
- 29.14 「request.referrer」について
- 29.15 RailsのActive Storage機能について
- 29.16 画像の検証にはGem「active_storage_validations」が使える
- 29.17 テストでfixturesフォルダの画像を読み込む例
- 29.18 画像のリサイズ処理について
- 30 第13章の本番環境での画像アップロードをGCS(Google Cloud Storage)で行う方法
- 30.1 ①Google Cloud Platformに登録
- 30.2 ②新規プロジェクトの作成
- 30.3 ③Cloud Storageにバケットを作成
- 30.4 ④認証情報の作成
- 30.5 ⑤Gem「google-cloud-storage」のインストール
- 30.6 ⑥認証情報(機密情報)を「credentials.yml.enc」に設定
- 30.7 ⑦Active Storageの設定を「storage.yml」に追加
- 30.8 ⑧environmentsフォルダ内の環境設定ファイルで対象のストレージを指定
- 30.9 ⑨本番環境には環境変数にmaster.keyの値を設定
- 30.10 ⑩全ての設定が完了!開発環境や本番環境で試す!
- 31 第14章のポイントまとめ
- 32 最後に
Railsチュートリアル(第6版)を進めるための参考資料
- 第1章でローカル環境に開発環境を構築する場合のやり方
- 第1章の1.4でGitで使うデフォルトのエディタをVSCodeに変更する方法
- 第1章の1.4でGitのデフォルトのブランチ名「master」を「main」に変更する方法
- 第1章の1.5でHerokuにデプロイする方法
- 第2章の2.2.1で「/users」を開くとエラーが出る
- Herokuに作成したアプリの確認方法と削除する方法
- 第3章以降のGitでのブランチ作成から本格的な作業開始までの流れ
- 開発を進めるにあたってVSCodeに追加した拡張機能まとめ
- 作業完了後からGitHubのmainブランチを更新するまでの流れ
- ローカルのmainブランチをGitHubのmainブランチで強制的に上書きしたい場合
- 第6章でSQLite3::BusyException: database is lockedが発生した場合の解決方法
- Railsのタイムゾーンを日本にする方法
- モデルの一意性の検証における注意点
- 第6章リスト6.23の正規表現について簡単に解説
- Railsにおけるbcrypt(ビークリプト)の使い方
- 「bundle update」と「bundle install」の違いについて
- Railsにおけるデバッグ方法について
- Strong Parametersについて
- 本番環境における設定について
- Railsで対象のリソースに対するルーティングを調べる方法
- ブラウザ「Chrome」でcookiesの情報を調べる方法
- 「||=(自己代入演算子)」と「&.(ぼっち演算子)」の例
- 第8章のBootstrap用にWebpackにjQueryを追加して読み込む方法
- 第10章のポイントまとめ
- 第11章のポイントまとめ
- 第11章の本番環境でのメール送信をGmailから行う方法
- 第12章のポイントまとめ
- 第13章のポイントまとめ
- 第13章の本番環境での画像アップロードをGCS(Google Cloud Storage)で行う方法
- 第14章のポイントまとめ
第1章でローカル環境に開発環境を構築する場合のやり方
①rbenvでruby 2.6.3をインストール
Homebrewとrbenvが使える状態を前提としますが、特定バージョンのRubyをインストールするため、ターミナル(Macの場合)を開いた後に以下のコマンドを実行します。
※ターミナルのシェルは「bash」です。
$ brew update
$ brew upgrade rbenv ruby-build
$ rbenv install 2.6.3
インストール後、使用するRubyのバージョンを指定します。
$ rbenv global 2.6.3
②ファイル「.gemrc」にRubyドキュメントをスキップする設定を追加する
ターミナル(Macの場合)を開いて、以下のコマンドを実行します。
$ echo "gem: --no-document" >> ~/.gemrc
③rails 6.0.4をインストールする
特定バージョンのrailsをインストールするため、以下のコマンドを実行します。
$ gem install rails -v 6.0.4
④bundler 2.2.17をインストールする
特定バージョンのbundlerをインストールするため、以下のコマンドを実行します。
$ gem install bundler -v 2.2.17
⑤Yarnをインストールする
Railsチュートリアルの中でYarnをインストールする話がありますが、Yarnをインストールするには事前にNode.jsをインストールし、npmコマンドを実行できるようにする必要があります。
まずはnodebrewを使ってNode.jsをインストールするため、以下のコマンドを実行します。
$ brew install nodebrew
次にNode.jsをインストールするため、以下のコマンドを実行します。(安定版をインストールするため、「stable」を指定しています。)
$ mkdir -p ~/.nodebrew/src
$ nodebrew install-binary stable
※後述の”第2章の2.2.1で「/users」を開くとエラーが出る”でも書きましたが、第6版のRailsチュートリアルを進める場合は、nodeのバージョンがv16系の方が望ましいようなので、ターミナルでコマンド「nodebrew ls-remote」を実行してインストール可能なnodeのバージョンを確認後、v16系のバージョンをインストールすることをおすすめします。(v16.15.1を指定したら問題は発生しませんでした!)
$ nodebrew install-binary v16.15.1
インストール後、nodeのバージョンを確認するため、以下のコマンドを実行します。
※nodeを有効化していない場合は、以下コマンド実行後のcurrent:の値が「none」になっています。
$ nodebrew ls
nodeのバージョン確認後、nodeを有効にするため、以下のコマンドを実行します。
$ nodebrew use 上記で確認したnodeのバージョンを指定
次にnpmコマンドを実行できるようにするため、ファイル「.bash_profile」にPATHを設定します。設定するには以下のコマンドを実行します。
※今回はターミナルのシェルがbashのため、ファイル「.bash_profile」にパスを設定しています。例えばシェルがzshの場合はファイル「.zshrc」にパスを設定する必要があるのでご注意下さい。
$ echo "export PATH=$HOME/.nodebrew/current/bin:$PATH" >> ~/.bash_profile
$ source ~/.bash_profile
以下のコマンドを実行し、npm、node、npxのバージョンが確認できればOKです。
$ npm -v
$ node -v
$ npx -v
最後にYarnをインストールします。
$ npm install -g yarn
インストール後、以下のコマンドを実行し、Yarnのバージョンが確認できればOKです。
$ yarn -v
⑥Railsチュートリアル用のディレクトリを作成する
Railsチュートリアル用のディレクトリを作成し、作成したディレクトリに移動するため、以下のコマンドを実行します。
※下記例ではディレクトリ名を「railstutorial」にしましたが、別の名前に変更してもOKです。
$ mkdir railstutorial
$ cd railstutorial
⑦「1.3 最初のアプリケーション」の内容を進める
Railsチュートリアル1章の1.3 最初のアプリケーションの内容を進めるため、以下のコマンドを実行します。
$ mkdir environment
$ cd environment
$ rails _6.0.4_ new hello_app
$ cd hello_app
rails newでファイル作成後、Gemfileの内容を修正します。
# 元の内容を=begin、=endで囲んで全てをコメントアウト
=begin
〜 元の内容 〜
=end
# Railsチュートリアルの第1章リスト1.8の内容に置き換え
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.4'
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.10.3', 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'
end
# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Gemfileの内容を修正後、Gemfileの内容を反映させるため、以下のコマンドを実行します。
$ bundle _2.2.17_ config set --local without 'production'
$ bundle _2.2.17_ update
$ bundle _2.2.17_ install
※ここではGemfileの内容を大幅に修正しているため、bundle updateが必要になる。Gemを追加するだけとかならbundle installのみ実行すればOK
次にYarnでWebpackerをインストールするため、次のコマンドを実行します。
※以下のコマンド実行後、いくつかのファイルが既に存在していて「上書きしますか?」と何度か聞かれるので、随時「no」を入力してEnterを押していけばいいっぽいです。
$ rails webpacker:install
※第2章からはwebpackを利用することになりますが、その際にはインストール後にコンパイルも必要になります。コンパイルする場合はコマンド「rails webpacker:compile」を実行して下さい。
最後にRailsサーバーを実行するため、ターミナルで新規ウインドウを立ち上げてから以下のコマンドを実行します。
$ cd railstutorial/environment/hello_app
$ rails s
ブラウザで「http://localhost:3000/」を開き、デフォルトのRailsページが表示されればOKです。
第1章の1.4でGitで使うデフォルトのエディタをVSCodeに変更する方法
これはエディタがVSCodeを利用している方向けの設定になります。GitのデフォルトのエディタをVSCodeに変更するには、VSCodeを開いてからコマンド「shift + command + p」を実行します。
コマンドパレットが開いたら、入力欄に「Shell Command: Install ‘Code’ command in path」を入力し、画面をクリックして実行します。
次にターミナルから以下のコマンドを実行します。
※事前にGitが導入されている前提です。
$ git config --global core.editor "code --wait"
これでgit commitを実行した際にVSCodeが起動するようになります。
※尚、git commitを実行してVSCodeが起動した後、コミットのメッセージを記入して保存し、画面を閉じるとコミット処理が進みます。メッセージを入力せず画面を閉じるとコミットされません。
第1章の1.4でGitのデフォルトのブランチ名「master」を「main」に変更する方法
2020年10月以降、GitHubのデフォルトブランチ名が「master」から「main」に変更になったようです。
そのため、gitのメインのブランチ名が「master」の場合(git init後)は、GitHubに合わせて「main」に変更した方がわかりやすいので、以下のコマンドを実行してブランチ名を変更します。
$ git branch -m master main
また、リスト1.18でgit pushする場合は以下のコマンドを実行する。
$ git push -u origin main
尚、次のコマンドを実行すると、gitのデフォルトのブランチ名を変更できるため、git init実行後にメインブランチ名がmainになるようになります。
$ git config --global init.defaultBranch main
第1章の1.5でHerokuにデプロイする方法
①Herokuをインストールする
Herokuをインストールするには、以下のコマンドを実行します。
$ brew tap heroku/brew && brew install heroku
インストール後、以下のコマンドを実行してバージョンを確認します。
$ heroku --version
②Herokuにログインする
事前にHerokuのアカウントを作成している前提ですが、以下のコマンドを実行し、メールアドレスとパスワードを入力するとログインできます。
$ heroku login --interactive
③Heroku上に本番環境を作成する
以下のコマンドを実行し、Heroku上に本番環境を作成します。
$ heroku create
④Gemfile.lockに設定を追加する
ローカルの開発環境からHerokuにデプロイする場合、そのままだとエラーが発生するため、以下のコマンドを実行してGemfile.lockに設定を追加します。
$ bundle lock --add-platform x86_64-linux
設定追加後、Gitでコミットし、GitHubにもプッシュします。
$ git commit -a -m "Add platform"
$ git push
⑤Herokuにデプロイする
以下のコマンドを実行し、Herokuにデプロイします。
※前述でgitのブランチ名を「master」から「main」に変更しているため、以下のコマンドでは「main」を指定しています。
$ git push heroku main
デプロイ後、上記③で作成した時にターミナルに表示されたURLを開き、Railsチュートリアルで作成途中の画面が表示されることを確認します。
⑥Herokuコマンドでアプリケーション名を変更する
上記③で作成されたURLはデフォルトのアドレスになりますが、任意のアプリケーション名に変更することも可能です。(例:https://任意のアプリ名.herokuapp.com)
アプリ名を変更する場合は、以下のコマンドを実行します。
※任意のアプリ名については、既に使われている名前は使用できないのでご注意下さい。
$ heroku rename 任意のアプリ名
アプリ名変更後、変更したURLにアクセスできればOKです。
第2章の2.2.1で「/users」を開くとエラーが出る
第2章の2.2.1を進めている際に、「http://localhost:3000/users」を開きましたが、エラー画面が表示されました。
内容としては、「app/public/packs/manifest.json」が読み込めてないというものでしたが、これはwebpackerのコンパイルがされてないということ(そもそもapp/publicの中にpacksフォルダ自体がなかった)なので、ターミナルでコマンド「rails webpacker:compile」を実行しました。
すると、その際にもエラーが発生し、コンパイルがうまくいきませんでしたが、webpack(v5系以前)でnode(v17系以上)の場合に出るバグのようなので、以下のコマンドを実行してオプションを追加するといいっぽいです。
※webpack(v6系)以上では解決しているらしいです。
$ export NODE_OPTIONS=--openssl-legacy-provider
その後、以下のコマンドを実行してwebpackerのコンパイルを実行します。
$ rails webpacker:compile
コンパイル完了後、ブラウザから「http://localhost:3000/users」を開いて画面が表示されればOKです。
尚、この後にRailsチュートリアルを進め、「2.3.5 アプリケーションをデプロイ」するの最後でHerokuにデプロイ後、コマンド「heroku log」で内容を確認すると「/usr/local/opt/heroku-node/bin/node: –openssl-legacy-provider is not allowed in NODE_OPTIONS」というメッセージが表示されました。(その後のコマンド「heroku run rails db:migrate」が実行できなかった。)
内容としては先ほど設定した環境変数「NODE_OPTIONS」が許可されていないということなので、以下のコマンドを実行して環境変数を削除すると問題が解決しました。
$ export -n NODE_OPTIONS
この問題については、そもそもwebpackのバージョンに対して、nodeのバージョンが高すぎるのが原因だと思うので、最初からnodeのバージョンをv16系にして進めれば発生しないと思います。(またはwebpackのバージョンをv6系以上にする)
Herokuに作成したアプリの確認方法と削除する方法
Herokuに作成したアプリを確認するには、以下のコマンドを実行します。
$ heroku apps
そして、アプリを削除する場合は、以下のコマンドを実行します。
$ heroku apps:destroy --app アプリ名
その後、警告メッセージが表示されるので、もう一度アプリ名を入力してEnterを押すと削除できます。また、以下のコマンドを実行すると一回で削除可能です。
$ heroku apps:destroy --app アプリ名 --confirm アプリ名
第3章以降のGitでのブランチ作成から本格的な作業開始までの流れ
第3章から本格的な開発練習を行なっていきますが、開発を進めるにはまず以下のコマンドを実行し、Gitでブランチを作成します。
$ git checkout -b ブランチ名
※オプション「-b」を付けるとブランチを作成し、その後に作成したブランチに切り替わります。
ブランチ作成後、新規ファイルを作成した場合など本格的な作業に入る前には、一度ファイルをコミットし、GitHubにもブランチをプッシュしておきます。
$ git commit -am "コミットメッセージ"
$ git push -u origin ブランチ名
※git push のオプション「-u」は「–set-upstream」の省略形で、このオプションをつけるとローカルリポジトリの現在のブランチの上流をorigin mainに規定することになります。それにより、次からはgit pushだけで上記コマンド「git push -u origin ブランチ名」と同じことを実施できます。
これで準備は完了したので、ここから本格的な開発作業を進めて行きます。
開発を進めるにあたってVSCodeに追加した拡張機能まとめ
- Ruby:Ruby用の拡張機能
- indent-rainbow:インデントに色を付ける拡張機能
- Auto Close Tag:終了タグを自動で入力する拡張機能(設定でAuto-close-tag: Sublime Text3 Modeを有効にする)
作業完了後からGitHubのmainブランチを更新するまでの流れ
完了した作業用ブランチをローカルのmainブランチにマージし、その後GitHubにプッシュする場合(個人開発を想定した場合)
開発を進める前に作成した作業用ブランチでの作業が完了後(コミットまで完了後)、ローカルのmainブランチにマージするには以下のコマンドを実行します。
※個人開発を想定した場合の作業例です。
$ git checkout main
$ git merge ブランチ名
mainブランチにマージ後、GitHubのmainブランチにプッシュするには以下のコマンドを実行します。
$ git push
最後に先ほどマージした作業用ブランチを以下のコマンドを実行して削除します。
$ git branch -d ブランチ名
※git branchのオプションが「-d」の場合、削除対象のブランチがリモートブランチにプッシュおよびマージ済みの場合のみ、削除を実行します。プッシュやマージされていないブランチを強制的に削除したい場合は、代わりに「-D」オプションを指定します。
プルリクエストを作成し、レビュー完了後にmainブランチへマージする場合(チーム開発を想定)
開発を進める前に作成した作業用ブランチでの作業が完了後(コミットまで完了後)、以下のコマンドを実行して作業用ブランチをGitHubにプッシュします。
※チーム開発を想定した作業例です。
$ git push
※上記のコマンドは、事前に「git push -u origin ブランチ名」を実行している場合になります。
次にGitHubからプルリクエストを作成します。
GitHubにある対象のリポジトリを確認し、先ほどプッシュしたブランチの「Compare & pull request」をクリックします。
プルリクエスト作成画面が表示されるので、必要事項を記載後に画面右下の「Create pull request」をクリックします。
プルリクエストが作成されると下図のように表示されるため、レビュー担当者はプルリクエストの「Files changed」または「Commits」の履歴からソースコードの変更内容を確認します。
例えば「Files changed」をクリックしてソースコードの変更内容を確認後、もし修正が必要な場合は対象の箇所で「+」をクリック後、レビュー内容を記載してプルリクエスト作成者に修正依頼の通知を出すことが可能です。
※もし修正することになった場合は、修正担当者がソースコードを修正してコミットし、再度GitHubにプッシュ後、プルリクエストの「Conversation」からレビュー担当者のコメントに対して修正が完了した旨の返信コメントを記載し、再度レビューを行なって下さい。
レビューの結果問題が無い場合は、レビュー担当者(マージ担当者)がプルリクエストの「Conversation」画面の下にある「Merge pull request」をクリックし、マージ処理を進めます。
必要に応じてコメントなどを記載し、「Confirm merge」をクリックしてマージします。
その後、プルリクエストのステータスが「Merged」になり、プルリクエストはクローズです。
プルリクエストがクローズした後はマージ元のブランチは不要になるため、特に残す必要がなければ画面下の「Delete branch」をクリックしてブランチを削除します。
これでプルリクエストからマージまでの作業が完了です。
その後、作業担当者はGitHubのリポジトリから最新のリポジトリ内容をプルします。
$ git checkout main
$ git pull origin main
最後に作業が完了したローカルの作業用ブランチを以下のコマンドを実行して削除します。
$ git branch -d ブランチ名
ローカルのmainブランチをGitHubのmainブランチで強制的に上書きしたい場合
例えばローカルのmainブランチとGitHubのmainブランチに差異が生じ、ローカルのmainブランチをGitHubのmainブランチ(正しい方で)で強制的に上書きしたい場合は、次の操作を実行すると可能です。
$ git checkout main
$ git fetch origin main
$ git reset --hard origin/main
第6章でSQLite3::BusyException: database is lockedが発生した場合の解決方法
第6章のリスト6.29でマイグレーションファイルを作成後、コマンド「rails db:migrate」を実行するとエラー「SQLite3::BusyException: database is locked」が発生した。
以下のコマンドを実行してDBを初期化した後、再度「rails db:migrate」を実行すると問題が解決できた。
$ rails db:reset
Railsのタイムゾーンを日本にする方法
RailsのデフォルトのタイムゾーンはUTCになっており、そのままだとDBの更新日時にはUTCの時間が設定されます。
Railsのタイムゾーンを日本にするには、config/application.rbに以下の設定を追加します。
module アプリ名
classApplication < Rails::Application
〜 省略 〜
# タイムゾーン設定を日本にする
config.time_zone = "Asia/Tokyo"
config.active_record.default_timezone = :local
end
end
※本番環境については、これらに加えてサーバー側ののタイムゾーン設定(例えばHerokuならコマンド「heroku config:add TZ=Asia/Tokyo」で設定する)も必要になるかと思います。
モデルの一意性の検証における注意点
モデルに対して一意性の検証をする際には、バリデーション「uniqueness: true」や「uniqueness: { case_sensitive: false }」の設定を追加することになりますが、Active Recordはデータベースのレベルでは一意性を保証していないので注意が必要(トラフィックが多い時に問題が発生する可能性がる)です。
データベースに対して一意性の検証を追加するにはマイグレーションファイルを作成後、対象のカラムに「unique: true」を追加する。
class マイグレーションファイル名 < ActiveRecord::Migration
def change
add_index テーブル名, カラム名, unique: true
end
end
第6章リスト6.23の正規表現について簡単に解説
第6章リスト6.23には最終的なメールアドレス用の正規表現で「/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i」を使っていますが、それぞれの要素を簡単に解説すると次のようになります。
※正規表現はrubular.comで試す
正規表現 | マッチ条件 |
/ | 正規表現の開始 |
\A | 文字列の先頭 |
[\w+\-.]+ | 英数字、アンダースコア(_)、ハイフン(-)、ドット(.)を1回以上繰り返す |
@ | @が含まれている |
[a-z\d\-]+ | アルファベット、数字、ハイフン(-)を1回以上繰り返す |
(\.[a-z\d\-]+)* | 『ドット(.)の後に「アルファベット、数字、ハイフン(-)」を1回以上繰り返す』を0回以上繰り返す(存在しない or 1回以上存在する場合OK) |
\. | ドット(.)が含まれている |
[a-z]+ | アルファベットを1回以上繰り返す |
\z | 文字列の末尾 |
/ | 正規表現の終了 |
i | 大文字小文字を無視するオプション |
Railsにおけるbcrypt(ビークリプト)の使い方
セキュアなパスワードを追加する場合など、パスワードの文字列をハッシュ化するためにbcrypt(ビークリプト)を使うことがあるが、Railsでの使い方は以下の通りです。
※ここでのハッシュ化とはハッシュ関数を使って入力されたデータを元に戻せない(不可逆な)データにする処理を指す(例えばユーザーの認証はパスワード送信、ハッシュ化、データベース内のハッシュ化された値と比較という手順で進む)
①Gemfileにgem「bycrypt」を追加
Railsでbcrypt(ビークリプト)を使うためにはGemのインストールが必要なので、まずはGemfileにGem「bcrypt」を追加します。
gem 'bcrypt'
Gemfile修正後、以下のコマンドを実行してインストールします。
$ bundle install
※Gemfileに新しくGemを追加し、それだけを反映させる場合については、「bundle update」は不要なので、「bundle install」だけをを実行して下さい。
②対象のモデルに「password_digest」カラムを追加
例えばユーザーのモデル「user」に「password_digest」カラムを追加する場合は、以下のコマンドを実行してマイグレーションファイルを作成する。
$ rails g migration ファイル名_to_users password_digest:string
※上記コマンドのファイル名について、末尾を「to_users」にすると、usersテーブルにカラムを追加するマイグレーションファイルを作成してくれる
マイグレーションファイル作成後、以下のコマンドを実行してDBに反映させる。
$ rails db:migrate
③対象のモデルに「has_secure_password」メソッドを追加
Gemの追加と対象のモデルにカラム「password_digest」を追加すると、メソッド「has_secure_password」を使えるようになります。
対象のモデルにメソッド「has_secure_password」を追加すると、仮想的なpassword属性とpassword_confirmation属性が付与(存在性のバリデーションも強制的に追加)され、それらの属性に文字列を設定すると、ハッシュ化された値を追加したカラム「password_digest」に保存するようになります。
class User < ApplicationRecord
has_secure_password
end
「authenticate」メソッドの戻り値について
モデルに「has_secure_password」を追加すると、「authenticate」メソッドを使えるようになりますが、これは引数に渡された文字列(パスワード)をハッシュ化した値と、データベース内にあるカラム「password_digest」の値を比較します。
そして、「authenticate」メソッドの戻り値については、パスワードが間違っていれば「false」、正しければ「オブジェクト」を返します。
>> user = User.new(name: "Tarou", email: "tarou@example.com", password: "foobar", password_confirmation: "foobar")
>> user.authenticate("test")
false
>> user.authenticate("foobar")
=> #<User id: 1, name: "Tarou", email: "tarou@example.com", created_at: "2022-07-09 07:00:00", updated_at: "2022-07-09 07:00:00", password_digest: [FILTERED]>
「bundle update」と「bundle install」の違いについて
ここまでで何度か「bundle update」や「bundle install」を実行したと思いますが、それらのコマンドには違いがあるので注意が必要です。
その違いについては、「bundle update」の場合はGemfileを元にGemのインストールを行い、その後にGemfile.lockを更新します。
そして「bundle install」の場合は最初にGemfile.lockを元にGemのインストールを行なった後、Gemfile.lockに記載が無いかつGemfileに記載があるGemをインストールし、最後にGemfile.lockを更新します。(Gemfile.lockに記載があるGemは更新しない)
そのため、例えば本番環境に対して新しいGemを追加する場合などは、安易に「bundle update」を実行するとGemのバージョンのズレが発生したり、アプリケーションがクラッシュする原因に繋がる可能性があるので注意しましょう。
Railsにおけるデバッグ方法について
開発環境のHTMLにデバッグ情報を表示する方法
開発環境のHTMLに対してデバッグ情報を表示させたい場合は、「application.html.erb」のfooterの下の部分あたりに以下のコードを差し込む。
<%= debug(params) if Rails.env.development? %>
※開発環境のみHTML画面にデバッグ情報を表示
そして、デバッグ表示用のCSSを設定する。(例えばBootstrapでcustom.scssにCSSを記載している場合は以下の通り)
/* ミックスインの設定 */
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
その後、開発環境のHTML画面を確認すると、デバッグ情報が表示されます。
Gem「byebug」によるデバッグ
もっと直接的にデバッグする方法として、Gem「byebug」を利用することも可能です。
このGemをインストールしている場合は、debuggerメソッドが利用でき、対象のソースコードに差し込むことでデバッグが可能です。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
# デバッグしたい箇所にメソッドを記載
debugger
end
def new
end
end
※デバッグ完了後は記載したメソッド「debugger」の削除を忘れないように注意!
その後、ブラウザから対象のURLにアクセス(リクエストを発行)し、Railsサーバーを立ち上げたターミナルを確認するとbyebugプロンプトが表示されます。
Strong Parametersについて
Railsにはリクエストに対して不正なパラメータの入力を防ぐ仕組みとして、「Strong Parameters」があります。(Rails4系以降はこれを使う)
例えばユーザーコントローラーのcreateアクションにこの仕組みを導入する方法の例としては次の通りです。
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
# 保存成功時の処理
else
# 保存失敗時の処理
end
end
private
def user_params
# :user属性を必須とし、名前、メールアドレス、パスワード、
# パスワードの確認の属性をそれぞれ許可し、それ以外を許可しない場合
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
本番環境における設定について
本番環境でSSLを利用する場合
本番環境でSSLを利用する場合は、「config/environments/production.rb」という本番環境の設定ファイルにある「config.force_ssl = true」のコメントを外して有効化します。
これによりSSLを強制し、httpsによる安全な通信を確立できます。
そして、本番環境のWebサイトでSSLを使うためには、ドメイン毎にSSL証明書を購入し、セットアップして下さい。
※例えばHerokuのサブドメインを利用して使う場合などは、自動的にSSLが有効かされているため、そういう場合は設定不要です。
HerokuのWebサーバーにPumaを使う方法
HerokuのデフォルトのWebサーバーはWEBrickですが、本番環境に適したWebサーバーとしてPumaを利用することも可能です。
Rails5以降はデフォルトでPumaのGemがインストールされているため、設定ファイル「puma.rb」を修正し、ルートディレクトリ直下にファイル「Procfile」を追加することでHerokuで利用できるようになります。
# Pumaの設定ファイル
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") {
max_threads_count }
threads min_threads_count, max_threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { ENV['RACK_ENV'] || "production" }
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
preload_app!
plugin :tmp_restart
web: bundle exec puma -C config/puma.rb
本番環境のデータベース設定
本番環境のデータベース設定については、「config/database.yml」のproductionセクションに設定を追加して下さい。
〜 省略 〜
production:
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
database: アプリ名_production
username: アプリ名
password: <%= ENV['アプリ名_DATABASE_PASSWORD'] %>
Herokuのタイムゾーン設定
Herokuのタイムゾーンを日本にする場合は、以下のコマンドを実行する。
$ heroku config:add TZ=Asia/Tokyo
Railsで対象のリソースに対するルーティングを調べる方法
Railsでルーティングを調べる方法としてコマンド「rails routes」がありますが、grepコマンドを合わせることで対象のリソースに対するルーティングのみを調べることが可能です。
$ rails routes | grep users
ブラウザ「Chrome」でcookiesの情報を調べる方法については、画面を右クリックしてメニューを開き、メニューの「検証」をクリックします。
次に検証画面のタブ「Application」をクリックします。
次に検証画面左側のメニューにある「Sgrate>Cookies」から対象のサイトを選択するとcookiesの情報を確認できます。
そして、cookiesの有効期限については項目の「Expires / Max-Age」の値を確認します。
※cookiesは明示的に削除しないと消えないため、例えばログアウト処理の後にsessionが正常に削除されているかどうかを確認するには、デバッグなど別のアプローチが必要です。(ブラウザを閉じればセッションが切れるが、cookies自体は消えない)
「||=(自己代入演算子)」と「&.(ぼっち演算子)」の例
# 通常のif文
if @current_user.nil?
@current_user = User.find_by(id: session[:user_id])
else
@current_user
end
# 上記を1行で書く場合
@current_user = @current_user || User.find_by(id: session[:user_id])
# 上記を「||=(自己代入演算子)」で書く場合
@current_user ||= User.find_by(id: session[:user_id])
# 通常のif文
if user && user.authenticate(params[:session][:password])
# 上記を「&.(ぼっち演算子)」で書く場合
if user&.authenticate(params[:session][:password])
第8章のBootstrap用にWebpackにjQueryを追加して読み込む方法
第8章ではBootstrapのドロップダウンメニュー機能を導入しますが、この機能を有効にするためにはjQueryも読み込む必要があります。
そのため、まずは以下のコマンドを実行してYarnでjQueryとBootstrapのJavaScriptライブラリをインストールします。
$ yarn add jquery@3.4.1 bootstrap@3.4.1
次にWebpackの設定ファイル(environment.js)にjQueryの設定を追加します。
const { environment } = require('@rails/webpacker')
// 〜 ここから 〜
const webpack = require('webpack')
environment.plugins.prepend('Provide',
newwebpack.ProvidePlugin({
$:'jquery/src/jquery',
jQuery:'jquery/src/jquery'
})
)
// 〜 ここまでを追加 〜
module.exports = environment
最後にapplication.jsファイルでjQueryを読み込みます。
require("jquery")
import "bootstrap"
第10章のポイントまとめ
<a>タグにtarget=”_blank”属性を付与する際の注意点
<a>タグにtarget=”_blank”属性を付与すると、リンクをクリック時に新しいタブやウインドウで開くようになるが、そのままだとセキュリティ上の問題(リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう)もあるため、合わせてrel=”noopener”属性も付与する必要があります。
バリデーションのオプション「allow_nil: true」について
モデルのバリデーションにオプション「allow_nil: true」を付けると、対象の値がnilの場合でもバリデーションをスキップできるようになります。
ただし、例えば新規データの登録時では存在性の検証が必要になることが多いと思うので、このオプションを付ける場合は他の箇所に影響が無いかを確認すること。
※Railsチュートリアルではパスワードにこのオプションを付け、パスワードがnilでもデータ更新ができるようにしているが、ユーザーの新規登録時は「has_secure_password」メソッドでパスワードの存在性の検証をしているため、影響が出ないようになっている。
認可する場合は「beforeフィルター」を使う
認証(authentication)はサイトのユーザーを識別することで、認可(authorization)はそのユーザーが実行可能な操作を管理することですが、認可をする場合はコントローラーに「beforeフィルター」を付与することになります。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
〜 省略 〜
private
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
フレンドリーフォワーディングについて
ログインが必要なページに対して、ユーザーが未ログイン状態でアクセスした際に、一度ログイン画面に遷移させ、その後ユーザーがログインした場合、元々開こうとしていたページにリダイレクトさせてあげる機能のこと。
module SessionsHelper 〜 省略 〜 # アクセスしようとしたURLを覚えておく def store_location session[:forwarding_url] = request.original_url if request.get? end # 記憶したURL(もしくはデフォルト値)にリダイレクトする def redirect_back_or(default) redirect_to(session[:forwarding_url]) || default) session.delete(:forwarding_url) end
end
※URLを保存する際はGETリクエスト時のみ保存する(POST、PATCH、DELETEリクエストができなくなる可能性を考慮)ようにし、リダイレクトの際は保存したセッションの削除が必要です。
Gem「faker」について
テスト用にサンプルデータを作る場合は、Gem「faker」を使うことで架空の文字列(架空の名前など)を簡単に作成できるので便利です。
name = Faker::Name.name
ページネーション(pagination)について
ユーザー一覧を表示する場合など、表示するデータが多い場合はページネーション(pagination = ページ分割)を導入する。
例えばBootstrapのページネーションスタイルを使う場合は、Gem「will_paginate」、「bootstrap-will_paginate」をインストールし、HTMLファイルに「<%= will_paginate %>」を差し込む。(対象データの取得は「モデル.paginate(page: params[:page])」)
パーシャルのリファクタリング例
①リファクタリング前のコード例
<% provide(:title, "All users") %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
<%= will_paginate %>
②リファクタリング1回目
<% provide(:title, "All users") %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<%= render user %>
<% end %>
</ul>
<%= will_paginate %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
③リファクタリング2回目(完成)
<% provide(:title, "All users") %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate %>
※Railsは@usersをUserオブジェクトのリストであると推測、ユーザーのコレクションを与えて呼び出した後、自動的にユーザーのコレクションを列挙し、それぞれのユーザーをパーシャルで出力します。
Herokuのデータベースをリセットするコマンド
$ heroku pg:reset DATABASE --confirm アプリ名
$ heroku run rails db:migrate
尚、seedからデータを作る場合は以下のコマンドを実行する。
$ heroku run rails db:seed
第11章のポイントまとめ
特定のアクションへの名前付きルートが必要な場合の例
Rails.application.routes.draw do
〜 省略 〜
resouces :account_activations, only: [:edit]
end
beforeアクションやメソッド参照の例
beforeアクションには、before_save(バリデーションに成功し、実際にオブジェクトが保存される直前で実行)やbefore_create(before_saveの後に実行)なども存在する。
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
〜 省略 〜
private
def downcase_email
#self.email = email.downcase
email.downcase!
end
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
db/seeds.rbについて
コマンド「rails db:seed」を実行すると、seed.rbの内容を元にプログラムを実行し、DBにテスト用データを作成可能。
# create!は登録できない場合にfalseではなく例外を発生させる
User.create!(name: "Test User",
email: "test@example.com",
password: "test123",
password_confirmation: "test123")
99.times do |n|
#名前の生成はGem「faker」を利用する
name = Faker::Name.name
email = "example-#{n+1}@example.com"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password)
end
また、データベースをリセットして、seedからデータを登録するには以下のコマンドを実行します。
$ rails db:migrate:reset
$ rails db:seed
fixtureの注意点
test/fixtures/users.ymlにテスト用のモデルデータを記載した場合、コマンド「rails test」実行時にテスト環境にデータが登録される処理が走るため、特にモデルにカラムを追加した場合などはusers.ymlも修正が必要になる場合があるので注意が必要です。
送信メールのプレビューについて
Railsで送信メールのプレビューをしたい場合は、config/environmentsフォルダ内のファイル「development.rb」に以下の設定を追加します。
Rails.application.configure do
・
・
host = 'localhost:3000'
config.action_mailer.default_url_options = { host: host, protocol: 'http' }
・
・
end
そして、test/mailers/previewフォルダ内のファイル「user_mailer_preview.rb」を必要に応じて修正すれば、「http://localhost:3000/rails/mailers/user_mailer/password_reset」にアクセスすると送信メールのプレビューが見られます。
※user_mailer_preview.rbでは、app/mailersフォルダ内にある各種mailerファイルのメソッドを呼び出しています。
assignsメソッドについて
Gem「rails-controller-testing」を使うとassignsメソッドを利用できますが、これは対応するアクション内のインスタンス変数にアクセスできるようになるため、テストを書く際に便利です。
ただし、Rails 5以降はデフォルトのRailsテストで非推薦化されているため、使用する際はGemをインストールして下さい。
update_columnsについて
データ更新時にはupdate_columnsで複数の項目を1回で更新することも可能ですが、update_columnsは直接SQLを実行するため、モデルのコールバックやバリデーションが実行されない点は注意が必要です。
第11章の本番環境でのメール送信をGmailから行う方法
第11章の11.4では本番環境でのメール送信の話があり、そこではHerokuのアドオン「Mailgun」を利用しています。
ただし、このアドオンを利用するにはHerokuのアカウントにクレジットカードの登録が必要になるため、私はテスト用のGmailアカウントからメール送信をできるようにしてテストを行いました。
設定方法などについては、以下にまとめておきます。
①Gmailアカウントの作成&2段階認証の有効化
セキュリティのことも考えて普段とは別のGmailアカウントを用意し、2段階認証まで完了させて下さい。(2段階認証をするには電話番号が必要です。)
②アプリパスワードの作成
アプリ用のパスワードを作成するため、まずはGoogleアカウントのホーム画面にあるメニュー「セキュリティ」をクリックします。
次にGoogleへのログイン欄にある「アプリ パスワード」をクリックします。
次に画面下にある「アプリを選択」から「その他(名前を入力)」をクリックします。
次に任意のアプリ名を入力し、右下の「生成」をクリックします。
その後、画面が切り替わってアプリ用の16桁のパスワードが表示されるので、それを一度メモして下さい。(後で環境変数を設定するファイル「credentials.yml.enc」に記載します。)
※このパスワードは他人に知られると危険なので注意
③config/credentials.yml.encに機密情報の設定
configフォルダ内のファイル「credentials.yml.enc」は、機密情報を管理するためのファイルで、configフォルダ内のmaster.keyと紐付けて暗号化されています。
環境変数を使わずRails単体で機密情報を管理できるため、ローカル環境に依存しない便利な機能です。(Rails5.1まではサーバーの環境変数に機密情報をセットし、Railsで読み込んだりしていた。)
このファイルを編集するには、ターミナルから以下のようなコマンドを実行します。
# Vimで開く場合
$ EDITOR='vim' rails credentials:edit
# VScodeで開く場合
$ EDITOR='code --wait' rails credentials:edit
※VScodeで開く場合は、事前にVScodeのコマンドパレットから「shell」を検索し、PATH内に’code’をインストールする必要があります。
コマンドを実行するとファイルが開いて編集可能になるため、以下のようにGmailの機密情報を設定します。編集完了後、ファイルを閉じると保存されます。
# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifires ・・・
secret_key_base: 8be4・・・・・・
#Gmailの機密情報を追記
gmail: user_name: 'XXXX@gmail.com' # 使用するGmailアドレス
password: 'XXXXXXXXXXXXXXXX' # 上記で作成した16桁のアプリパスワード
④config/environments/production.rbにMaler用の設定を追加
本番環境でMalerを使う場合は、config/environmentsフォルダ内にあるファイル「production.rb」対して、以下のようなMailer用の設定を追加します。
Rails.application.configure do
・
・
・
# Mailer用の設定を追加
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
host = '<あなたのHerokuサブドメイン名>.herokuapp.com'
config.action_mailer.default_url_options = { host:host }
ActionMailer::Base.smtp_settings = {
:port => 587,
:address => 'smtp.gmail.com',
:user_name => Rails.application.credentials.gmail[:user_name],
:password => Rails.application.credentials.gmail[:password],
:domain => 'gmail.com',
:authentication => :login,
}
・
・
・
end
※hostに設定する値はあなたのHerokuサブドメイン名になるので注意して下さい。また、user_nameとpasswordについては、「Rails.application.credentials.〜」の部分で先ほど設定したファイル「credentials.yml.enc」から値を読み込むように設定します。そのほか、port、address、domainは上記のように固定でOKです。最後にメールサーバー側で認証する時はパスワードをBase64でエンコードする必要があるようなので、authenticationの値は「:login」にしています。
⑤本番環境の環境変数にmaster.keyの値を設定
credentials.yml.encから値を読み込むためには、master.keyの値も必要になります。master.keyはローカル環境にあり、GitHubなどには公開しないのが基本なので、master.keyの値は本番環境の環境変数に直接設定する必要があります。
そのため、Herokuに環境変数「RAILS_MASTER_KEY」を設定する場合は以下のコマンドを実行します。
$ heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
⑥本番環境にアプリをデプロイし、メール送信テストを実施
最後に本番環境にアプリをデプロイすれば全ての設定が完了です。実際にアプリを動かして、メール送信のテストを実施してみて下さい。
# GitでアプリをHerokuにデプロイ
$ git push heroku
# Herokuのデータベースをリセット(任意)
$ heroku pg:reset DATABASE --confirm アプリ名
# Herokuでマイグレーションを実行
$ heroku run rails db:migrate
第12章のポイントまとめ
from_withのオプション「:local」、「:scope」について
ビューでフォームを作成する際にform_withを使っていますが、まずオプション「:local」については、ブラウザなどの読み込みをするような同期通信を実装する場合には「:local true」を設定します。(JavaScriptでAjax通信によるフォーム送信なら不要)
そして、オプション「:scope」については、これによってフォームの各要素のname属性をグループ化することが可能です。
# :scopeを設定していない場合
params[:title]
# :scopeに:postを指定した場合の例
params[:post][:title]
※尚、オプションで「model」を指定している場合は、自動的にグループ化されます。(例えばuserモデルを指定している場合、params[:user][:email]のようになる)
隠しフィールドについて
URLのリンクから画面を開いた場合など、URLに付いているパラメータの値を一時的に保持しておきたい場合は、隠しフィールドを使うことになると思います。
# params[:email]に値を設定したい場合の例
<%= hidden_field_tag :email, @user.email %>
# params[:user][:email]に値を設定したい場合の例
<%= form_with( 〜省略〜 ) do |f| %>
<%= f.hidden_field :email, @user.email %>
<% end %>
Time.zone.nowについて
日付を更新する際に「Time.zone.now」を使っていますが、これはRailsで拡張されたメソッド(Active::Support:TimeWithZoneクラス)であり、タイムゾーン設定はRailsのapplication.rbの設定が適用されます。
通常のTimeクラス(Rubyのクラス)を使ってしまうと、開発者のローカル環境の設定に依存してしまう(Aさんの環境変数TZがUTC、Bさんの環境変数TZがJSTという場合に、表示される時間に違いが出る)ため、Railsの開発ではActive::Support:TimeWithZoneクラスを使うのが基本です。
第13章のポイントまとめ
モデル作成時に指定できる型「references」について
モデル作成時に「references」型を指定すると、自動的にインデックスと外部キー参照付きのカラムを追加でき、対象とした他のテーブルと関連付けることが可能になります。
例えばMicropostモデルを作成する際に、Userモデルと1対1の関係を紐付けたい場合は、以下のようなコマンドを実行する。(マイグレーション実行後、Mcropostモデルが作成され、「belongs_to :user」も付与される。)
$ rails g model Micropost content:text user:references
また、合わせてインデックスを付けたい場合は、マイグレーションファイルに「add_index :microposts, [:user_id, :created_at]」などを追加し、その後にマイグレーションを実行する。
演習問題でMicropost.new実行時にエラーがでた場合の対処方法
演習問題の1.でRailsコンソールでMicropost.newを実行するという内容がありますが、試しに実行すると「NameError (uninitialized constant Micropost)」というエラーが発生しました。
これについては、Spring(プリローダー)を再起動すると治ったので、同じ問題が発生した方は以下のコマンドを実行してみて下さい。
※私の場合はstartする場合にはbundle execも必要でした。
$ spring stop
$ bundle exec spring start
default_scopeについて
モデルに対して「default_scope」付けることで、モデルからデータを取得する際の条件を付けたりすることが可能です。
例えば「default_scope -> { order(created_at: :desc)}」を付けると、データ取得時に作成日の降順で取得できるようになります。(SQLの条件にorder by created_at descが付く)
ただし、リレーションが複雑なモデルに対して「default_scope」を使うと予期しないバグを生む原因になりやすいようなので、基本的には使わない方がいいっぽいです。
尚、上記の「->」についてはラムダ式という文法であり、Procやlambda(もしくは無名関数)と呼ばれるオブジェクトを作成する方法です。
「->」はブロックを引数に取り、Procオブジェクトを返しますが、callメソッドが呼ばれたときにブロック内の処理を評価します。
「dependent: destroy」について
リレーションしているモデルに対してオプション「dependent: destroy」を付けると、モデルが削除された場合にリレーション先のモデルも一緒に削除できるようになります。
例えば以下のようにUserモデルのマイクロポストモデルへのリレーションに「dependent: destroy」を付けると、Userモデルが削除されたら紐付いたマイクロポストモデルも一緒に削除されます。
# user.rbのサンプルコード
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
・
・
end
<ol>タグについて
HTMLで順序無しのリストを表示する場合は<ul>タグを使いますが、順序付きのリストを表示する場合は<ol>タグを使います。
countメソッドについて
データの件数を取得する場合は、基本的にcountメソッドを利用すれば問題ないが、それでもボトルネックになるようなら、さらに高速な「counter cache」を使うことも可能です。
ヘルパーメソッド「time_ago_in_words」について
ヘルパーメソッド「time_ago_in_words」を使うことで、例えば「3分前に投稿」といった文字列を出力可能です。
また、ヘルパーメソッドはRailsコンソールのhelperオブジェクトから呼び出すことができます。
#Railsコンソールの例
> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"
テストの「assert_select ‘h1>img.gravatar’」について
テストで使用した「assert_select ‘h1>img.gravatar’」について、これはh1タグの内側にあるgravatarクラス付きのimgタグを指定しています。
テストの「response.body」について
テストで使用した「response.body」について、これにはそのページの完全なHTMLが含まれているため、HTMLの要素を検証する際に使えます。
パーシャルのオプション「object」について
パーシャルのオプションに「object: f.object」を指定する(例えば「<%= render ‘shared/error_messages’, object: f.object %>」など)ことで、パーシャルの関連付けられたオブジェクトにアクセスできるようになります。
ただし、これを使うためにはパーシャルのファイルでも、オブジェクト名を「object」に修正する必要があります。(例えばパーシャルで<% if @user.errors.any? %>というのは、<% if object.errors.any? %>のように修正する。)
パーシャルの書き方について
パーシャルは呼び出すコントローラーに依存するため、基本的には明示的に指定すると予期せぬエラーを回避できます。(フォルダ名/パーシャル名にする。例えば「shared/error_messages」など。)
renderとredirect_toの違いについて
データの登録処理などにおいて、成功した場合はredirect_to、失敗した場合はrenderを使っていますが、render後はURLが変わってしまうため、その後にブラウザの更新などをすると予期せぬエラーに繋がるため注意が必要です。
※例えばHome画面(localhost:3000/)でmicropostsコントローラーの処理をして失敗した場合、renderするとURLが「localhost:3000/microposts」のようになってしまう。
そのため、登録処理に失敗した場合にはredirect_toをした方が予期せぬエラーを回避できますが、入力していた項目の内容などが全てクリアされてしまい、もう一度入力し直す必要が出てくるため、ユーザビリティの低下に繋がります。
この問題の解決方法はいくつかあるようですが、例えばバックエンド側で簡単に解決するなら、登録処理に失敗した際に、入力していた項目内容をセッション(session[:micropost]など)に一時的に保存しておき、リダイレクト後にセッションから項目内容を読み出して表示することで解決可能になりますが、それでも色々と問題点があるようなので、本格的な解決には色々と検討が必要なので注意しましょう。
関連記事👇
>> 【Qiita】現場から学ぶ、RailsのControllerアンチパターン
will_paginateのオプション指定について
will_paginateについても、直前に実行したコントローラーの処理に影響を受けてしまうため、そういう場合はオプションでコントローラーとアクションを明示的に設定して下さい。
※home(localhost:3000/)でmicropostsコントローラーの処理をすると、ページネーションのリンクが変わってしまう。(localhost:3000/?page=2となって欲しいが、localhost:3000/microposts?page=2のようになってしまう。)
# params:で明示的にコントローラーとアクションを指定すると解決可能
<%= will_paginate @feed_items,
params: { controller: :static_pages, action: :home } %>
「request.referrer」について
「request.referrer」は一つ前のURLを返す(見つからない場合はnilを返す)ため、例えば異なるページから同一のリクエストが実行された場合でも、処理実行後にリクエストが発行された元のページに戻すことが可能になります。(非常に便利)
また、「redirect_to request.referrer || root_url」という書き方については、「redirect_back(fallback_location: root_url)」という書き方に変えることも可能です。
RailsのActive Storage機能について
Railsでファイルのアップロード処理などについては、Active Storage機能を利用することで簡単に実現可能です。
Active Storageをアプリケーションに追加するには、以下のコマンドを実行します。
$ rails active_storage:install
$ rails db:migrate
また、指定のモデルに対してアップロードされたファイルを関連付けたい場合は、「has_one_attached」メソッドを使います。(マイクロポスト1件につき画像1件の場合)
# Micropostモデル(models/micropost.rb)に画像を追加する例
class Micropost < ApplicationRecord
・
has_one_attached :image
・
・
end
※has_many_attachedオプションもある
次にHTMLファイルでは「file_field」を指定するとファイルのアップロードボタンを表示できます。
#HTMLファイルの例
<%= form_with(・・・) do |f| %>
<div class="field">
</div>
<%= f.submit "Post", class: "btn btn-primary">
<span class="image">
<%= f.file_field :image %>
</span>
<% end %>
あとはコントローラーでインスタンス変数にアップロードされたファイルを格納します。(格納後は保存処理を実行し、データベースに登録。)
# micropostsコントローラーで画像をインスタンス変数に格納する例
class MicropostsController < ApplicationController
・
・
def create
@micropost = current_user.microposts.build(micropost_params)
@micropost.image.attach(params[:micropost][:image])
・
・
end
・
・
private
# ストロングパラメーター
def micropost_params
params.require(:micropost).permit(:content, :image)
end
end
コントローラーの処理で画像を保存後、HTMLファイルで画像を表示するにはimage_tagを使います。
<%= image_tag micropost.image if micropost.image.attached? %>
画像の検証にはGem「active_storage_validations」が使える
画像のサイズやフォーマットに対してバリデーションを実装するためには、Gem「active_storage_validations」が使えます。
Gemをインストール後、対象のモデルに対して以下のように実装可能です。
# Micropotモデルに紐付く画像にバリデーションをかける際の例
class Micropost < ApplicationRecord
validates :image, content_type: { in: %w[image/jpeg image/gif image/png],
message: "エラー時のメッセージ" },
size: { less_than: 5.megabytes,
message: "エラー時のメッセージ" }
end
また、jQueryなどを使うことで、クライアント側(ブラウザ)にも画像アップロードのサイズやフォーマットをチェックする仕組みを追加することも可能です。
# jQueryでファイルサイズをチェックする例
<script type="text/javascript">
$("#micropost_image").bind("change", function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert("Maximum file size is 5MB. Please choose a smaller file.");
$("#micropost_image").val("");
}
});
</script>
そのほか、HTMLの「file_field」でオプション「accept」を付けると、画像を選択する画面で対象外のファイルタイプは灰色で表示させることも可能です。
# file_fieldでオプション「accept」を付ける例
<span class="image">
<%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
</span>
テストでfixturesフォルダの画像を読み込む例
テストで画像を扱う場合は、以下のようにするとfixturesフォルダに置いた画像を読み込めます。
# test
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
・
・
test "テストケース" do
・
・
image = fixture_file_upload('test/fixtures/kitten.jpg', 'image/jpeg')
・
・
end
end
画像のリサイズ処理について
画像のリサイズ処理をするような場合は、画像を操作するプログラムが必要になるため、例えばImageMagickというプログラムなどを使用します。(本番環境がHerokuなら既にImageMagickが使えるようになっている)
MacでImageMagickをインストールするためにはHomebrewを導入している必要がありますが、その場合は以下のコマンドを実行してインストールを行います。
$ brew install imagemagick
※私の場合はインストール時にエラーが出ましたが、コマンド「brew doctor」を実行後、表示された解決方法(You should〜の部分に問題解決のための実行すべきコマンドが表示される)を実行後、再度インストールを実行すると出来ました。
その後、モデルにリサイズするためのメソッドを追加すれば利用できます。
# 表示用のリサイズ済み画像を返すメソッドの例
class Micropost < ApplicationRecord
・
・
・
def display_image
image.variant(resize_to_limit: [500, 500])
end
end
※resize_to_limitでは、画像の横縦比を維持したまま、指定した横幅、縦幅を超えないようにリサイズします。
第13章の本番環境での画像アップロードをGCS(Google Cloud Storage)で行う方法
第13章の12.4.4では本番環境での画像アップロードをAWSのS3を利用して行なっていますが、その場合は永久無料枠のようなサービスが無いと思うので、私はGCS(Google Cloud Strage)を使って試しました。
GCSを利用するにはクレジットカードの登録が必要になるのがネックになるかもしれませんが、上手く設定すれば永久無料枠で利用できるので、今回のような学習目的ならGCSの方がいいのかなと思います。
ということで、以下に設定方法などについてまとめておきます。
※クレカ登録無しで完全無料でやろう思い、Cloudinaryというサービスを使えないかも試しましたが、データ登録以外の部分で上手く行かない感じだったので断念しました。(モデルを削除してもCloudinary側のデータが削除されなかったり、画像のリサイズ処理が上手くいかなかったり。。)
①Google Cloud Platformに登録
まずはGoogle Cloud Consoleにアクセスし、Google Cloud Platformの登録を行うため「無料トライアルに登録」をクリックします。
※クレジットカードの登録が必要です。
次に組織またはニーズの説明を選択し、利用規約に同意したらチェックボックスを付け、「続行」をクリックします。
電話番号で認証を行うので入力し、「コードを送信」をクリック後、SMSで送られてくるコードを入力して認証を進めて下さい。
次にお支払いプロファイルに関する登録を行い、「無料トライアルを開始」をクリックします。
次にアンケートが表示されるので回答し、右下の「完了」をクリックします。
次にチュートリアルが表示されますが、特に見たいものがなければ右下の「今回はスキップ」をクリックします。
これで登録が完了です。
②新規プロジェクトの作成
次に新規プロジェクトを作成するため、画面上のプロジェクト名のところをクリック(ここで作成したプロジェクトを切り替えられます。)します。
※先ほどの登録時に新規プロジェクトも自動で作成されていますが(不要なら消してOK)、GCPではプロジェクト毎に管理する(プロジェクトを消せばクリーンになる)ため、Railsチュートリアル用のプロジェクトを新たに作成することにしています。
次にプロジェクト選択画面が表示されるので、画面右上の「新しいプロジェクト」をクリックします。
次にプロジェクト名を入力し、「作成」をクリックします。これで新しくプロジェクトが作成されるので、先ほどのプロジェクト選択画面で選択して下さい。
※プロジェクト名は後で使うのでメモしておいて下さい。
③Cloud Storageにバケットを作成
次にCloud Storageの設定(データを保存する場所を作る。これをバケットと言う。)を行うため、画面左のメニューにある「Cloud Storage」をクリックします。
次に画面上の「バケットを作成」をクリックします。
バケット名を入力し、「続行」をクリックします。
※バケット名は後で使うのでメモしておいて下さい。
次にロケーションを選択しますが、無料枠で使える対象が決まっているので注意して下さい。
今回はRegion、us-west1(オレゴン)を選択し、「続行」をクリックします。
※2022年7月現在はロケーションタイプはRegion(単一リージョン)、ロケーションはus-west1、us-central1、us-east1だけのようですが、今後変更される可能性もあるのでご注意下さい。
次にストレージクラスを選択しますが、これも無料枠は「Standard」のみになるので、選択して「続行」をクリックします。
次にアクセス制御を選択できますが、特に要件がなければ均一で問題ないと思うので「続行」をクリックします。
最後にデータを保護する方法も選択できますが、これも特に必要ないと思うので「なし」で画面下の「作成」をクリックします。
これでバケットの作成が完了です。
※無料枠の条件をしっかり選んでいれば、5GBまでは永久に無料で使えるようです。(そんなに容量は多くないので超えないようにご注意下さい。学習用なら十分でしょう。)
④認証情報の作成
次にバケットにアクセスするための認証情報を作成するため、画面左のメニューから「APIとサービス>認証情報」をクリックします。
次に画面上の「認証情報を作成」をクリックします。
次に「サービスアカウント」をクリックします。
次にサービスアカウント名を入力し、「作成して続行」をクリックします。
次にロールを「Storageオブジェクト管理者」を選択し、「完了」をクリックします。
※この権限だとバケットにあるデータの編集などは行えるようですが、バケット自体の作成や削除はできないようです。
これで認証情報が作成されたので、サービスアカウント欄にある認証情報の右のペンマークをクリックします。
次に画面中央にあるメニューの「キー」をクリックします。
次に画面左下にある「鍵を追加>新しい鍵を作成」をクリックします。
次にキーのタイプは「JSON」でOKなので、画面右下の「作成」をクリックします。
これで鍵が作成され、ローカルにJSONファイルがダウンロードされます。これでGoogle Cloud Platformでの作業は完了です。
尚、このJSONファイルの中に認証に必要な機密情報が書かれているため、後でその内容の「credentials.yml.enc(機密情報を管理するファイル)」に設定して読み込めるようにします。
※JSONファイルは公開しないように注意して下さい。
⑤Gem「google-cloud-storage」のインストール
RailsのActive StorageでGCSを利用するにはGem「google-cloud-storage」をインストールする必要があるため、Gemファイルに追記し、コマンド「bundle install」を実行して下さい。
gem 'google-cloud-storage'
$ bundle install
⑥認証情報(機密情報)を「credentials.yml.enc」に設定
次に先ほど取得したJSONファイルの機密情報を、credentials.yml.encファイルに設定します。
※config/credentials.yml.encは機密情報を管理するためのファイルで、config/master.keyと紐付けて暗号化されています。環境変数を使わずRails単体で機密情報を管理できるため、ローカル環境に依存しない便利な機能です。(Rails5.1まではサーバーの環境変数に機密情報をセットし、Railsで読み込んだりしていた。)
このファイルを編集するには、ターミナルから以下のようなコマンドを実行します。
# Vimで開く場合
$ EDITOR='vim' rails credentials:edit
# VScodeで開く場合
$ EDITOR='code --wait' rails credentials:edit
※VScodeで開く場合は、事前にVScodeのコマンドパレットから「shell」を検索し、PATH内に’code’をインストールする必要があります。
コマンドを実行するとファイルが開いて編集可能になるため、以下のようにGCSの認証情報をJSONファイルからコピーして記載します。編集完了後はファイルを閉じると保存されます。
# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifires ・・・
secret_key_base: 8be4・・・・・・
#GCSの認証情報を追記
gcs:
project_id: "JSONファイルからコピー"
private_key_id: "JSONファイルからコピー"
private_key: "JSONファイルからコピー"
client_email: "JSONファイルからコピー"
client_id: "JSONファイルからコピー"
client_x509_cert_url: "JSONファイルからコピー"
⑦Active Storageの設定を「storage.yml」に追加
次にActive Storageの設定を「config/storage.yml」に追加します。
・
・
google:
service: GCS
project: "メモしておいたプロジェクト名"
credentials:
type: "service_account"
project_id: <%= Rails.application.credentials.dig(:gcs, :project_id) %>
private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %>
private_key: <%= Rails.application.credentials.dig(:gcs, :private_key).dump %>
client_email: <%= Rails.application.credentials.dig(:gcs, :client_email) %>
client_id: <%= Rails.application.credentials.dig(:gcs, :client_id) %>
auth_uri: "https://accounts.google.com/o/oauth2/auth"
token_uri: "https://accounts.google.com/o/oauth2/token"
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
client_x509_cert_url: <%= Rails.application.credentials.dig(:gcs, :client_x509_cert_url) %>
bucket: "メモしておいたバケット名"
・
・
※機密情報はRails.application.credentialsメソッドで読み込んでいるのと、type、auth_uri、token_uriは固定でいいため、プロジェクト名とバケット名を変えてもらえばそのままコピペで使えると思います。
⑧environmentsフォルダ内の環境設定ファイルで対象のストレージを指定
最後にenvironmentsフォルダ内にある環境設定ファイル(開発環境なら「development.rb」、本番環境なら「production.rb」)で、Active Storageで使う対象のストレージを指定(config.active_storage.service = :google)します。
Rails.application.configure do
・
・
# Active Storageの設定を「:google」に変更
#config.active_storage.service = :local
config.active_storage.service = :google
・
・
end
⑨本番環境には環境変数にmaster.keyの値を設定
先ほど設定した「credentials.yml.enc」ファイルから値を読み込むためには、「master.key」の値も必要になります。
master.keyはローカル環境にあり、GitHubなどには公開しないのが基本なので、master.keyの値は本番環境の環境変数に直接設定する必要があります。
そのため、例えばHerokuに環境変数「RAILS_MASTER_KEY」を設定する場合は、以下のコマンドを実行します。
$ heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
⑩全ての設定が完了!開発環境や本番環境で試す!
これで全ての設定が完了したので、開発環境や本番環境で試してみて下さい。
上手くいくと、先ほど作成したバケットに画像ファイルが保存されます。(対象のモデルを削除すると、Cloud Storage内のファイルも削除されます。)
第14章のポイントまとめ
モデルに対するリレーション設定について
モデルに対してリレーションを設定する際に、存在しないモデル名を付けたい場合は、クラス名を明示的に指定する。(active_relationshipsモデルは存在しない)
class User < ApplicationRecord
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
end
リレーションを設定した場合のバリデーションについて
リレーションを設定した場合、Rails5以降はバリデーションチェックが自動的に付く模様なので注意。(バリデーションを省略されている可能性を考慮しておく)
リレーションの「has_many through」について
多対対のリレーションでは、「has_many through」を使います。そして、リレーション名を変えたい場合は、sourceも指定します。(例えばfollowdsが不適切なので、followingを使いたいとすると下記のようにする。)
class User < ApplicationRecord
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through :active_relationships, source: :followed
end
ルーティングの「member」について
「/users/1/following」や「/users/1/followers」のようにしたい場合は、ルーティングで「member」を使う。
resources :users do
member do
get: :following, :followers
end
end
※上記はどちらもデータを表示するページなので、GETリクエストを指定している。
また、idを指定せずに全てのメンバーを表示する場合はcollectionメソッドを使う。
resources :users do
collection do
get: :tigers
end
end
※この場合は「/users/tigers」となる。
RailsでAjaxを使う場合について
RailsでAjaxを使う場合は、form_withのオプション「local」を「false」に設定する。(これでformタグの内部でdata-remote=”true”となる)
<%= form_with(・・・, local: false) do |f| %>
<% end %>
そして、対応するコントローラーでAjaxリクエストに応答できるようにします。(respond_toを使う)
class RelationshipsController < ApplicationController
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
# Ajaxに対応
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
また、ブラウザ側でJavaScriptが無効になっていた場合(Ajaxリクエストが送れない場合)でもうまく動くようにするには、config/application.rbに以下の設定を追加します。
require_relative 'boot'
・
・
module SampleApp
class Application < Rails::Application
・
・
# 認証トークンをremoteフォームに埋め込む
config.action_view.embed_authenticity_token_in_remote_forms = true
end
end
最後にAjaxリクエストを受信した場合は、Railsが自動的にアクション名と同じ名前を持つJavaScript用の埋め込みRuby(.js.erb)ファイル(例えばcreate.js.erbやdestroy.js.erb)を呼び出すため、それらのファイルを作ります。
$('#follow_form').html("<%= escape_javascript(render('users/unfollow')) %>");
$('#followers').html('<%= @user.followers.count %>')
そのほか、Ajaxのテストをする場合は、「xhr: true」オプションを使います。
class FollowingTest < ActionDispatch::IntegrationTest
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, params: { followed_id: @other.id }
end
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
end
end
サブセレクトについて
feedのデータを取得す際にまず以下のようにデータを取得するが、この場合はSQLを2回実行しているためパフォーマンスが悪い。
class User < ApplicationRecord
・
・
def feed
Micropost.where("user_id IN(?) OR user_id = ?", following_ids, id)
end
・
・
end
そのため、サブセレクトを利用して以下のようにすると、SQLを実行する回数が1回になるため、パフォーマンスが向上する。
class User < ApplicationRecord
・
・
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN(#{following_ids})
OR user_id = :user_id", user_id: id)
end
・
・
end
最後に
今回はRailsチュートリアル(第6版)の学習を進めて行く上で、躓きそうなポイントや解決方法などについて記録を残しました。
私の場合は全体を通して約100時間ぐらい学習(1週目は読むだけで約14時間程度、2週目はコーディングも実施して約80時間程度)しましたが、Webアプリケーションを開発する上での基本的な部分の理解がめちゃめちゃ深まったので学習して非常に良かったなと思いました。
これからRailsチュートリアルの学習を始める方や、過去に学んだことを振り返りたい方は、ぜひこの記事を参考にしてみて下さい。
合わせて読みたい👇
Tomoyuki
最新記事 by Tomoyuki (全て見る)
- 37歳Web系エンジニア3年目。生成AI(ChatGPT・Gemini)現る。 - 2024年7月3日
- 【スト6】モダン豪鬼の初心者向けコンボまとめ【STREET FIGHTER 6(ストリートファイター6)】 - 2024年5月26日
- Laravel11の変更点を踏まえてバックエンドAPIを開発する方法まとめ - 2024年5月20日
コメントを残す