- 投稿日:2021-02-25T22:45:37+09:00
productionモードでサーバーを立ち上げるとcssが反映されない。
producition用のデータベース作成
rails db:create RAILS_ENV=productionアセットのプリコンパイルを行います
rails assets:precompile RAILS_ENV=productionサーバー起動
rails s -e production -b 0.0.0.0...cssが反映されない。。
解決策
config/enviroments/production.rbの
config.assets.compile = falseconfig.assets.compile = trueに変更したらうまくいきました。
アセットファイルがないときにファイルを探して自動コンパイルしてくれる設定のようです。
- 投稿日:2021-02-25T19:46:15+09:00
test
良い記事を書くためのガイドライン
このガイドラインについて
Qiitaは、プログラミングに関する知識を記録・共有するためのサービスです。Qiita上の記事は、投稿者以外の人にとっても価値のある記事であることが望まれます。あなたの知識が他の誰かの役に立つようにするために、このガイドラインを参考にしてください。
役に立つ記事を書くために
Qiitaに投稿した記事はフィードに表示され、他のユーザーが目にすることになります。また検索から記事を見つける人もいるでしょう。あなたの記事が読んだ人の役に立つかどうかを意識してみましょう。自分用のメモのつもりで書いたものでも、少し気をつけるだけでぐっと価値のある記事にすることができます。
意識すると望ましいもの
具体的で簡潔なタイトルをつける
記事のタイトルは、記事のページだけでなくQiitaのフィードやGoogleの検索結果にも表示されます。タイトルを見ただけで記事の中身を想像できるようにしましょう。具体的なタイトルをつけることで、特定の情報を探している人が記事を見つけやすくなります。
例:
「Rubyメモ」→「Rubyの定数探索はスコープと継承関係のどちらが優先されるか」
「正規表現でハマった」→「RubyとJavaScriptの正規表現では複数行モードの意味が違う」
「OpenSSLのバグ」→「CentOS 6.5でOpenSSLのHeartbleedバグにパッチを当てた」
環境や前提条件を説明する
記事で取り上げたソフトウェアのバージョンは何か、記事中のコードを実行するためにはどんな手順が必要かを書いておくと、自分の環境に当てはまるかどうかを後から読む人が判断しやすくなります。例:
この記事ではCentOS 6.5にインストールしたRuby 2.2を使っています。
コードを実行するためには http://... で紹介されている設定ファイルが必要です。
コマンドライン引数に与えるユーザー名は適宜読み替えてください。
また、適切なタグをつけることで記事を見つけやすくなります。ある言語やライブラリの特定のバージョンについて話をするときは、タグにバージョンをつけてみましょう。例: Ruby:2.2引用元や参考元へのリンクを書く
他のwebページから文章を引用したらそのページへのリンクを張りましょう。そのほか、記事の内容を理解する助けになるページがあればリンクすると親切です。例:
色々な言語で配列の重複を削除する方法
uniq.plsub uniq { my %seen; grep !$seen{$_}++, @_; } say uniq(1, 1, 2, 3, 3);(uniq関数は http://stackoverflow.com/q/7651/454997 より引用)
uniq.rbp [1, 1, 2, 3, 3].uniq(uniqにブロックを与える書き方は http://ruby-doc.org/core-2.2.0/Array.html を参照)
記事を書くうえで気をつけること
どんなに役に立つ記事であっても、最低限守らなければならないルールがあります。著作権に気をつける
自分のものではない文章を引用したり画像を掲載したりするときは、著作権で認められた引用の範囲を超えないように注意しましょう。ライセンスや秘密保持契約に気をつける
利用にあたっての条件がライセンスで定められている著作物を記事に含めるときは、そのライセンスに従うようにしましょう。プレリリース版や開発者向けプレビュー版のソフトウェアの中には、秘密保持契約 (NDA; Non-Disclosure Agreement) によって情報の公開が制限されているものがあります。正式公開前のソフトウェアについての記事を書くときは契約を確認しましょう。
利用規約を守る
その他にも、Qiitaの利用規約ではサービスを利用するうえで行ってはならないことを定めています。規約に目を通し、違反することのないようにしましょう。分からないことがあったら
このガイドラインの内容について不明な点があれば support@qiita.com までお気軽にお問い合わせください。
- 投稿日:2021-02-25T19:39:06+09:00
【 Ruby on Rails 6.0 】AWS + Nginx + Unicornでデプロイ⑥
始めに
前回の内容でブラウザ上でRailsアプリを起動するところまで実装出来ました。
今回はNginxというWebサーバーをインストールしてリクエストとレスポンスをインタラクティブに可能にしていきたいと思います。目次
目次 内容 セクション1 EC2インスタンス作成 セクション2 Linuxサーバー構築 セクション3 データベース設定 セクション4 EC2上でGemをインストールし環境変数を設定 セクション5 Railsアプリを起動 セクション6 Nginxの導入(今回の内容) Nginxの導入
Nginx(エンジン・エックス)とは、Webサーバの一種です。
ユーザーのリクエストに対して静的コンテンツの取り出し処理を行い、そして動的コ ンテンツの生成をアプリケーションサーバに依頼するためのものになります。 早速インストールしていきましょう。Nginxをインストール
ターミナル(EC2)# Nginxをインストール [ec2-user@ip-172-31-25-189 ~]$ sudo amazon-linux-extras install nginx1Nginxの設定ファイルを編集
次に、Nginxが正しく動くように設定しましょう。
Nginxの設定は設定項目X 設定値x;
という形式で入力していきます。
これも先ほどと同様に、vimコマンドを使ってターミナル上で編集していきます。 /etc以下のファイルなので、強い権限でないと書き込み、保存ができません。そのため、コマンドの頭にsudo
をつけています。ターミナル(EC2)[ec2-user@ip-172-31-25-189 ~]$ sudo vim /etc/nginx/conf.d/rails.conf開けたら次のように編集します。
rails.confupstream app_server { # Unicornと連携させるための設定。 # アプリケーション名を自身のアプリ名に書き換えることに注意。今回であればおそらく server unix:/var/www/〇〇〇〇〇〇<アプリケーション名>/tmp/sockets/unicorn.sock; } # {}で囲った部分をブロックと呼ぶ。サーバの設定ができる server { # このプログラムが接続を受け付けるポート番号 listen 80; # 接続を受け付けるリクエストURL ここに書いていないURLではアクセスできない server_name XX.XXX.XXX.XX(Elastic IP); # クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく client_max_body_size 2g; # 接続が来た際のrootディレクトリ root /var/www/〇〇〇〇〇<アプリケーション名>/public; # assetsファイル(CSSやJavaScriptのファイルなど)にアクセスが来た際に適用される設定 location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; } try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://app_server; } error_page 500 502 503 504 /500.html; }
- 3行目の<アプリケーション名> となっている箇所は、ご自身のものに変更してください。
- 11行目のとなっている箇所も同様に、ご自身のものに変更してください。
- 14行目の<アプリケーション名>となっている箇所は、ご自身のものに変更してくだ さい。
nginxの権限を変更
POSTメソッドでもエラーが出ないようにするために、下記のコマンドも実行
ターミナル(EC2)[ec2-user@ip-172-31-25-189 ~]$ cd /var/lib [ec2-user@ip-172-31-25-189 lib]$ sudo chmod -R 775 nginxこれで、Nginxの設定が完了しました。
Nginxを起動して、設定ファイルを再読み込み
[ec2-user@ip-172-31-25-189 lib]$ cd ~ [ec2-user@ip-172-31-25-189 ~]$ sudo systemctl start nginx [ec2-user@ip-172-31-25-189 ~]$ sudo systemctl reload nginx # ステータス確認 [ec2-user@ip-172-31-25-189 ~]$ sudo systemctl status nginx ● nginx.service - The nginx HTTP and reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled) Active: active (running) since 日 2021-02-21 04:20:53 UTC; 32s agounicorn.rbを修正
次にNginxを介した処理を行うためにunicornの設定を修正します。
unicorn.rb(ローカル)listen 3000 ↓以下のように修正 listen "#{app_path}/tmp/sockets/unicorn.sock"修正をしたら忘れずに、commitとpushをし、サーバ側で以下のコマンドを実行して修正点を反映させておきます。
ローカルの変更点を本番環境へ反映
ターミナル(EC2)[ec2-user@ip-172-31-25-189 ~]$ cd /var/www/アプリ名 # 本番環境に反映 [ec2-user@ip-172-31-23-189 <アプリ名>]$ git pull origin masterUnicornを再起動
Unicornのプロセスをkillして、再起動する作業を行います。
ターミナル(EC2)# プロセスを確認 [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn ec2-user 17877 0.4 18.1 588472 182840 ? Sl 01:55 0:02 unicorn_rails master -c config/unicorn.rb -E production -D ec2-user 17881 0.0 17.3 589088 175164 ? Sl 01:55 0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn # 続いて、unicorn_rails master(一番上)のプロセスをkillします。 [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID(上のコードでは17877)> # unicornを起動します [ec2-user@ip-172-31-23-189 <アプリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -Dブラウザで確認してみましょう
ブラウザからElastic IPでアクセスすると、アプリケーションにアクセスできます(:3000 をつける必要はありません)。なお、この時もunicornが起動している必要があります。
ローカルの変更を本番環境に反映させる手順まとめ
- 開発環境でgit push
- EC2インスタンスにSSHログイン
ターミナル(ローカル)# ssh.ディレクトリで行う ssh. $ ssh -i ダウンロードしたキーペア名.pem ec2-user@Elastic IPアドレス
- ローカルの変更を本番環境に反映
ターミナル(EC2)# アプリのディレクトリでに移動 [ec2-user@ip-172-31-23-189 <アプリ名>]$ cd /var/www/リポジトリ名 # 本番環境に反映 [ec2-user@ip-172-31-23-189 <アプリ名>]$ git pull origin master
- アセットのコンパイル
ターミナル(EC2)[ec2-user@ip-172-31-23-189 <アプリ名>]$ rails assets:precompile RAILS_ENV=production
- Unicorn起動
ターミナル(EC2)# プロセスを確認 [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn ec2-user 17877 0.4 18.1 588472 182840 ? Sl 01:55 0:02 unicorn_rails master -c config/unicorn.rb -E production -D ec2-user 17881 0.0 17.3 589088 175164 ? Sl 01:55 0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn # 続いて、unicorn_rails master(一番上)のプロセスをkillします。 [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID(上のコードでは17877)> # unicornを起動します [ec2-user@ip-172-31-23-189 <アプリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -DIPアドレスをうってブラウザで確認
IPアドレスにアクセスしてもエラーが出る時
エラーが出る時は以下の項目確認してみてください。
- 502 but gateway とエラーが出る場合は、nginxのlogの確認が必要になります。
- /var/log/nginx/error.logをlessまたはcatコマンドで確認する。
- サーバ側で、 /var/www/<レポジトリ名>/log/unicorn.stderr.log を less またはcatコマンドで確認し、エラーが出ていないか確認する(下に行くほど最新のログです。時刻表記がUTCであることに注意してください)
- Railsを起動しているか
- EC2インスタンスの再起動を行ってみる(※本番環境にてmysqlとnginxの起動が必要です。)
終わりに
以上で本番環境でRailsアプリを起動・操作できると思います!
しかし、現状だとローカル環境で変更点が発生した時に本番環境に反映させるのが工数が多く大変です。
なので次回の記事でこのデプロイ作業を自動化するためCapistranoというツールを導入したいと思います。
お疲れさまでした。。。
- 投稿日:2021-02-25T19:00:08+09:00
【Railsチュートリアル】第10章 ユーザーの更新・表示・削除 10.2まで
はじめに
これまで未実装だったedit、update、index、destroyアクションを加え、RESTアクションを完成させる。
10.1 ユーザーを更新する
PATCHリクエストに応答するupdateアクションを作成する。
10.1.1 編集フォーム
まず、Usersコントローラにeditアクションを追加して、それに対応するeditビューを実装する。
ユーザー編集ページのURLは/users/1/edit。ユーザーidはparams[:id]
変数で取り出すことができる。app/controllers/users_controller.rbdef edit @user = User.find(params[:id]) endアクションを作成したら、ビューを作成する。
editビューは見た目はapp/views/users/new.html.erb
と似ているが、HTMLソースに少し違いがある。<form accept-charset="UTF-8" action="/users/1" class="edit_user" id="edit_user_1" method="post"> <input name="_method" type="hidden" value="patch" /> # 注目すべきは1つ上のコード↑ . . . </form><input name="_method" type="hidden" value="patch" />URL
/users/1
にPATCH
でリクエストをするとユーザーを、Railsが既存のユーザーである(すでにDBに存在する)ということを区別し、更新をしてくれる。演習 1
先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング(Phising)サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel(relationship)属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。
app/views/users/edit.html.erb. . . <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a> </div> </div> </div>演習 2
リスト 10.5のパーシャルを使って、new.html.erbビュー(リスト 10.6)とedit.html.erbビュー(リスト 10.7)をリファクタリングしてみましょう(コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3 。
リストに沿ってリファクタリングする。
10.1.2 編集の失敗
update
アクションを作成する。def update @user = User.find(params[:id]) # DBからparams[:id]でuserを検索し、@userに代入 if @user.update(user_params) # 更新に成功した場合を扱う。 else render 'edit' # falseの場合はeditビューに再レンダリング end end演習 1
編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
確認のみなので省略
10.1.3 編集失敗時のテスト
エラーを検知するための統合テストを実装する。
test/integration/users_edit_test.rbrequire 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do # 編集が失敗するときのテスト get edit_user_path(@user) # 編集ページにアクセス assert_template 'users/edit' # editビューがレンダリングされるかどうか検証 patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } # 無効な情報を送信 assert_template 'users/edit' # editビューが再レンダリングされるか検証 # updateアクションがfalseのときは「render 'edit'」が実行されるため end end演習 1
リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。
test/integration/users_edit_test.rb. . . test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' assert_select "div.alert", "The form contains 4 errors." end10.1.4 TDDで編集を成功させる
編集の成功に対するテストを実装する。
test/integration/users_edit_test.rbclass UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "successful edit" do # 編集が成功するときのテスト get edit_user_path(@user) # 編集ページにアクセス assert_template 'users/edit' # editビューがレンダリングされるか検証 name = "Foo Bar" # nameを更新 email = "foo@bar.com" # emailを更新 patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } # 有効な情報を送信 assert_not flash.empty? # flashメッセージが空かどうか assert_redirected_to @user # プロフィールページにリダイレクト @user.reload # リロードする assert_equal name, @user.name # nameと@user.nameが同じかどうか assert_equal email, @user.email #emailと@user.emailが同じかどうか end endパスワードが空でも更新できるようにしているが、バリデーションがかかっているため、まだエラーになる。
app/models/user.rbclass User < ApplicationRecord . . . has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: true **. . .** end
has_secure_password
がオブジェクト生成時に存在性を検証するため、新規ユーザー登録時に空のパスワードが有効になることは無い。演習 1
実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。
確認のみなので省略。
演習 2
もしGravatarと紐付いていない適当なメールアドレス(foobar@example.comなど)に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。
初期設定のアイコン?が表示される。
10.2 認可
ユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する。
10.2.1 ユーザーにログインを要求する
Usersコントローラの中でbeforeフィルターを使う。beforeフィルターは、before_actionメソッドを使って何らかの処理が実行される直前に特定のメソッドを実行する仕組みのこと。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] # editアクション、updateアクションが呼び出されたら、logged_in_userアクションを実行する。 . . . private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? # ログインしているか? flash[:danger] = "Please log in." # falseのときはflashを表示 redirect_to login_url # ログインページへリダイレクト end end endunless 条件式 条件式が偽の時に実行する処理 endunless文は条件式が偽の場合の処理を記述するのに使われる。
test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "should redirect edit when not logged in" do get edit_user_path(@user) # editページにアクセス assert_not flash.empty? # flashが表示されていないか? assert_redirected_to login_url # ログインページにリダイレクトされたかどうか? end test "should redirect update when not logged in" do patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } # @user情報を送信 assert_not flash.empty? # flashが表示されていないか? assert_redirected_to login_url # ログインページにリダイレクトされたかどうか? end end演習 1
デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです(結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか(テストが失敗するかどうか)確かめてみましょう。
確認のみなので省略。
10.2.2 正しいユーザーを要求する
ユーザーが自分の情報だけを編集できるようにする。
test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect edit when logged in as wrong user" do log_in_as(@other_user) # テストユーザー(:archer)としてログイン get edit_user_path(@user) # michaelのeditビューにアクセス assert flash.empty? # flashが表示されて assert_redirected_to root_url # root_urlにリダイレクトされる end test "should redirect update when logged in as wrong user" do log_in_as(@other_user) # テストユーザー(:archer)としてログイン patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } # michaelの情報を更新しようとする assert flash.empty? # flashが表示されて assert_redirected_to root_url # root_urlにリダイレクトされる end end別のユーザーのプロフィールを編集しようとしたらリダイレクトさせたいので、correct_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出すようにする。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] before_action :correct_user, only: [:edit, :update] # editアクション、updateアクションが呼び出されたらcorrect_userアクションを実行する . . . private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end # 正しいユーザーかどうか確認 def correct_user @user = User.find(params[:id]) # 受け取ったログイン情報を@userに代入 redirect_to(root_url) unless current_user?(@user) # @userと現在ログインしているユーザーが違う場合はroot_urlにリダイレクトさせる end end演習 1
何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。
他のユーザーの個人情報の表示、更新ができてしまうから。
演習 2
上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?
editアクション。viewが定義されているから。
10.2.3 フレンドリーフォワーディング
リダイレクト先をユーザーがアクセスしたかったページにする。
編集ページにアクセスし、ログインした後に、(デフォルトのプロフィールページではなく)編集ページにリダイレクトされているかどうかをチェックするテスト。
test/integration/users_edit_test.rbrequire 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "successful edit with friendly forwarding" do # フレンドリーフォワーディングのテスト get edit_user_path(@user) # ログインがまだの状態で、editビューにアクセス log_in_as(@user) # テストユーザー(michael)でログイン assert_redirected_to edit_user_url(@user) # editビューにリダイレクト name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end endユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要がある。
app/helpers/sessions_helper.rbmodule SessionsHelper . . . # 記憶したURL(もしくはデフォルト値)にリダイレクト def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) # 送られてきたURLがnilでなければ左側を評価する。 session.delete(:forwarding_url) # 転送用のURLを削除 end # アクセスしようとしたURLを覚えておく def store_location session[:forwarding_url] = request.original_url if request.get? # もしGETリクエストが送られてきたらoriginal_urlをsession[:forwarding_url]に代入する end end演習 1
フレンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを、テストを書いて確認してみましょう。次回以降のログインのときには、転送先のURLはデフォルト(プロフィール画面)に戻っている必要があります。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。
test/integration/users_edit_test.rbrequire 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest . . . test "successful edit with friendly forwarding" do get edit_user_path(@user) assert_equal session[:forwarding_url], edit_user_url(@user) log_in_as(@user) assert_nil session[:forwarding_url] name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end end演習 2
7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください(デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう(デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって(コラム 1.2)、落ち着いて対処してみましょう)。
[1, 10] in /home/vagrant/work/sample_app2/app/controllers/sessions_controller.rb 1: class SessionsController < ApplicationController 2: 3: # GET /login 4: def new 5: debugger => 6: end 7: 8: # POST /login 9: def create 10: @user = User.find_by(email: params[:session][:email].downcase) (byebug) session[:forwarding_url] "http://localhost:3000/users/1/edit" (byebug) request.get? true
- 投稿日:2021-02-25T18:55:25+09:00
第9章 発展的なログイン機構
はじめに
永続cookie(permanent cookies)を使って[remember me]を実装する。
9.1 Remember me 機能
ユーザーのログイン状態をブラウザを閉じた後でも有効にする[remember me]機能を実装していく。
9.1.1 記憶トークンと暗号化
Cookiesの場合
ブラウザ(cookie)に暗号化したパスワードとDBに入っているハッシュ化しているパスワードが一致するかRailsで認証する。
下記の方針で永続的セッションを作成する。
1. 記憶トークンを保存する場所を用意する。
2. 記憶トークンにはランダムな文字列を生成して用いる。
3. ブラウザのcookiesにトークンを保存するときには、有効期限を設定する。
4. トークンはハッシュ値に変換してからデータベースに保存する。
5. ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
6. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する。db/migrate/[timestamp]_add_remember_digest_to_users.rbclass AddRememberDigestToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :remember_digest, :string end end記憶トークン用のカラムを用意。string(文字列)のremember_digest属性を追加する。
app/models/user.rb# ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 endA–Z、a–z、0–9、"-"、""のいずれかの文字(64種類)からなる長さ22のランダムな文字列を返すクラスメソッド`User.newtoken`を作成する。
マイグレーションは実行済みなので、Userモデルには既に
remember_digest
属性が追加されているが、remember_token
属性はまだ追加されていない。attr_accessor
を使って「仮想の」属性を作成する。app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token . . . # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token # 自分自身のremember_tokenに新しいtokenを代入する。保存されない update_attribute(:remember_digest, User.digest(remember_token)) # :remember_digestにremember_tokenをハッシュ化したものを保存する。 # 頭のselfが省略されている end end
user.remember_token
メソッドを使ってトークンにアクセスできるようにし、かつ、トークンをDBに保存せずに 実装する。演習 1
コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。
>> user = User.first (1.9ms) SELECT sqlite_version(*) User Load (1.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2021-02-13 18:10:29", updated_at: "2021-02-13 18:10:29", password_digest: [FILTERED], remember_digest: nil> >> user.remember (0.1ms) begin transaction User Update (8.2ms) UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ? [["updated_at", "2021-02-23 08:22:41.315576"], ["remember_digest", "$2a$12$y6wygDQ4HwYF6smxv8E1Y.K6Nz.3tNhCHpJVEDKHOBFs9MBu/NuLe"], ["id", 1]] (14.1ms) commit transaction => true >> user.remember_token => "JTiObGhfDhCqI-mYx7jGrw" >> user.remember_digest => "$2a$12$O6z7y1SMzDbXqIBa5OoYrOXv4nKB132fjA4WQ7MQxubweRSg/5nYa"演習 2
リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、ややわかりにくいリスト 9.4の実装でも、非常に混乱しやすいリスト 9.5の実装でも、いずれも正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。
確認だけなので省略
9.1.2 ログイン状態の保持
cookies
メソッドを使い、永続セッションを作成する。
value
(値)とオプションのexpires
(有効期限)が必要。有効期限は省略できる。cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }cookies.permanent[:remember_token] = remember_token # Railsの20年で期限切れになるcookies設定app/helpers/sessions_helper.rb# ユーザーのセッションを永続的にする def remember(user) # 必ず引数を設定する user.remember # DBに書き込む cookies.permanent.signed[:user_id] = user.id # cookieに暗号化したuser.idを代入する(20年で期限切れになる) cookies.permanent[:remember_token] = user.remember_token # cookieにuser.remember_tokenを代入する(20年で期限切れになる) endsigned: 暗号化、復号化するときのメソッド
app/models/user.rbdef authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) # ハッシュ値remember_digestと平文remember_tokenがあっているかBCryptがチェック endapp/helpers/sessions_helper.rbdef current_user if (user_id = session[:user_id]) # session[:user_id]をuser_idに代入してnilかどうか確認 @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) # cookies.signed[:user_id]をuser_idに代入してnilかどうか確認 user = User.find_by(id: user_id) # if user && user.authenticated?(cookies[:remember_token]) # nilかどうか確認(左側)して、引数にcookies内の:remember_tokenを引数にuser.authenticatedする log_in user @current_user = user end end end
session[:user_id]
もcookies.signed[:user_id]
もnilの場合はnilを返す。演習 1
ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。
確認のみなので省略
演習 2
コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。
確認のみなので省略
9.1.3 ユーザーを忘れる
ユーザーがログアウトできるようにする。
user.forget
メソッドでuser.remember
が取り消される(nil
で更新する)app/models/user.rb# ユーザーのログイン情報を破棄する def forget self.update_attribute(:remember_digest, nil) # nilで更新する。削除する。 endapp/helpers/sessions_helper.rb# 永続的セッションを破棄する def forget(user) user.forget # remember_digestを削除 cookies.delete(:user_id) # user_idを削除 cookies.delete(:remember_token) # remember_tokenを削除 end # 現在のユーザーをログアウトする def log_out forget(current_user) # cookie情報を削除 session.delete(:user_id) @current_user = nil end演習 1
ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。
確認のみなので省略
9.1.4 2つの目立たないバグ
ユーザーがタブを複数開いているとき、複数のブラウザでログインしているときにそれぞれバグが発生する。
前者はユーザーがログイン中の場合にのみログアウトさせる必要があり、後者は
remember_digest
が存在しないときはfalseを返す処理をauthenticated?
に追加する必要がある。# 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) # log_outメソッドによってfalseになる @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) # log_outメソッドによってfalseになる user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) # 左側の条件式でエラーが出る log_in user @current_user = user end end end #current_userメソッドの評価はniltest/models/user_test.rbrequire 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') end end演習 1
リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。
確認のみなので省略
演習 2
リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。
確認のみなので省略
演習 3
上のコードでコメントアウトした部分を元に戻し、テストスイートが red から green になることを確認しましょう。
確認のみなので省略
9.2 [Remember me]チェックボック
params[:session][:remember_me] == '1' ? remember(user) : forget(user)もし、params[:session][:remember_me]が「1」だったら(チェックボックスにチェックが入っていたら)、ログイン情報を記憶するためにrememberメソッドを呼び出す。「1」でなかったら記憶しないのでforgetメソッドを呼び出す。
三項演算子
if boolean? (true)var = foo else (false)var = bar end三項演算子を使うと下記のようになる。
var = boolean? ? (true) : (false)演習 1
ブラウザでcookies情報を調べ、[remember me]をチェックしたときに意図した結果になっているかどうかを確認してみましょう。
確認のみなので省略。
演習 2
コンソールを開き、三項演算子を使った実例を考えてみてください(コラム 9.2)。
> def first(type) > type = "fire" ? "ヒトカゲ" : "フシギダネ" > end => :type > first("fire") => "ヒトカゲ" # ゼニガメごめんね9.3 [Remember me]のテスト
test "login with remembering" do log_in_as(@user, remember_me: '1') assert_not_empty cookies[:remember_token] end test "login without remembering" do log_in_as(@user, remember_me: '1') # cookieを保存してログイン # remember me チェックする delete logout_path # ログアウトする log_in_as(@user, remember_me: '0') # cookieを削除してログイン # remember me チェックしない assert_empty cookies[:remember_token] # cookiesには情報が入ってないはず end演習 1
リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを(インスタンス変数ではない)通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め(ヒントとして?やFILL_INを目印に置いてあります)、[remember me]チェックボックスのテストを改良してみてください。17
app/controllers/sessions_controller.rbdef create @user = User.find_by(email: params[:session][:email].downcase) # if user && user.authenticate(params[:session][:password]) if @user && @user.authenticate(params[:session][:password]) log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_to @user else # alert-danger => 赤色のフラッシュ flash.now[:danger] = 'Invalid email/password combination' render 'new' # GET /users/1 => show template # render 'new'(0回目) end endtest/integration/users_login_test.rbtest "login with remembering" do log_in_as(@user, remember_me: '1') assert_equal cookies[:remember_token], assigns(:user).remember_token end9.3.2 [Remember me]をテストする
raiseメソッド: 例外(わざとバグ)を発生させるメソッド。きちんとテストがされているか確認する。
require 'test_helper' class SessionsHelperTest < ActionView::TestCase def setup @user = users(:michael) # michaelを@userに代入 remember(@user) # @userの情報をrememberに入れる end test "current_user returns right user when session is nil" do assert_equal @user, current_user # @userとcurrent_user(ログインしているuser)が一致しているかどうか assert is_logged_in? # ログインしているかどうか end test "current_user returns nil when remember digest is wrong" do @user.update_attribute(:remember_digest, User.digest(User.new_token)) # @userのremember_digestを新しいものに書き換える assert_nil current_user # 新しいものに書き換えたのでcurrent_userはnilを返しているはず end end演習 3
リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう(このテストが正しい対象をテストしていることを確認してみましょう)。
確認のみなので省略。
さいごに
第9章からほぼ理解できないまま飛ばす箇所が出てきました。
Railsチュートリアルを最後まで進めたら戻ってこようと思います。
- 投稿日:2021-02-25T17:46:39+09:00
Rubyについて
本日はRubyについて。。。
志望している会社でRubyに力を入れていらっしゃったので、復習というより知識をつけようと。Rubyって何?
・プログラミング言語
・Webアプリ開発に特化
・DBと繋がりやすい
・幅広く使われている
・文法が覚えやすいってな感じ。
そもそもプログラムって実行しなきゃメモ用紙なのよねえ。=Excel的な。
Ruby on Railsって何?
最初、「どっちもRubyやん。違いが分からん」という感じでした。
結論は、「Rubyのフレームワークの一つ」ということ。。
フレームワークとは、要は「頻度の高いツールのハッピーセット」的なものだと考えてる。
それぞれを単品で細かく頼むより楽だし、早いし。
つまり、Ruby(?)のハッピーセットがRuby on Railsって名前で売ってある的な。わかりずらいか。もちろんRubyのフレームワークは他にも「Sinatra」や「HANAMI」なんて可愛い名前のものもある。
それぞれ特徴があるが、一番使われているのが「Ruby on Rails」とされているんだと。参考はこちら
https://eng-entrance.com/ruby-frameworkまとめ
Railsっていう、Webアプリが作りやすくてみんな使ってる言語の一つで、ツールがあらかじめセットになってるものがRuby on Rails。
つまり、Railsハンバーガーのハッピーセット(おもちゃがRails)を頼んでるってことね。
- 投稿日:2021-02-25T17:38:24+09:00
[Rails6] Sprockets::Rails::Helper::AssetNotPrecompiled in エラーが出た
結論
app/assets/config/manifest.js
ファイルにコードを一行追加したら解決しました。app/assets/config/manifest.js//= link_tree ../images //= link_directory ../stylesheets .css #以下を追記 //= link application.css環境
- macOS Catalina
- Ruby 2.7.2
- Rails 6.0.3
実際には下記の記事通りに環境構築をしています
参考
このサイトで「rails 6」でページ検索して出てきた解決案のひとつがうまくいきました!
【Stack Overflow】Rails: Sprockets::Rails::Helper::AssetNotPrecompiled in development
他に試したこと
config/initializers/assets.rb
ファイルに追記
Rails.application.config.assets.precompile += %w( application.css )
この一行を追記rails assets:precompile
コマンド実行後サーバー再起動画像を再取り込み?(SVGリンク切れ?)
いくつか試しましたが、Railsのバージョン違いなのか、これではエラー解消できませんでした。
(前提としては、タイトルのエラー文が出て、画像表示がうまく動作していなかったようです)さいごに
Railsガイド - アセットパイプライン
原因はこれかな?
このあたりを読めば、仮説と検証を考えながら解決できそう。
- 投稿日:2021-02-25T17:38:24+09:00
[Rails6]Sprockets::Rails::Helper::AssetNotPrecompiled in エラーが出た
結論
app/assets/config/manifest.js
ファイルにコードを一行追加したら解決しました。app/assets/config/manifest.js//= link_tree ../images //= link_directory ../stylesheets .css #以下を追記 //= link application.css環境
- macOS Catalina
- Ruby 2.7.2
- Rails 6.0.3
実際には下記の記事通りに環境構築をしています
参考
このサイトで「rails 6」でページ検索して出てきた解決案のひとつがうまくいきました!
【Stack Overflow】Rails: Sprockets::Rails::Helper::AssetNotPrecompiled in development
他に試したこと
config/initializers/assets.rb
ファイルに追記
Rails.application.config.assets.precompile += %w( application.css )
この一行を追記rails assets:precompile
コマンド実行後サーバー再起動画像を再取り込み?(SVGリンク切れ?)
いくつか試しましたが、Railsのバージョン違いなのか、これではエラー解消できませんでした。
(前提としては、タイトルのエラー文が出て、画像表示がうまく動作していなかったようです)さいごに
Railsガイド - アセットパイプライン
原因はこれかな?
このあたりを読めば、仮説と検証を考えながら解決できそう。
- 投稿日:2021-02-25T16:49:23+09:00
RubyでシンプルにAPIリクエストを行うための備忘録
Ruby の net/https にて、簡単に API のリクエストをしたいと思ったとき、
書き方がいくつもあるようで... 私が一番気に入っているコードを以下にメモしておきます。GETリクエストの例
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.get(uri.request_uri) arr = JSON.parse(res.body)リクエストヘッダーを追加する
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.get(uri.request_uri, { "Accept-Language" => "ja", "Content-Type" => "application/json" }) arr = JSON.parse(res.body)クエリ文字列を追加する
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." uri.query = URI.encode_www_form({ "id" => "c686397e4a0f4f11683d" }) req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.get(uri.request_uri) arr = JSON.parse(res.body)POSTリクエストの例
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.post(uri.request_uri, { "rendered_body" => "<h1>Example</h1>", "body" => "# Example" }.to_json) arr = JSON.parse(res.body)リクエストヘッダーを追加する
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.post(uri.request_uri, { "rendered_body" => "<h1>Example</h1>", "body" => "# Example" }.to_json, { "Accept-Language" => "ja", "Content-Type" => "application/json" }) arr = JSON.parse(res.body)クエリ文字列を追加する
#! /usr/bin/env ruby #-*- coding: utf-8 -*- require 'net/https' require 'json' uri = URI.parse "https://qiita.com/api/v2/..." uri.query = URI.encode_www_form({ "id" => "c686397e4a0f4f11683d" }) req = Net::HTTP.new(uri.host, uri.port) req.use_ssl = true res = req.post(uri.request_uri, { "rendered_body" => "<h1>Example</h1>", "body" => "# Example" }.to_json) arr = JSON.parse(res.body)
- 投稿日:2021-02-25T15:38:04+09:00
form_withのlocal: trueって必要なん?これ何なん?(Ruby on Rails)
はじめに:私が犯した罪と償い
社内研修の一環で、プチIoTシステムを開発しています。
そんな中、自分のエラーの解消に大先輩3人の60分を溶かしてしまったので、せめてもの償いのために、学んだことをメモしておきます。おかげで、
- Railsのメソッド
form_with
の引数local: true
が何の役に立っているのか- アプリ作成時のネットワーク通信のこと
が痛いほどわかりました。
環境
Rails6.0.3, macOS Catalina 10.15.7何のエラー出してたん?
フォームでバリデーションが通らなかった時にエラーメッセージをビューで表示したい。
ただそれだけのことでした。しかし仕込んだそのエラーメッセージが表示されません!うまくいっていなかったビューはこちら。1行目にご注目。
new.html.erb<%= form_with model: @supplier do |form| %> <% if form.object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> エラーが <%= form.object.errors.count %> 個あります。 </div> <ul> <% form.object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="mb-3"> <%= form.label :会社名, for: 'company', class: 'col-sm-2 col-form-label' %> <%= form.text_field :name, class: 'form-control', id: 'company', placeholder: '株式会社 喧嘩上等' %> </div> <div class="mb-3"> <%= form.label :メールアドレス, for: 'email', class: 'col-sm-2 col-form-label' %> <%= form.text_field :email, class: 'form-control', id: 'email', placeholder: 'order@example.com' %> </div> <%= form.submit '送信', class: 'btn' %> <% end %>私は、Railsチュートリアル出身なのですが、そこでは
form_with
にlocal: true
という引数を渡していました。しかし、ネットでform_withの使い方を他で改めて調べてみたところ、この引数がなくても新規レコードの登録処理ができそうだったので、省いてみちゃえ!と出来心が全ての事の始まりでした。実際、これでデータベースまでの登録はきちんとできていて、なんだlocalパラメータいらないんじゃん。となっていました。一方.....
if form.object.errors.any?
を入れても、エラー表示は一切できませんでした。ちなみにコントローラはこんな感じ。問題なさそう。
supplier.controller.rbclass SuppliersController < ApplicationController def new @supplier = Supplier.new end def create @supplier = Supplier.new(supplier_params) if @supplier.save redirect_to suppliers_path else render 'new' end end endbinding.irbをビューやコントローラなど至る箇所に埋め込んでは、各変数に値が入っているかどうかをチェック。
form.object
には値もある。@supplier
にしても同じ。errors.any?
もtrueを返している。何故なんだ。タイポもなさそう。先輩たちの時間をどんどん溶かしてしまっています...。解決:「クルクルしてない!」
「クルクルしてない!」
問題の原因が全くわからず途方にくれていた末に、ある先輩が。
フォームの送信ボタンを教えても画面がリロードしないことを変に思ったようで、そこからフォームの動きが怪しいとなりました。そこでlocal: true
を追記したのです。new.html.erb<%= form_with(model: @supplier, local: true) do |form| %> 以下略学び1:
local: true
とは?form_withはRailsの比較的新しいメソッド。このメソッドではデフォルトがAjax通信で、非同期通信になってしまうのです。(これはRails 5.1〜6.0の仕様でRails 6.1からは同期通信がデフォルトに戻っているそうです。当記事はRails6.0.3です。→参考。@jnchito さんありがとうございます!)それはつまり、必要な箇所だけページが更新され、その他の箇所はそのままになるという事。だからエラーメッセージのHTML部分が加えられなかったのです。
ここにlocal: true
と引数を渡す事で、これが通常のHTTPリクエストになり、ページ全体が返ってきてページがリロードされ、エラーメッセージも表示されるようになります。学び2:アプリの開発でネットワークをチェックする
その後、ブラウザの開発者ツールでネットワークを確認しました。
↑Typeがxhrになっとる。xhr=XMLHttpRequest。Ajaxのxの部分。つまりAjax通信になっているということ。
↑suppliersが下の方に。他の要素がリロードされずにそのまま残っているということ。↓
local: true
にしておく(=通常のHTTP通信にする)とこのようなネットワーク動作になります。
suppliersがリストの一番先頭にいます。
ページ内の全ての要素が更新され、suppliersが一番最初に処理されたという事です。
「エンジニア中級者になるには、ネットワークのことを理解すること」と先輩。
最後に
エラーの解消の過程で、他にもbinding.irbの使い方、errorsやfull_messagesにどんなデータが格納されているのかなどいろんなことを勉強できました。
先輩方ありがとうございました!
宣伝ではないですが、周りがこんなエラーに親身に向き合ってくれる先輩ばかりです。
本当にFusicという会社でエンジニアキャリアを始められて幸せすぎる...。
- 投稿日:2021-02-25T15:27:49+09:00
【Rails】1日に記録できる数値の合計を制限するバリデーション
はじめに
投稿3回目です。
文章力が赤ちゃんレベルかつ遅筆なのでまだ慣れないです...
間違い等あればご指摘よろしくお願いします。環境
- Ruby 2.6.3
- Rails 5.2.4
背景/目的
- ポートフォリオで学習内容を記録するサイトを開発中。
- 学習内容は1日に何回でも記録可能であり、項目には学習時間がある。
- 学習時間の合計値が24時間/1日を超えないようにバリデーションを設定する。
- 学習内容(learningsテーブル)の詳細は下記の通り。
カラム名 データ型 説明 date date 学習日 time float 学習時間 結論
app/models/learning.rbclass Learning < ApplicationRecord # 一つのユーザー(user)に対して、複数の学習記録(learning)が結びついている belongs_to :user # validateに定義したメソッドを設定 validate :total_time_cannot_exceed_limit_time, on: :create validate :total_time_cannot_exceed_limit_time_for_edit, on: :update # 1日に記録できる学習時間の合計 LIMIT_TIME_HOUR = 24 # 入力された学習日に既に記録されている学習時間の合計値を取得する def one_day_time_sum(date) user.learnings.where(date: date).sum(:time) end # 入力された学習日に既に記録されている「編集対象の投稿以外の」学習時間の合計値を取得する def one_day_time_sum_unless_target_date(date) user.learnings.where(date: date).where.not(id: id).sum(:time) end # create時のカスタムバリデーション用のメソッドを定義 def total_time_cannot_exceed_limit_time # [学習日が入力済]かつ[学習時間が入力済]かつ[学習日に既に記録された学習時間と入力した学習時間の合計が24時間を超える場合] if date.presence && time.presence && one_day_time_sum(date) + time > LIMIT_TIME_HOUR # エラーメッセージを表示する errors.add(:date, ":#{date.strftime("%Y年%m月%d日")}の学習時間の合計が#{LIMIT_TIME_HOUR}時間を超えています") end end # update時のカスタムバリデーション用のメソッドを定義 def total_time_cannot_exceed_limit_time_for_edit # [学習日が入力済]かつ[学習時間が入力済]かつ[学習日に既に記録された「編集対象の投稿以外の」学習時間と入力した学習時間の合計が24時間を超える場合] if date.presence && time.presence && one_day_time_sum_unless_target_date(date) + time > LIMIT_TIME_HOUR errors.add(:date, ":#{date.strftime("%Y年%m月%d日")}の学習時間の合計が#{LIMIT_TIME_HOUR}時間を超えています") end end endcreateとupdateで処理を分けています。
コメントで処理の内容を記載しましたが、自分で読んでても正直よく分からないです。
例えば、下記のデータが既に保存されており、学習日2021-01-01
・学習時間4
の投稿を新たにする場合、
id 学習日 学習時間 1 2021-01-01 10.6 2 2021-01-01 11.4 入力された学習日
2021-01-01
に既に記録されている学習時間の合計値22
時間を取得し、入力した学習時間4
時間を足して、24時間と比較します。=> この場合26時間なのでエラーメッセージが表示されます。これをupdateでも同じ処理にすると動作がおかしくなります。
例えば、id:1
の学習時間を5
時間に変更する場合、5 + 11.4 =16.4
時間になるのが理想です。
しかし、実際は10.6 + 11.4 + 5 =27
時間でNGになってしまいます。
user.learnings.where(date: date).sum(:time)
だと編集前の学習時間も取得してしまうので、update時はwhere.not(id: id)
を追記し、編集対象の投稿以外の学習時間の合計を取得するようにしています。参考にさせていただきました
ここまで見て頂きありがとうございました。
ネーミングセンスがないのは許していただけると助かります?
- 投稿日:2021-02-25T15:12:57+09:00
f.collection_selectの使い方(超ざっくり&初学者向け)
f.collection_selectで別モデルのカラムを使って選択肢を作りたい時に役立つ情報を提供します!
まずf.collection_selectについて
書き方としては<%= f.collection_select :保存先カラム, :選択肢に使いたいカラムを持った配列, :保存先カラムに保存したい値, :選択肢に表示したいカラム %>といった感じです。
例としては<%= f.collection_select :genre_id, @genres, :id, :name, :prompt => "選択してください" %>こんな感じです。
promptに関しては未選択の時に表示させておきたい文字を表示させることができます!!
ぜひ使ってみてください!!
- 投稿日:2021-02-25T14:59:51+09:00
Active Hashを使ってidを DBに保存する
ActiveHashを利用する
gem 'active_hash'ActiveHashを導入する。
modelsディレクトリに新しいファイル作成後に、クラスを定義してActiveHash::Baseクラスを継承する。ActiveHashを用いるのに必要なもの。
class Genre < ActiveHash::Base self.data = [ { id: 1, name: '----' }, { id: 2, name: '経済' }, { id: 3, name: '政治' }, { id: 4, name: '地域' }, { id: 5, name: '国際' }, { id: 6, name: 'IT' }, { id: 7, name: 'エンタメ' }, { id: 8, name: 'スポーツ' }, { id: 9, name: 'グルメ' }, { id: 10, name: 'その他' } ] include ActiveHash::Associations has_many :examples endActiveHashを用いてアソシエーションの記述をするので、モジュールをincludeする。
また紐付いているモデルとのアソシエーションを記述する。class Example < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to :genre validates :genre_id, numericality: { other_than: 1 } end同様に紐付いているモデル内に対応するアソシエーションを記述する。
こちらでもActiveHashを用いるのに使用するモジュールを取り込む。
また今回の場合はid1は {'---'}となったいるためDBに保存しないようにバリデーションをかけておく。<%= f.collection_select(:genre_id, Genre.all, :id, :name, {}, {class:"genre-select"}) %>プルダウンを生成するために上記の記述をする。
値 役割 :genre_id 保存先のカラム名 Genre.all 配列データ :id 表示する際に参照するDBのカラム名 :name 実際に表示されるカラム名 {} オプションの指定 {class:"genre-select"} htmlオプション 扱うデータを簡単に保存できるために役立ちそうです。
- 投稿日:2021-02-25T14:19:49+09:00
ダミーデータを条件付きで表示/非表示にする方法
はじめに
本記事は製品情報などのダミーデータを、DBにデータが保存されているかいないかで表示/非表示にする方法を解説するものです。
考えてみれば難しいことではないのですが、とても悩んで時間を使ってしまったので、今後のために残しておくことと、同じようなエラーに遭遇した方の助けになれば良いと思い、記事にしました。環境
- 言語:Ruby
- フレームワーク:Rails
- DB:MySQL
やりたかったこと
DBに保存済みのレコードをトップページに表示し、DBに何も保存されていなければダミーデータの製品を表示させる。
該当箇所のコード(コントローラー)
class ItemsController < ApplicationController before_action :authenticate_user!, only: [:new, :create] def index @items = Item.all.order('created_at DESC') end該当箇所のコード(ビューファイル)
<% if @items == nil %> #製品を表示させるための処理(今回は割愛) <% end %>結果・・・・・
エラーは発生しないものの、ダミーデータが表示されない。。。
製品情報をDBに保存してみたところ、保存した製品自体は表示されるので、DBのデータを取得してトップページに反映させる処理自体は問題ない様子。原因
if文の条件式に
nil
を指定しましたが、if文がDB内を「空」と判断してくれなかった様子解決策①
if文の条件式部分を下記のコードに変更 (配列の要素を指定する)
<% if @items[0] == nil %>nilに対し配列の1つ目、すなわち
[0]
を指定することにより、if文が「配列の1つ目が空」=「DBにデータは保存されていない」という判断をしてくれます。
これでDBにデータが保存されている時は保存されたデータを表示し、DBにデータが保存されていなければダミーデータを表示することができました。解決策②
if文の条件式部分を下記のコードに変更 (empty?メソッドを用いる)
<% if @items.empty %>emptyメソッドは要素が0の時にtrueを返すメソッドです。
これを条件式に指定することにより、DBに解決策①と同様の挙動をしてくれました。さいごに
nilは条件指定で頻繁に使いますが、今回はそこに頭が集中するあまり他の方法論を検討していなかったのが時間を使い過ぎた原因と思っています。
一つの書き方が完答、というわけではありませんし、柔軟に考えるためにも様々な書き方を試してみるのも大事だなと感じました。
- 投稿日:2021-02-25T12:52:17+09:00
RSpecの書き方で最近悩んだこと3つとその解決法について
RSpecの書き方について、最近躓いた3選です
検証環境
- ruby: 2.6.5
- rails: 6.0.3.2
- rspec-rails: 3.9.0
module単体に対してテストを書きたい
moduleをいろんなクラスで使用することを想定するとき、テストを特定のクラスに依存させて書くのはよくなさそうです。
そんなとき、どうすればよいか悩んだのですが、こんなふうに書くと、特定のクラスに依存せず、moduleをinclude/extendしたクラスのテストができそうでした。
Class.new { include Module }
して作成したダミーのクラスに対しテストする# app/models/concerns/good_module.rb module GoodModule def ok 'ok!' end end# spec/models/concerns/good_module_spec.rb describe GoodModule do context '特異メソッドとして使う場合' do let(:dummy_class) { Class.new { extend GoodModule } } subject { dummy_class.ok } it { expect(subject).to eq 'ok!' } end context 'インスタンスメソッドとして使う場合' do let(:dummy_class) { Class.new { include GoodModule } } let(:dummy_class_instance) { dummy_class.new } subject { dummy_class_instance.ok } it { expect(subject).to eq 'ok!' } end endこんな感じで書いてみます。
$ bundle exec rspec spec/models/concerns/good_module_spec.rb .. Finished in 0.0046 seconds (files took 1.79 seconds to load) 2 examples, 0 failures実行できました!
インスタンス変数の書き込みメソッドをモックしたい
モックを利用すると、テストしやすい場面があると思います。
# app/models/concerns/sendable.rb module Sendable def send_mail(message, email) message.to = email puts 'send!' end endこんな感じの処理を作りましたが、messageクラスをまだ作っていません。
モックにして先にテストだけしてみたいと思います。# spec/models/concerns/sendable_spec.rb describe Sendable do let(:dummy_class) { Class.new { extend Sendable } } let(:message) { double('message') } subject { dummy_class.send_mail(message, 'example@example.com') } before do allow(message).to receive(:to) end it { subject } endいざ実行すると…
$ bundle exec rspec spec/models/concerns/sendable_spec.rb F Failures: 1) Sendable Failure/Error: message.to = email #<Double "message"> received unexpected message :to= with ("example@example.com") # ./app/models/concerns/sendable.rb:6:in `send_mail' # ./spec/models/concerns/sendable_spec.rb:9:in `block (2 levels) in <top (required)>' # ./spec/models/concerns/sendable_spec.rb:15:in `block (2 levels) in <top (required)>' Finished in 0.01396 seconds (files took 1.74 seconds to load) 1 example, 1 failure
received unexpected message
となってしまいました。
これだと想定通りに動きません。attr_accessorの対象となるインスタンス変数に=をつける
ただ、よく考えてみると、attr_accessorの定義は
https://docs.ruby-lang.org/ja/latest/method/Module/i/attr_accessor.html
def name @name end def name=(val) @name = val endこうなので、モックも
# spec/models/concerns/sendable_spec.rb before do # =を追加する allow(message).to receive(:to=) endこれが正しかったのでした。
メソッドチェーンをモックする
例えば、
Time.zone.now.hour
のようなメソッドチェーンが返す値をモックしたいときがあると思います。# app/models/concerns/checkable.rb module Checkable def ok Time.zone.now.hour == 10 end end早速RSpecを書いてみます。
# spec/models/concerns/checkable_spec.rb describe Checkable do let(:dummy_class) { Class.new { extend Checkable } } subject { dummy_class.ok } before do allow(Time).to receive('zone.now.hour').and_return(10) end it { expect(subject).to be true } end$ bundle exec rspec spec/models/concerns/checkable_spec.rb F Failures: 1) Checkable Failure/Error: allow(Time).to receive('zone.now.hour').and_return(10) Time does not implement: zone.now.hour # ./spec/models/concerns/checkable_spec.rb:11:in `block (2 levels) in <top (required)>'勘で書いてみましたが、やっぱり無理でした。。
receive_message_chain
を使うそういう場合は、RSpec側にこれを想定したメソッドが用意されているのでそれを利用します。
# spec/models/concerns/checkable_spec.rb before do allow(Time).to receive_message_chain(:zone, :now, :hour).and_return(10) end$ bundle exec rspec spec/models/concerns/checkable_spec.rb . Finished in 0.00916 seconds (files took 1.65 seconds to load) 1 example, 0 failuresこれで、無事、メソッドチェーンでも、特定の値を返すことができました。
終わり
RSpecのドキュメントを読むと、こんな書き方できたんだという発見が多いです。
テストに助けられることが多いので、今後も転ばぬ先の杖として、需要にあったテストを書けるように、RSpec力を鍛えていきたいと思いました。
- 投稿日:2021-02-25T12:45:57+09:00
Railsのfind_eachやfind_in_batchesでorderにid以外を指定したい場合の解決方法
はじめに
大量のデータにアクセスして処理を行う場合、
- メモリ不足で処理が中断されないよう、少しずつメモリに展開したい
- 途中で処理が中断されても問題ないよう、一定件数ごとにコミットをしたい
と考えることがあると思います。
そんなときにRailsで役に立つのがfind_eachやfind_in_batchesですね。
ただしこの2つのメソッドには弱点があり、id(主キー/primary key)の昇順(ASC)でしかデータを扱うことができません。
※Rails v6.1.0時点での情報です。
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to # ascending on the primary key ("id ASC"). # This also means that this method only works when the primary key is # orderable (e.g. an integer or string).そこで、今回は自分が指定したorderで大量データを扱いたい場合の解決方法を紹介いたします。
解決方法
前提
Rankingモデルのrank(順位)順で処理をしたい、とします。
実装
@rank_offset = 0 @batch_size = 1000 def find_rankings_in_batches loop do rankings = Ranking.where('rank > ?', @rank_offset).order(rank: :asc).limit(@batch_size) break if rankings.blank? rankings.each do |ranking| yield(ranking) end @rank_offset = rankings.last.rank end end find_rankings_in_batches do |ranking| # ここに処理を書く end発行されたSQL
1ループ目
SELECT `rankings`.* FROM `rankings` WHERE (rank > 0) ORDER BY `rankings`.`rank` ASC LIMIT 10002ループ目
SELECT `rankings`.* FROM `rankings` WHERE (rank > 1000) ORDER BY `rankings`.`rank` ASC LIMIT 1000rankの昇順かつbatch_size単位で取得できていることがわかります。
注意点
基本的には、orderに指定するカラムはUNIQUE制約が設定されているものにしてください。
batchの切れ目で同じ値が続く場合、処理されないレコードが存在してしまうためです。
- 投稿日:2021-02-25T11:46:02+09:00
【Rails】seed_fuまとめ
何がうれしいか
- seedデータの一部を変更した時、変更したファイルだけを読み込み、データの更新や追加ができる
- デフォルトの
seeds.rb
の場合、既存のデータを削除してから再度読み込まなきゃいけない- 環境ごとにseedデータを分けやすくなる
インストール
Gemfilegem 'seed-fu'$ bundle install$ mkdir db/fixtures # 必須 # 以下、環境ごとにseedファイルを分けたい場合に作成 $ mkdir db/fixtures/development $ mkdir db/fixtures/productionデータを読み込む
$ touch db/fixtures/development/01_user.rb #ファイル名は自由。アルファベット順に読み込まれる。以下の2通りの書き方がある。データが多い時は2つ目の書き方の方が良さそう。
基本の書き方User.seed do |s| s.id = 1 s.name = '茂野吾郎' s.email = 'shigeno@example.com' s.password = 'password' end User.seed do |s| s.id = 2 s.name = '佐藤寿也' s.email = 'sato@example.com' s.password = 'password' endこの書き方でも良いUser.seed( { name: '茂野吾郎', email: 'shigeno@example.com', password: 'password' }, { name: '佐藤寿也', email: 'sato@example.com', password: 'password' }, )$ rails db:seed_fu
読み込むseedファイルを明示的に指定する
$ rails db:seed_fu
開発環境で上記のコマンドを叩くと、
db/fixtures/development
以下のファイルだけが読み込まれる。
他のディレクトリのファイルを読みたい場合などには以下のようにディレクトリを指定することができる。$ rails db:seed_fu FIXTURE_PATH=db/fixtures/hogehoge【注意】データの同一性はidで判断される
既存のデータが更新されたのか、新規データが追加されたのかは、idを元に判断されている。seedファイル内のデータにidを書かないと、既存のデータを更新したつもりでも新規データが追加されてしまう。
id以外の(ユニークな)データを基準にデータの同一性を判断させたい場合は、以下のように書く。
emailがユニークな場合User.seed(:email) do |s| s.name = 'foo' s.email = 'foo@example.com' s.password = 'password' end # or User.seed(:email, { name: 'foo', email: 'foo@example.com', password: 'password' } )既存のDBからseedファイルを作成するrakeタスク
SeedFu::Writer.write
を使ってseedファイルを作ることができる$ rails g task seed-fu-gen-user
lib.tasks/seed_fu_gen_user.rakenamespace :seed_fu_gen_user do desc 'usersテーブルのデータを元にseedファイルを生成' task create_seed_by_db: :environment do SeedFu::Writer.write('./db/fixtures/user_gen.rb', class_name: 'User') do |w| User.all.each do |x| w << x.as_json(except: %i[created_at updated_at]) end end end end$ ./bin/rake seed_fu_gen_user:create_seed_by_db
※公式のREADMEを読むと、
SeedFu::Writer
にはもろもろ変更が入ったと注意書きがありますが、少なくともver2.3.9では上記の方法で動作確認ができました。参考
- 投稿日:2021-02-25T11:04:26+09:00
【 Ruby on Rails 6.0 】AWS + Nginx + Unicornでデプロイ⑤
始めに
前回まででEC2上に必要なGemをインストールし本番環境用に環境変数を設定しました。今回はHTTP通信を設定しRailsアプリを起動する工程までをまとめたいと思います。
目次
目次 内容 セクション1 EC2インスタンス作成 セクション2 Linuxサーバー構築 セクション3 データベース設定 セクション4 EC2上でGemをインストールし環境変数を設定 セクション5 Railsアプリを起動(今回の内容) セクション6 Nginxの導入 ポート開放
立ち上げたばかりのEC2インスタンスはSSHでアクセスすることはできますが、HTTPなどの他の通信方法では一切つながらないようになっています。そのため、WEBサーバとして利用するEC2インスタンスは事前にHTTPがつながるように「ポート」を開放する必要があります。
セキュリティグループのポートを設定
AWSのEC2インスタンス一覧画面から、対象のインスタンスを選択し、「セキュリティグ
ループ」のリンクをクリックします。
すると、インスタンスの属するセキュリティグループの設定画面に移動するので、「イン バウンド」タブの中の「編集」をクリックします。
モーダルが開くので、「ルールの追加」をクリックします。
タイプ カスタムTCPルール」 プロトコル TCP ポート範囲 3000 送信元 カスタム、0.0.0.0/0 「0.0.0.0」は「全てのアクセスを許可する」という意味です。
以上で、ポートの開放が完了です。
database.ymlの本番環境の設定を編集
本番環境のmysqlの設定に合わせるため、ローカルのdatabase.ymlを以下のように編集して下さい。
config/database.ymlproduction: <<: *default database: (ここは編集しないこと) username: root password: <%= ENV['DATABASE_PASSWORD'] socket: /var/lib/mysql/mysql.sockローカルでの編集をコミットして、GitHubにプッシュ
リモートリポジトリが更新されたため、サーバ上のアプリケーションにも反映させましょう。今回はすでにEC2とGithubは接続できているため、
git pullコマンド
を利用します。
※別にブランチを切っている場合は、masterブランチにmergeしてから以下のコマンドを 実行しましょう。ターミナル(EC2)[ec2-user@ip-172-31-23-189 <app名>]$ git pull origin masterデータベースを作成しマイグレーションを実行
ターミナル(EC2)[ec2-user@ip-172-31-23-189 <app名>]$ rails db:create RAILS_ENV=production [ec2-user@ip-172-31-23-189 <app名>]$ rails db:migrate RAILS_ENV=productionもしここで
Mysql2::Error: Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'
というエラーが起こった場合、データベース が起動していない可能性があります。sudo systemctl start mariadb
というコマン ドをターミナルから打ち込み、mysqlの起動を試してみましょう。アセットファイルをコンパイル
続いて、アセットファイルをコンパイルします。
アセットファイルとは、画像・CSS・ JavaScript等を管理しているファイルです。
コンパイルとはアセットファイルを圧縮し、 そのデータを転送するということです。
この作業を行わないと、本番環境でCSSが反映されずにビューが崩れてしまったり、エラーでブラウザが表示されない、などの問題が生じてしまいます。ターミナル(EC2)[ec2-user@ip-172-31-23-189 <アプリ名>]$ rails assets:precompile RAILS_ENV=production # 成功すると Yarn executable was not detected in the system. Download Yarn at https://yarnpkg.com/en/docs/install I, [2020-01-18T12:51:01.4345644 #1265] INFO -- : Writing /var/app/web-share/public/assets/member_photo_noimage_thumb-224a733c50d48aba6d9fdaded809788bbeb5ea5f6d6b8368adaebb95e58bcf53.png I, [2020-01-18T12:51:02.2615123#1265] INFO -- : Writing /var/app/appname/public/assets/application-bc071e28a78e2b63c9313afed5ad3476e00e3f0e5b12445c37214d1f1317be48.js I, [2020-01-18T12:51:02.2626434 #1265] INFO -- : Writing /var/app/appname/public/assets/application-bc071e28a78e2b63c9313afed5ad3476e00e3f0e5b12445c37214d1f1317be48.js.gz I, [2020-01-18T12:51:08.484546 #1265] INFO -- : Writing /var/app/appname/public/assets/application-8549fb9a804686e593d5c0f90a2412a39de85908e5fb58fdf6681d4b0073d891.css I, [2020-01-18T12:51:08.485454 #1265] INFO -- : Writing /var/app/appname/public/assets/application-8549fb9a804686e593d5c0f90a2412a39de85908e5fb58fdf6681d4b0073d891.css.gzここでyarnのエラーが発生したらnode.jsをupdateします。
Node.jsのアップデート
Node.jsのバージョン管理ツールnvmを使用してインストールします。
ターミナル(EC2)# 現在のバージョン確認 [ec2-user@ip-172-31-23-189 <アプリ名>]$ node -v v4.9.1 # Node.jsのバージョン管理ツールnvmをclone [ec2-user@ip-172-31-23-189 <アプリ名>]$ git clone git://github.com/creationix/nvm.git ~/.nvm [ec2-user@ip-172-31-23-189 <アプリ名>]$ echo . ~/.nvm/nvm.sh >> ~/.bashrc [ec2-user@ip-172-31-23-189 <アプリ名>]$ . ~/.bashrc # nvmバージョン確認 [ec2-user@ip-172-31-23-189 <アプリ名>]$ nvm --version 0.35.0 # インストールできるNode.jsの確認 [ec2-user@ip-172-31-23-189 <アプリ名>]$ nvm ls-remote # 最新の安定版をインストール [ec2-user@ip-172-31-23-189 <アプリ名>]$ nvm install stable # バージョン確認 [ec2-user@ip-172-31-23-189 <アプリ名>]$ node -v v12.12.0 # バージョンが最新になっていたら成功もう一度コンパイルを実行してみましょう。
Unicornのプロセスを確認
コンパイルが成功したら反映を確認するため、Railsを再起動します。しかし、まずは今動 いているUnicornをストップします。そのために、Unicornのプロセスを確認し、プロセスを止めます。ターミナルからプロセスを確認するには
psコマンド
を利用します。ターミナル# unicorn起動 [ec2-user@ip-172-31-38-126 <リポジトリ名>]$ bundle exec unicorn_rails -c config/unicorn.rb -E production -D [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn ec2-user 17877 0.4 18.1 588472 182840 ? Sl 01:55 0:02 unicorn_rails master -c config/unicorn.rb -E production -D ec2-user 17881 0.0 17.3 589088 175164 ? Sl 01:55 0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn大事なのは左から2番目の列です。ここに表示されるのがプロセスのid、つまりPIDになります。
「unicorn_rails master」と表示されているプロセスがUnicornのプロセス本体です。この時のPIDは、17877となっています。killコマンドを入力してUnicornのプロセスを停止
ターミナル[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID> # 再度確認する [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn ... ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn # できない場合は強制終了する [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill -9 [プロセスID]先頭にRAILS_SERVE_STATIC_FILES=1をつけて、unicornを起動
続いて再びunicornを起動しましょう。このときRAILS_SERVE_STATIC_FILES=1という指定を先頭に追加してください。これは、コンパイルされたアセットをRailsが見つけられるような指定になります。以下のようにコマンドを実行してください。
ターミナル[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -Dブラウザで確認
ブラウザで http://:3000/ にアクセスして、サイトが正常に表示されているか確認してみましょう。
Railsの起動がうまくできなかった時
上記のコマンドを実行してもRailsが起動しないときや、起動できてもIPアドレス:3000に
アクセスするとエラーが表示されていることがあります。以下の項目をチェックしてみてください。
- pushのし忘れ、またはEC2サーバ側でのpullのし忘れは無いか
- ローカルでの編集のpushやEC2でのgit pullを忘れていないか
- データベースの起動は正しく行えているか
- EC2サーバ側のSECRET_KEY_BASE等は正しく設定できているか
- EC2インスタンスの再起動を行ってみる
unicornのログを確認する
上記のポイントでも解決出来ない場合はunicornのログを確認してみましょう。
lessコマンド
またはcatコマンド
を使えばログを見ることが出来ます。ターミナル(EC2)[ec2-user@ip-172-31-23-189 <app名>]$ less log/unicorn.stderr.log # 実行結果 I, [2021-02-21T02:28:26.245480 #7257] INFO -- : Refreshing Gem list I, [2021-02-21T02:28:28.232692 #7257] INFO -- : listening on addr=0.0.0.0:3000 fd=9 E, [2021-02-21T02:28:28.245079 #7257] ERROR -- : Unknown database 'app_production' (ActiveRecord::NoDatabaseError)ログはEnterキーを押せば下にスクロールすることが出来ます。(下にいくほど最新のログです。)
上の例ではUnknown database
となっているためrails db:create
をし忘れている可能生があります。
もしくはdatabase.yml
の設定に誤りがあるかもしれません。
このようにログを頼りに原因を特定していきます。終わりに
今回の内容までで、Elastic IPアドレスを入力してブラウザ上でアプリが起動していることを確認出来ました。
次回はユーザーのリクエストに対してレスポンスを送れるようにWebサーバーを導入していきます。次回
Nginxの導入
- 投稿日:2021-02-25T09:45:57+09:00
AWSにデプロイ後、よく使うコマンド
EC2にSSHで接続し、git pullしてからよく使うコマンドまとめました。
- 使用頻度が多いものを自分用に簡単にまとめました。
環境
rails 5.2.4
ruby 2.6.3
Cloud9
MySQLAWSの構成
EC2にSSHで接続する方法 $ ssh -i ~/.ssh/キー名.pem ec2-user@xx.xx.xx.xx Nginxの再起動する方法 $ sudo systemctl restart nginx アプリ(puma)起動する方法 $ rails s -e production アプリ(puma)停止する方法 $ kill プロセスID どちらも同じです $ kill $(cat tmp/pids/puma.pid)本番環境でbundle installする時のコマンド $ bundle install --path vendor/bundle --without test development CSS・JS変更時に使うコマンド $ bundle exec rails assets:precompile RAILS_ENV=production 本番環境でrails db:migrateする時のコマンド $ bundle exec rails db:migrate RAILS_ENV=productionseeds.rb編集した場合は、本番環境のデータベースを削除してからやり直す方がいいです。
本番環境のデータはなくなるので使う際はご自身で判断してください。$ RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:drop $ mysql -u root -p -h エンドポイント mysql> CREATE DATABASE 作成したいデータベース名; $ bundle exec rails db:migrate RAILS_ENV=production $ bundle exec rails db:seed RAILS_ENV=productionnginxエラーログ確認するコマンド $ sudo tail -f /var/log/nginx/error.log railsのエラーログ確認するコマンド $ sudo tail -f log/production.log全体の把握に時間がかかりたくさんのエラーを経験し心が折れそうになりました。
何度もEC2作り直ししましたのでAWSの知識もより深まりました。
今回は、S3を使っていないため今度はS3を使って構成することを考えてます?
- 投稿日:2021-02-25T08:20:39+09:00
Selenium Webdriver + Chrome DevTools Protocolを使って情報を取得してみる
はじめに 〜この記事でご紹介すること〜
本記事では「 Selenium Webdriver Chrome DevTools Protocol 」などでググるとパッとヒットするものがなかったので、それについてご紹介します。
Selenium Webdriverを使ってコードを書いたことがある方、Chrome開発者ツールやFirebugなどの解析ツールを使ったことがある方であれば
この記事を読んでいただくことで「な〜んだ!思ってたよりも簡単にいろいろなことできるようになるじゃん。」と思われるかもしれません。Chrome DevTools Protocolとは
Chrome DevTools Protocol(以下、CDP)を使うと、WebDriverでは定義されていなかったDevToolsの各種機能(JavaScrioptデバッガ、プロファイラ、パフォーマンスツール、などなど...)が利用できます。
公式ドキュメントはこちらCDPを使ってどんなことができるのかは、 @d-dai さんが書かれている記事 Chromeデベロッパーツールの使い方まとめにわかりやすく解説がされていましたのでここでは割愛します。
Selenium WebdriverでCDPを使ってみる
まず、CDPには非推奨、実験的なものも含めて以下のようにたくさんのドメインがあります。
これらすべてのリファレンスを書いてみようと思いましたが、各ドメイン毎にもメソッドが定義されており多すぎるため、
ドメインの中で情報を取得するものをいくつかピックアップしてサンプルコードを書いてみました。Domains
- Domains
- Deprecated Domains(非推奨)
- Experimental Domains(実験)
Rubyでの実装例
使用したメソッド
- Page.navigate
- 指定したURLにページに遷移します
- これはSeleniumWebdriverでおなじみの
driver.get 'https://google.com'
と同義です- Browser.getVersion
- ブラウザの情報を取得します
- Network.getAllCookies
- すべてのクッキーを取得します
- Performance.getMetrics
- 現在のパフォーマンスの値を取得します
- Performance.enableに設定しておく必要があります
ソースコード
Gemfilesource 'https://rubygems.org' gem 'selenium-webdriver'sample.rbrequire 'selenium-webdriver' driver = Selenium::WebDriver.for :chrome puts '### Page.navigate ###' driver.execute_cdp('Page.navigate', { url: 'https://google.com' }) puts "\n### Browser.getVersion ###" puts driver.execute_cdp 'Browser.getVersion' puts "\n### Network.getAllCookies ###" cookies = driver.execute_cdp 'Network.getAllCookies' puts cookies['cookies'] puts "\n### Performance.getMetrics ###" driver.execute_cdp 'Performance.enable' metrics = driver.execute_cdp 'Performance.getMetrics' puts metrics['metrics']実行結果
取得した一部情報はマスキングしています。
### Page.navigate ### ### Browser.getVersion ### {"jsVersion"=>"8.8.278.17", "product"=>"Chrome/88.0.4324.192", "protocolVersion"=>"1.3", "revision"=>"@31b458a18f133db9203eb5a5dd6552de0716dda3", "userAgent"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36"} ### Network.getAllCookies ### {"domain"=>".google.com", "expires"=>1616770705.154825, "httpOnly"=>false, "name"=>"1P_JAR", "path"=>"/", "priority"=>"Medium", "sameSite"=>"None", "secure"=>true, "session"=>false, "size"=>19, "value"=>"2021-02-24-14"} {"domain"=>".google.com", "expires"=>1629989904.81409, "httpOnly"=>true, "name"=>"NID", "path"=>"/", "priority"=>"Medium", "sameSite"=>"None", "secure"=>true, "session"=>false, "size"=>178, "value"=>"XXXXXX"} ### Performance.getMetrics ### {"name"=>"Timestamp", "value"=>655734.877406} {"name"=>"AudioHandlers", "value"=>0} {"name"=>"Documents", "value"=>2} {"name"=>"Frames", "value"=>2} {"name"=>"JSEventListeners", "value"=>173} {"name"=>"LayoutObjects", "value"=>92} {"name"=>"MediaKeySessions", "value"=>0} {"name"=>"MediaKeys", "value"=>0} {"name"=>"Nodes", "value"=>325} {"name"=>"Resources", "value"=>17} {"name"=>"ContextLifecycleStateObservers", "value"=>6} {"name"=>"V8PerContextDatas", "value"=>1} {"name"=>"WorkerGlobalScopes", "value"=>0} {"name"=>"UACSSResources", "value"=>0} {"name"=>"RTCPeerConnections", "value"=>0} {"name"=>"ResourceFetchers", "value"=>2} {"name"=>"AdSubframes", "value"=>0} {"name"=>"DetachedScriptStates", "value"=>0} {"name"=>"ArrayBufferContents", "value"=>0} {"name"=>"LayoutCount", "value"=>0} {"name"=>"RecalcStyleCount", "value"=>0} {"name"=>"LayoutDuration", "value"=>0} {"name"=>"RecalcStyleDuration", "value"=>0} {"name"=>"DevToolsCommandDuration", "value"=>0.000104} {"name"=>"ScriptDuration", "value"=>0} {"name"=>"V8CompileDuration", "value"=>0} {"name"=>"TaskDuration", "value"=>0.000131} {"name"=>"TaskOtherDuration", "value"=>2.7e-05} {"name"=>"ThreadTime", "value"=>0.000256} {"name"=>"ProcessTime", "value"=>0.36707} {"name"=>"JSHeapUsedSize", "value"=>7745352} {"name"=>"JSHeapTotalSize", "value"=>10215424} {"name"=>"FirstMeaningfulPaint", "value"=>0} {"name"=>"DomContentLoaded", "value"=>655734.046827} {"name"=>"NavigationStart", "value"=>655733.006387}
- 投稿日:2021-02-25T07:02:30+09:00
Dockerのコンテナ立ち上げで上手くいかなかった時にやったこと
はじめに
初学者で、今回が初めての投稿になります。
Docker超入門 #5
(動画内容としましては イメージ作成→コンテナ作成→コンテナ起動 の順になります)
こちらの動画の内容に沿って学習した際に起こったエラー、そしてどう対処したかの記事になりますエラー内容
イメージ作成するとこまでは上手く行ったのですがコンテナ作成時にエラー表示が出ました。
結論、今回のエラーはイメージ内の誤字にありました。やって見たこと
まずは作成はできていたものの、「イメージ作成」が上手く行っていないのではと思いファイル内の誤字脱字チェックに入りました。やはり2箇所誤字発見できました。そして今回はイメージファイルにミスがあったものの、イメージが作成出来てしまったのだとわかりました
対処の流れとしては
「①間違って起動したコンテナの停止→そのコンテナの削除→誤字含むイメージの削除」
続いて
「②修正後のイメージ作成→コンテナ立ち上げ→コンテナ起動」で上手く行きましたコンテナの稼働状況チェック→停止→削除
$ docker ps $ docker container stop 対象のコンテナ名 $ docker container rm 対象のコンテナ名イメージのチェック→削除
$ docker images $ docker image rm 対象のイメージ名最後に
もし記事内容に誤り等ございましたら是非ともご指摘ください。
同じような初学者の方の参考になれば幸いです
- 投稿日:2021-02-25T05:01:43+09:00
【 Ruby on Rails 6.0 】AWS + Nginx + Unicornでデプロイ④
始めに
前回の記事ではEC2インスタンスにmariaDBというデータベースの設定を行いました。今回はインスタンス上でRailsアプリの起動に必要なGemをインストールし環境変数を設定する工程までをまとめたいと思います。
目次
目次 内容 セクション1 EC2インスタンス作成 セクション2 Linuxサーバー構築 セクション3 データベース設定 セクション4 EC2上でGemをインストールし環境変数を設定(今回の内容) セクション5 Railsアプリを起動 セクション6 Nginxの導入 EC2のサーバにアプリのコードをクローン
全世界に公開できるIPアドレスを持ったEC2サーバ上でRailsアプリを動かすためにアプリケーションのコードをGithubからEC2サーバへクローンします。
GithubにSSH鍵を登録
現状、EC2サーバにアプリケーションのコードをクローンしようとしても permission deniedとエラーが出てしまいます。これは、Githubから見てこのEC2インスタンスが何者かわからないためです。
EC2インスタンスからGithubにアクセスするためには、作成したEC2インスタンスのSSH 公開鍵をGithubに登録する必要があります。 SSH鍵をGithubに登録すると、Githubはそれを認証に利用し、コードのクローンを許可してくれるようになります。
ターミナル[ec2-user@ip-172-31-23-189 ~]$ ssh-keygen -t rsa -b 4096 # 三段階ほど認証を求められるが全てyesでenterを押す Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: # 以下の表示が出れば成功 Your identification has been saved in /home/ec2-user/.ssh/id_rsa. Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub. The key fingerprint is: 3a:8c:1d:d1:a9:22:c7:6e:6b:43:22:31:0f:ca:63:fa ec2-user@ip-172-31-23-189 The key's randomart image is: +--[ RSA 4096]----+ | + | | . . = | | = . o . | | * o . o | |= * S | |.* + . | | * + | | .E+ . | | .o | +-----------------+ # .SSH公開鍵を表示し、値をコピーするため、下記コマンドを実装 [ec2-user@ip-172-31-23-189 ~]$ cat ~/.ssh/id_rsa.pub # ssh~から最後の文字列までをコピーする ssh-rsa AAAAB3Nza・・・・・以下のURLからGithubにアクセスします。
https://github.com/settings/keys
アクセスしたら以下の画像のように操作します。
Githubに鍵を登録できたら、SSH接続できるか以下のコマンドで確認してみましょう。ターミナル[ec2-user@ip-172-31-23-189 ~]$ ssh -T git@github.com # 下記の表示が出たらyesを選択 The authenticity of host 'github.com (IP ADDRESS)' can't be established. RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48. Are you sure you want to continue connecting (yes/no)? # 成功したら下記の表示が出る [ec2-user@ip-172-31-23-189 ~]$ ssh -T git@github.com Hi <Githubユーザー名>! You've successfully authenticated, but GitHub does not provide shell access. # エラーが出たらログインし直してもう一度ssh接続する Warning: Permanently added the RSA host key for IP address 'XXXXXXXXX' to the list of known hosts.アプリケーションサーバの設定
アプリケーションサーバとは、ブラウザからの「リクエスト」を受け付けRailsアプリケ ーションを実際に動作させるソフトウェアのことです。
アプリケーションサーバが動いていれば、ブラウザからのリクエストを受け付けてRails アプリケーションが動作します。
という訳で、全世界に公開するEC2サーバ上でもアプリケーションサーバを動かす必要が あるのです。今回はUnicorn
というアプリケーションサーバーを使用します。Unicorn
全世界に公開されるサーバ上で良く利用されるアプリケーションサーバです。rails sコマンドの代わりに unicorn_rails コマンドで起動することができます。
この後、EC2サーバにSSH接続しUnicornを起動することで全世界からアクセスできるようにしていきます。Unicornをインストール
UnicornはRubyで作成されており、gem化されています。なのでローカルのGemfileを編集しましょう。
Gemfile# 追記(本番環境) group :production do gem 'unicorn', '5.4.1' # バージョン指定 endターミナル(ローカル)$ bundle installconfig/unicorn.rbを作成し、内容を以下のように編集
Unicornの設定ファイルとして、次の内容でファイルを作成しましょう。最初からは存在していないので、自分でconfig ディレクトリ以下に作成します。
この後すぐ、ファイルの中身がそれぞれ何を行なっているか説明しますので、まずはコピ ー&ペーストで作成したファイルに貼り付けましょう。config/unicorn.rbapp_path = File.expand_path('../../', __FILE__) #アプリケーションサーバの性能を決定する worker_processes 1 #アプリケーションの設置されているディレクトリを指定 working_directory app_path #Unicornの起動に必要なファイルの設置場所を指定 pid "#{app_path}/tmp/pids/unicorn.pid" #ポート番号を指定 listen 3000 #エラーのログを記録するファイルを指定 stderr_path "#{app_path}/log/unicorn.stderr.log" #通常のログを記録するファイルを指定 stdout_path "#{app_path}/log/unicorn.stdout.log" #Railsアプリケーションの応答を待つ上限時間を設定 timeout 60 #以下は応用的な設定なので説明は割愛 preload_app true GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true check_client_connection false run_once = true before_fork do |server, worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! if run_once run_once = false # prevent from firing again end old_pid = "#{server.config[:pid]}.oldbin" if File.exist?(old_pid) && server.pid != old_pid begin sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU Process.kill(sig, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH => e logger.error e end end end after_fork do |_server, _worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection end変更修正をリモートリポジトリに反映
ここまで、ローカルのフォルダ内で変更修正を行ったので、こちらをリモートリポジトリへ反映します。変更修正をコミットしてプッシュしましょう。
この時必ず、masterブランチ
で行うようにしてください。もし、別ブランチでコミット&プッシュした場合は、リモートリポジトリでプルリクエストを作成し、ブランチをmasterへマージしてください。Githubからコードをクローン
続いて、Unicornの設定を済ませたコードをEC2インスタンスにクローンします。
/var/wwwディレクトリを作成し、権限をec2-userに変更
ターミナル(EC2)#/var/wwwディレクトリを作成(後述するCapistranoの初期値がwwwなので、ディレクトリをwwwに設定しています) [ec2-user@ip-172-31-23-189 ~]$ sudo mkdir /var/www/ #作成したwwwディレクトリの権限をec2-userに変更 [ec2-user@ip-172-31-23-189 ~]$ sudo chown ec2-user /var/www/Githubから「リポジトリURL」を取得
取得した「リポジトリURL」を使って、コードをクローンします。ターミナル(EC2)[ec2-user@ip-172-31-23-189 ~]$ cd /var/www/ [ec2-user@ip-172-31-23-189 www]$ git clone https://github.com/リポジトリURL # 成功したら以下の表示が出る remote: Enumerating objects: 298, done. remote: Counting objects: 100% (298/298), done. remote: Compressing objects: 100% (190/190), done. remote: Total 298 (delta 109), reused 274 (delta 86), pack-reused 0 Receiving objects: 100% (298/298), 58.53 KiB | 365.00 KiB/s, done. Resolving deltas: 100% (109/109), done.EC2の能力を拡張
現状動かしているEC2のインスタンスではコンピューターの能力が足りず、Gemのインストール時などにエラーが発生する可能性があります。具体的には、コンピューターの処理能力に関係するメモリというものが足りません。これは、無料で動かせるインスタンスの限界であるため仕方ありません。
そこで、今後の設定を行う前にメモリを増強する処理を行います。ターミナル# ホームディレクトリに移行 [ec2-user@ip-172-31-25-189 ~]$ cd ~ [ec2-user@ip-172-31-25-189 ~]$ sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512 # 成功すると以下の表示が出る 512+0 レコード入力 512+0 レコード出力 536870912 バイト (537 MB) コピーされました、 5.19011 秒、 103 MB/秒 # 次は権限に制限をかけましょう(chmodコマンド) [ec2-user@ip-172-31-25-189 ~]$ sudo chmod 600 /swapfile1 # スワップ(swap)領域を作成する - mkswap [ec2-user@ip-172-31-25-189 ~]$ sudo mkswap /swapfile1 #成功すると下記の表示が出ます スワップ空間バージョン1を設定します、サイズ = 524284 KiB ラベルはありません, UUID=74a961ba-7a33-4c18-b1cd-9779bcda8ab1 # スワップ(swap)領域を有効化する - swapon [ec2-user@ip-172-31-25-189 ~]$ sudo swapon /swapfile1 [ec2-user@ip-172-31-25-189 ~]$ sudo sh -c 'echo "/swapfile1 none swap sw 0 0" >> /etc/fstab'gemをインストール
ターミナル(EC2)# まずは、EC2にダウンロードしたWEB Appを開く [ec2-user@ip-172-31-23-189 www]$ cd /var/www/アプリ名 # Rubyのバージョンを確認する [ec2-user@ip-172-31-23-189 <アプリ名>]$ ruby -v # 指定したrubyのバージョンが表示されれば成功です。 ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]本番環境でgemを管理するための bundlerをインストール
まず開発環境(ローカル)で開発してきたアプリにおいて、どのバージョンの bundlerが使われていたのか確認します。
ターミナル(ローカル)$ bundler -v # するとバージョンが表示されます Bundler version 2.1.4 # 人によってバージョンが違う可能性があります開発環境で仕様しているbundlerのバージョンがわかったので、同じバージョンのものを EC2サーバ側にも導入します。上記の場合では、bundler 2.1.4のバージョンを導入して
bundle install
を実行します。ターミナル(EC2)# ローカルで確認したbundlerのバージョンを導入する [ec2-user@ip-172-31-23-189 <アプリ名>]$ gem install bundler -v 2.1.4 Fetching bundler-2.1.4.gem Successfully installed bundler-2.1.4 Parsing documentation for bundler-2.1.4 Installing ri documentation for bundler-2.1.4 Done installing documentation for bundler after 3 seconds 1 gem installed # 上記コマンドは、数分以上かかる場合もあります。 [ec2-user@ip-172-31-23-189 <アプリ名>]$ bundle install環境変数の設定
データベースのパスワードなどセキュリティのためにGithubにアップロードすることがで きない情報は、環境変数というものを利用して設定します。
環境変数は、Railsからは ENV['<環境変数名>'] という記述でその値を利用することができます。今回は、SECRET_KEY_BASE という環境変数を指定していきます。secret_key_baseを作成
ターミナル(EC2)[ec2-user@ip-172-31-23-189 <アプリ名>]$ rake secret # うまくいくと、 cdfasdfadgfsadfdgc314751a8dadfadf7c8b9a1dc888e... という感じで表示されます。 これをコピーしておきましょう環境変数は /etc/environment というファイルに保存することで、サーバ全体に適用さ れます。環境変数の書き込みはvimコマンドを使用して行います。
ターミナル[ec2-user@ip-172-31-23-189 <アプリ名>]$ sudo vim /etc/environment
上の様な画面になれば、iと打ち込んで入力モードに切り替えた後、下記の記述を打ち込みます。=
の前後にスペースは入れません。/etc/environmentDATABASE_PASSWORD='データベースのrootユーザーのパスワード' SECRET_KEY_BASE='先程コピーしたsecret_key_base'書き込みができたら esc(エスケープキー)を押下後、
:wq
と入力して内容を保存します。 保存できたら環境変数を適用するために一旦ログアウトします。ターミナル(EC2)[ec2-user@ip-172-31-23-189 <アプリ名>]$ exit logout Connection to XX.XXX.XXX.XX closed. # もう一度ログイン $ ssh -i ダウンロードした鍵の名前.pem ec2-user@Elastic IPアドレス # 設定した環境変数が適用されているか確認 $ env | grep SECRET_KEY_BASE SECRET_KEY_BASE='XXXXXXXXXXXXXXXXXXXXXXXXX' $ env | grep DATABASE_PASSWORD DATABASE_PASSWORD='XXXXXXXXXXXXX'これで環境変数が設定出来ました!
終わりに
ここまでで、EC2インスタンスにSSHログインしてアクセスすることが出来ました。
次回でHTTP通信を可能にしてRailsアプリを起動させる工程まで進めます。
お疲れさまでした。。。次回
Railsアプリを起動
- 投稿日:2021-02-25T02:32:42+09:00
railsを使ってアプリを作る 登録編
データベースにデータを登録
ルーティングの作成
ルーティングに登録する時はpostで登録する
post "animals/create" => "animals#create"
postで登録する時は、フォームで入力した値をコントローラーで受け取る時に使うらしい
viewの作成
送りたい内容を
<%= form_tag "/animals/create" do %><% end %>
で囲むコントローラーの作成
viewからのデータを取得する
view = Animal.new(name: params[:name])
view.save登録が完了したら、別ページに遷移させる(リダイレクト)
redirect_to "/animals/index"
- 投稿日:2021-02-25T01:47:11+09:00
railsを使ってアプリを作る3
詳細ページ等のid付きのページの作成と表示
mvcの作成
urlが
/details/1
みたいにする場合は、ルーティングの書き方を
get "details/:id" => "details#show
にするdetailsコントローラーにshowメソッドを作成する
show.html.erbのファイルを作成する
idの取得と利用方法
コントローラーでparams[:id] で取得する
取得した値を
@id = params[:id]
とし、viewで<%= @id %>
と書いて使う画面に置くリンクはこんな感じ
<% @details.each do |detail| %>
<%=link_to (animal.content,"/animal/#{detail.id}")%>
<% end %>
- 投稿日:2021-02-25T01:01:37+09:00
ポートフォリオ制作録(Day1:環境構築~デプロイ)
はじめに
- 今日は,railsの開発環境をvscodeで構築し,localhostとheroku上でhello worldが表示されるまで進めました
発生したエラーと解決策
1.herokuへデプロイ出来ない①
デプロイ時にweb pack not foundとエラーがでました。warningの文でnode.jsが古いよとあったので、node.jsをインストール(同時にnpmも)したら解決。
2.herokuへデプロイできない②
チュートリアルのままbundle installをしていたが、その際にインストールされるbundlerのバージョンがherokuに対応していないらしく,デプロイができなかった。bundleのバージョンをherokuに対応しているものにして再インストールして解決
3.application errorが出るよ問題
herokuにデプロイできたがapplicationエラーが発生。エラー文を読むと,postgreのバージョンが変だよとのことなので、指定し直したらできました
参考
- Rails チュートリアル