20210605のRailsに関する記事は18件です。

Railsチュートリアル(第6版) 第12章 パスワードの再設定

第12章 第11章ではアカウント有効化の実装が完了した。 今回の章ではパスワードを忘れた時に再設定できる機能を追加する。 やることは第11章と似ている。 流れ 1.ユーザーがパスワードの再設定をリクエストすると、ユーザーが送信したメールアドレスをキーにしてデータベースからユーザーを見つける 2.該当のメールアドレスがデータベースにある場合は、再設定用トークンとそれに対応する再設定ダイジェストを生成する 3.再設定用ダイジェストはデータベースに保存しておき、再設定用トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく 4.ユーザーがメールのリンクをクリックしたら、メールアドレスをキーとしてユーザーを探し、データベース内に保存しておいた再設定用ダイジェストと比較する(トークンを認証する) 5.認証に成功したら、パスワード変更用のフォームをユーザーに表示する PasswordResetsリソース まずは、PasswordResetsリソースのモデリングから始める。 前章と同じく新たなモデルは作らず、代わりに必要なデータをUserモデルに追加する。 PasswordResetsをリソースとして扱うため、RESTfulなURLを用意する。 今回は、ビューを描画するためのnewアクションとeditアクションが必要になる。 $ git checkout -b password-reset トピックブランチを作成する。 PasswordResetsコントローラ newアクションとeditアクションを含んだコントーラーの作成 $ rails generate controller PasswordResets new edit --no-test-framework テストを生成しないオプションを指定している。 理由として、統合テストでカバーしていくから。 新しいパスワードを再設定するためのフォームとUserモデル内のパスワードを変更するためのフォームが必要。 従って、new、create、edit、updateのルーティングを用意する。 config/routes.rb Rails.application.routes.draw do resources :password_resets, only: [:new, :create, :edit, :update] end HTTPリクエスト URL Action 名前付きルート GET /password_resets/new new new_password_reset_path POST /password_resets create password_resets_path GET /password_resets/トークン/edit edit edit_password_reset_url(token) PATCH /password_resets/トークン update password_reset_url(token) RESTfulルーティング パスワード再設定画面へのリンクを追加 <%= link_to "(forgot password)", new_password_reset_path %> app/views/sessions/new.html.erb <% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(url: login_path, scope: :session, local: true) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= link_to "(forgot password)", new_password_reset_path %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :remember_me, class: "checkbox inline" do %> <%= f.check_box :remember_me %> <span>Remember me on this computer</span> <% end %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div> 新しいパスワードの設定 今までのように、トークン用の仮想的な属性とそれに対応するダイジェストを用意する。 トークン関連で学んだ事だが、トークンをハッシュ化せず(平文で)データベースに保存してしまうと、攻撃者がトークンを盗み出したときセキュリティ上問題がある。なので、ダイジェストを使おう。 また、再設定用リンクは期限を設ける。そのために、送信時刻を記録する必要がある。 以上を踏まえて、reset_digest属性とreset_sent_at属性をUserモデルに追加する。 参照:railsチュートリアル 上記のUsersテーブルのするためマイグレーションを実行 $ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime いつものようにマイグレーション $ rails db:migrate 新しいパスワード再設定画面ビュー app/views/password_resets/new.html.erb <% provide(:title, "Forgot password") %> <h1>Forgot password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(url: password_resets_path, scope: :password_reset, local: true) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %> </div> </div> 演習 解答例 シンボルを使ってフォームを送信する事で、Railsが自動的に送信先に値を割り当てる。 createアクションでパスワード再設定 Forgot passwordから送信後、メールアドレスをキーとしてユーザーをデータベースから探し、パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する。 続いて、ルートURLにリダイレクトし、フラッシュメッセージを表示する。なお、送信が無効な場合は、newページを出力してfash.nowメッセージを表示する。 app/controllers/password_resets_controller.rb class PasswordResetsController < ApplicationController def new end def create @user = User.find_by(email: params[:password_reset][:email].downcase) if @user @user.create_reset_digest @user.send_password_reset_email flash[:info] = "Email sent with password reset instructions" redirect_to root_url else flash.now[:danger] = "Email address not found" render 'new' end end def edit end end Userモデルにもコードを追加していく。(create_activation_digestメソッドと似ている) app/models/user.rb class User < ApplicationRecord attr_accessor :remember_token, :activation_token, :reset_token # パスワード再設定の属性を設定する def create_reset_digest self.reset_token = User.new_token update_attribute(:reset_digest, User.digest(reset_token)) update_attribute(:reset_sent_at, Time.zone.now) end # パスワード再設定のメールを送信する def send_password_reset_email UserMailer.password_reset(self).deliver_now end end この時点では、無効なメールアドレスを入力すると正常に動作する。 そして、正しいメールアドレスを送信した場合にも正常に動作させるには、パスワード再設定のメイラーメソッドを定義する必要がある。 演習 エラーメッセージの確認 パスワード再設定のメール送信 パスワード再設定に関するメールを送信する部分を作成する。 第11章でUserメイラーを生成した時、デフォルトのpassword_restメソッドもまとめて生成されている。 パスワード再設定のメールとテンプレート UserメイラーにあるコードをUserモデルに移すリファクタリングを行う。 UserMailer.password_reset(self).deliver_now このコードの実装のために必要なpassword_resetメソッドを、Userメイラーに設定する。 app/mailers/user_mailer.rb def password_reset(user) @user = user mail to: user.email, subject: "Password reset" end パスワード再設定テンプレート(テキスト) app/views/user_mailer/password_reset.text.erb To reset your password click the link below: <%= edit_password_reset_url(@user.reset_token, email: @user.email) %> This link will expire in two hours. If you did not request your password to be reset, please ignore this email and your password will stay as it is. パスワード再設定テンプレート(HTML) app/views/user_mailer/password_reset.html.erb <h1>Password reset</h1> <p>To reset your password click the link below:</p> <%= link_to "Reset password", edit_password_reset_url(@user.reset_token, email: @user.email) %> <p>This link will expire in two hours.</p> <p> If you did not request your password to be reset, please ignore this email and your password will stay as it is. </p> Railsのメールプレビュー機能でプレビューを設定する。 test/mailers/previews/user_mailer_preview.rb class UserMailerPreview < ActionMailer::Preview def password_reset user = User.first user.reset_token = User.new_token UserMailer.password_reset(user) end end メールのプレビュー 送信メールのテスト メイラーメソッドのテストを書く。 test/mailers/user_mailer_test.rb test "password_reset" do user = users(:michael) user.reset_token = User.new_token mail = UserMailer.password_reset(user) assert_equal "Password reset", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.reset_token, mail.body.encoded assert_match CGI.escape(user.email), mail.body.encoded end パスワードを再設定する PasswordResetsコントローラのeditアクションの実装を進める。 また、統合テストでも行う。 editアクションで再設定 パスワード再設定の送信メールには、以下のようなリンクがある。 https://example.com/password_resets/3BdBrXeQZSWqFIDRN8cxHA/edit?email=fu%40bar.com このリンクを機能させるには、パスワード再設定のフォームを表示するビューが必要だ。 今回ちょっと面倒なのが、メールアドレスをキーとしてユーザーを検索するのに、editアクションとupdateアクションの両方でメールアドレスが必要になる。 なので、最初はメールアドレス入りリンクのおかげでeditアクションはOK。 しかし、フォームを一度送信すると、この情報は消える。 故に、この値をどこかに保存しよう!となる。 そこで今回使うのは、隠しフィールド。これでページ内にメールアドレスを保持し、フォームから送信した時に他の情報と一緒にメールアドレスが送信されるようになる。 app/views/password_resets/edit.html.erb <% provide(:title, 'Reset password') %> <h1>Reset password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, url: password_reset_path(params[:id]), local: true) do |f| %> <%= render 'shared/error_messages' %> <%= hidden_field_tag :email, @user.email %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Update password", class: "btn btn-primary" %> <% end %> </div> </div> 上のタグでは、フォームタグヘルパーを使っている。 hidden_field_tag :email, @user.email 今までは以下のようなコードを書いていた f.hidden_field :email, @user.email ・ポイント 前者のhidden_field_tagではメールアドレスがparams[:email]に保存される。 後者はparams[:user][:email]に保存される。 今後、上記のフォームを描画するためにPasswordResetsコントローラのeditアクション内で@userインスタンス変数を定義する。 params[:email]のメールアドレスに対応するユーザーを先ほどの変数に入れる。 そして、params[:id]の再設定用トークンとauthenticated?メソッドで、このユーザーが正当なユーザーかことを確認する。 editアクションとupdateアクションのどちらでも正当な@userが存在しなければならないため、beforeフィルタで@userの検索とバリデーションを行う。 app/controllers/password_resets_controller.rb class PasswordResetsController < ApplicationController before_action :get_user, only: [:edit, :update] before_action :valid_user, only: [:edit, :update] . . . def edit end private def get_user @user = User.find_by(email: params[:email]) end # 正しいユーザーかどうか確認する def valid_user unless (@user && @user.activated? && @user.authenticated?(:reset, params[:id])) redirect_to root_url end end end メールアドレスを入力し送信した結果にあったリンクから開くと、、、 パスワード再設定用のページが出てきた。 演習 現時点では、パスワードを更新するボタンを押しても動作しない。 パスワードを更新する フォームからの送信に対応するupdateアクションが必要だ。 updateアクションの4つのケース 1.パスワード再設定の有効期限が切れていないか 2.無効なパスワードであれば失敗させる(失敗した理由も表示する) 3.新しいパスワードが空文字列になっていないか(ユーザー情報の編集ではOKだった) 4.新しいパスワードが正しければ、更新する 1、2、4はこれまでの知識で対応可能。 1は、editとupdateアクションに以下のメソッドとbeforeフィルターを用意し対応する。 before_action :check_expiration, only: [:edit, :update] # (1)への対応案 check_expirationメソッドは、有効期限をチェックするPrivateメソッドとして定義する。 # 期限切れかどうかを確認する def check_expiration if @user.password_reset_expired? flash[:danger] = "Password reset has expired." redirect_to new_password_reset_url end end 上記のメソッドを呼び出しbeforeフィルターで保護したので、2と4のケースには対応できそう。 2は、無効なパスワードであれば失敗はOK 4は、更新が成功したらパスワードを再設定後、ログインさせればOK 問題としてパスワードが空文字だった時のケース。 以前Userモデルを作っていた時に、パスワードが空でも良いという実装をした。 従って、今回は明示的にキャッチする必要がある。 今回は、@userオブジェクトにエラーメッセージを追加するという方法を取る。 やり方は、errors.addを使ってエラーメッセージを追加する。 @user.errors.add(:password, :blank) 以上の結果から、1を除いたすべてのケースに対応したupdateアクションが完成する。 app/controllers/password_resets_controller.rb class PasswordResetsController < ApplicationController before_action :check_expiration, only: [:edit, :update] # (1)への対応 def update if params[:user][:password].empty? # (3)への対応 @user.errors.add(:password, :blank) render 'edit' elsif @user.update(user_params) # (4)への対応 log_in @user flash[:success] = "Password has been reset." redirect_to @user else render 'edit' # (2)への対応 end end private def user_params params.require(:user).permit(:password, :password_confirmation) end # トークンが期限切れかどうか確認する def check_expiration if @user.password_reset_expired? flash[:danger] = "Password reset has expired." redirect_to new_password_reset_url end end @user.password_reset_expired?を動作させるため、password_reset_expired?メソッドをUserモデルに定義する。 2時間以上パスワードが再設定されなかった場合には、期限切れとする。 reset_sent_at < 2.hours.ago 不等号の「<」は、「より少ない」と読むのではなく「早い」と考えると良い。 今回は2時間より早い=2時間以内捉えられる。 password_reset_expired?メソッドの実装。 app/models/user.rb class User < ApplicationRecord . . . # パスワード再設定の期限が切れている場合はtrueを返す def password_reset_expired? reset_sent_at < 2.hours.ago end private . . . end これで全体の実装はOK パスワードの再設定をテストする 送信に成功した場合と失敗した場合の統合テストを作成する。 パスワード再設定のテストファイルを生成する。 $ rails generate integration_test password_resets パスワード再設定手順 ・「forgot password」フォームを表示して無効なメールアドレスを送信 ・次にそのフォームで有効なメールアドレスを送信 ・後者ではパスワード再設定用のトークンが生成され、再設定用のメールが送信される。 ・続いて、メールのリンクを開き、無効な情報を送信 ・次にそのリンクから有効な情報を送信 ・それぞれが期待通りに動作しているかの確認を行う。 パスワード再設定の統合テスト test/integration/password_resets_test.rb require 'test_helper' class PasswordResetsTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear @user = users(:michael) end test "password resets" do get new_password_reset_path assert_template 'password_resets/new' assert_select 'input[name=?]', 'password_reset[email]' # メールアドレスが無効 post password_resets_path, params: { password_reset: { email: "" } } assert_not flash.empty? assert_template 'password_resets/new' # メールアドレスが有効 post password_resets_path, params: { password_reset: { email: @user.email } } assert_not_equal @user.reset_digest, @user.reload.reset_digest assert_equal 1, ActionMailer::Base.deliveries.size assert_not flash.empty? assert_redirected_to root_url # パスワード再設定フォームのテスト user = assigns(:user) # メールアドレスが無効 get edit_password_reset_path(user.reset_token, email: "") assert_redirected_to root_url # 無効なユーザー user.toggle!(:activated) get edit_password_reset_path(user.reset_token, email: user.email) assert_redirected_to root_url user.toggle!(:activated) # メールアドレスが有効で、トークンが無効 get edit_password_reset_path('wrong token', email: user.email) assert_redirected_to root_url # メールアドレスもトークンも有効 get edit_password_reset_path(user.reset_token, email: user.email) assert_template 'password_resets/edit' assert_select "input[name=email][type=hidden][value=?]", user.email # 無効なパスワードとパスワード確認 patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "foobaz", password_confirmation: "barquux" } } assert_select 'div#error_explanation' # パスワードが空 patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "", password_confirmation: "" } } assert_select 'div#error_explanation' # 有効なパスワードとパスワード確認 patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "foobaz", password_confirmation: "foobaz" } } assert is_logged_in? assert_not flash.empty? assert_redirected_to user end end 新しいものとして、inputタグがある。 assert_select "input[name=email][type=hidden][value=?]", user.email これは、inputタグに正しい名前、tyepe="hidden"、メールアドレスがあるか確認している。 <input id="email" name="email" type="hidden" value="michael@example.com" /> テストはOK 本番環境でのメール送信 本番環境でのメール送信は第11章でやったので、あとはGitのトピックブランチをmasterにマージしておく。 $ rails test $ git add -A $ git commit -m "Add password reset" $ git checkout master $ git merge password-reset リモートレポジトリにプッシュし、Herokuにデプロイ $ rails test $ git push && git push heroku $ heroku run rails db:migrate 最後に 第11章と似ている個所もあったが、難しい章になったと思う。 残りの章では、マイクロポスト機能とフォローなどのステータスフィード機能を実装していく。 has_manyやhas_many:throughが出てくる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】localeを使いこなして国際化向けのアプリと化そう!

localeとは デフォルトではエラーメッセージ等は英語で表示されるがconfig/locales/配下に多言語化用の言語ファイルを配置することで、指定した言語や文言に変換することが出来ます。 また、住所や時間の形式なども管理することが出来ます。 使い方 1.config/application.rbに以下のような記述をする。 config/application.rb . . . module SampleApp class Application < Rails::Application config.load_defaults 6.0 # config/locales以下のファイルを読み込む config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}").to_s] # デフォルトの言語を日本語にする config.i18n.default_locale = :ja end end 2.Gemfileに以下を記載してbundle installを実行する。 ※このgemをインストールすることで、よく使う言葉のlocaleファイルをダウンロードしなくても使用可能になります。 gem 'rails-i18n' 3.locales以下にファイルを作成し好きなように変換する。 config/locales/greet.ja.yml ja: word: greeting: hello: "こんにちは" 4.使用する際はi18n.t('〇〇.△△.□□')と言ったように階層を「.」で繋ぐ。 I18n.t("word.greeting.hello") #=> "こんにちは" 注意点 config/以下のファイルの内容を変更した場合は、変更を反映させる為にサーバーを再起動させる。 日本語にする際は、ファイル名を〇〇.ja.ymlとする。 対象となるモデルオブジェクトが狭義のモデル(ActiveRecord::Baseを継承するモデルクラス)のインスタンスである場合はactiverecordを使用する。 ja: activerecord: attributes: ... 対象となるモデルオブジェクトが広義のモデル(ActiveRecord::Baseを継承しないモデルクラス)のインスタンスである場合はactivemodelを使用する。 ja: activemodel: attributes: ... modelsやviewsなど、まとまりごとに管理すると分かりやすい。 config └── locales ├── models # models関連のファイルはここで管理する │ ├── staff │ ├── staff.ja.yml └── views # views関連のファイルはここで管理する │ ├── staff │ │ └── index.ja.yml └── enums # enums関連のファイルはここで管理する ├── staff └── staff.ja.yml 使用例 models config/locales/models/staff/staff.ja.yml ja: activerecord: models: staff: スタッフ attributes: staff: id: ID name: 名前 email: メールアドレス created_at: 登録日 updated_at: 更新日 views config/locales/views/staff/staff.ja.yml ja: staffs: index: title: 'スタッフ一覧' show: title: '%{name}さんの詳細情報' edit: title: '%{name}さんの情報を編集' time ※時間のフォーマットを管理するファイル。 config/locales/time.ja.yml ja: date: formats: japanese: "%Y年%m月%d日" japanese_without_zero: "%Y年%_m月%_d日" japanese_without_zero_and_blank: "%Y年%-m月%-d日" japanese_with_week: "%Y年%m月%d日(%a)" japanese_with_week_without_zero_and_blank: "%Y年%-m月%-d日 (%a)" japanese_kana: "%Jf" japanese_kana_year: "%Jy" japanese_kana_month: "%Jm" date: "%Y/%m/%d" hyphen: "%Y-%m-%d" time: formats: japanese_without_zero_until_minitue: "%Y年%_m月%_d日 %H:%M" japanese_without_zero_with_week_include_time: "%Y年%_m月%_d日(%a) %H:%M" japanese_without_zero_and_blank: "%Y年%-m月%-d日" japanese_with_week_include_time: "%Y年%m月%d日(%a) %H:%M" japanese_until_minitue: "%Y年%m月%d日 %H:%M" japanese_date: "%Y年%m月%d日" japanese_date_without_zero_and_blank: "%Y年%-m月%-d日" japanese_without_year_with_week_until_minitue: "%m月%d日(%a) %H:%M" japanese_date_with_week: "%m月%d日(%a)" japanese_date_with_week_without_zero: "%_m月%_d日(%a)" japanese_date_without_year: "%m月%d日" week: "%a" date: "%Y/%m/%d" slash: "%Y/%m/%d %H:%M:%S" slash_until_minitue: "%Y/%m/%d %H:%M" hyphen: "%Y-%m-%d %H:%M:%S" hyphen_until_minitue: "%Y-%m-%d %H:%M" number_only: "%Y%m%d%H%M%S" date_digital: "%Y%m%d" progressed_date: "%m/%d(%a)" time: '%H:%M' time_digital: '%-H%M' time_kanji: "%_H時%M分" time_kanji_with_zero: "%H時%M分" kaminari ※kaminari導入時のlocaleファイル。 config/locales/kaminari.ja.yml ja: helpers: page_entries_info: more_pages: display_entries: '<div class="list-count">[<span class="list-count__all">%{total}</span>件中 %{first}〜%{last}件表示]</div>' one_page: display_entries: one: '<div class="list-count">[<span class="list-count__all">%{count}</span>件中 1〜%{count}件表示]</div>' other: '<div class="list-count">[<span class="list-count__all">%{count}</span>件中 1〜%{count}件表示]</div>' zero: '<span class="text-notice">該当するデータが1件もありません。</span>' views: pagination: first: "&lt;&lt; 最初のページ" last: "最後のページ &gt;&gt;" next: "次のページ &gt;" previous: "&lt; 前のページ" truncate: "&hellip;" device ※device導入時のlocaleファイル。 config/locales/device.ja.yml ja: devise: failure: staff: already_authenticated: すでにログインしています。 invalid: "%{authentication_keys}、もしくはパスワードが違います。" not_found_in_database: "%{authentication_keys}、もしくはパスワードが違います。" timeout: セッションがタイムアウトしました。もう一度ログインしてください。 unauthenticated: ログインしてください。 inactive: メールアドレス、もしくはパスワードが違います。 sessions: already_signed_out: 既にログアウト済みです。 new: sign_in: ログイン signed_in: ログインしました。 signed_out: ログアウトしました。 flash ※falshメッセージのlocaleファイル。 config/locales/flash.ja.yml ja: flash: new: 作成しました updated: 更新しました failed: 失敗しました destroy: 削除しました login: ログインしました logout: ログアウトしました 参考 [初学者]Railsのi18nによる日本語化対応 Rails 国際化 (i18n) API 【Rails】 I18n入門書
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの標準モジュール「NKF」を使ってみましょうかね。

NKFとは? NKFモジュールとは文字コードを変換することが出来るRubyの標準ライブラリです。 例えば、ひらがなをカタカナに変換したり、全角を半角へ変換するのを簡単に行えます。 使い方 NKF.nkf(オプション、文字列)とする。 使用例 バリデーションでDBに保存する前に、空白を削除したりなどデータを整形するのに利用出来ます。 ※textには、それぞれに対応する文字列(メールアドレスや名前など)が渡ってくる想定です。 require 'nkf' module StringNormalizer extend ActiveSupport::Concern def normalizer_as_email(text) NKF.nkf("-W -w -Z1", text).strip if text end def normalizer_as_name(text) NKF.nkf('-W -w -Z1', text).strip if text end def normalizer_as_furigana(text) NKF.nkf('-W -w -Z1 --katakana', text).strip if text end def normalizer_as_postal_code(text) NKF.nkf("-W -w -Z1", text).strip.gsub(/-/, "") if text end def normalizer_as_phone_number(text) NKF.nkf('-W -w -Z1', text).strip if text end end よく使うオプション一覧 オプション 意味 -h1 カタカナに変換する -h2  ひらがなに変換する -s Shift_JISでの出力する -w UTF-8で入力する -W UTF-8での出力する -Z1 全角空白を半角空白へ変換する -Z2 全角空白を空白2つに変換する 参考 RubyのNKFモジュールの使い方を現役エンジニアが解説【初心者向け】 module NKF
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeでSeleniumを使ったRspecでinvalid session idエラーの解決方法

概要 docker環境でchromeでのブラウザテストを実行した際に今まで通っていたテストが "invalid session id"のエラー発生したので解消方法を記載します。 エラー内容 Selenium::WebDriver::Error::InvalidSessionIdError: invalid session id 原因と解決方法 【原因】 どうやらメモリ不足が原因みたいでした。 【解決方法】 chromeコンテナのメモリを増やすように設定を行う。 → docker-compose.ymlを修正してコンテナで使用するメモリを設定します。 docker-compose.yml chrome: image: selenium/standalone-chrome:latest shm_size: 256m  # ← 追加 ports: - 4444:4444 shmサイズは、Chromeがコンテンツをダウンロードした際の一時ファイル領域のことです。 デフォルトは64mらしいので、多めに設定してみます。 設定後テストが通るようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Herokuデプロイ後のstatus 143エラー

目的 Herokuデプロイ後に出たエラー「status 143」を解決する。 開発環境 macOS: Big Sur Rubyバージョン: 2.6.5 Railsバージョン: 6.0.0 問題 アプリをデプロイして本番環境での確認をしたところ、 We're sorry, but something went wrong.とエラーが出ました。 対策 とりあえず原因を探るべくエラーログを確認。 ターミナル heroku logs --tail --app アプリ名 確認すると、「status 143」というエラー文が出ていました。 これについて調べたところ、何らかの影響で処理が止まっているとのこと。 Herokuの再起動をすれば直る可能性があるとの記事を発見したので、さっそく再起動をかけました。 ターミナル heroku restart しかし、何も変化せず。 エラーログをもう一度よく確認すると、ビューファイルの〜行目で止まってるとの記述がありました。 ビューファイルを確認してみると、アプリ内に置いてある画像を表示させている記述でした。 他にも画像表示させている箇所はあるため、この画像がおかしいと予測し、詳しく見てみるとこの画像のみ拡張子が「jpeg」でした。 調べてみると、どうやらHerokuで「jpeg」は使用できないようで、 拡張子を「jpg」に変換しデプロイしたところ、エラー解決しました! JPEGの拡張子は「jpg」や「jpeg」があるようですが、今回使用した画像は「.jpeg」でした。 ちなみにどちらも同じファイル形式であり、あまり違いは無いようです。 最後に ローカル環境では発生しなかったエラーだったので、最初混乱しましたが、 エラーログをよく見れば解決できる問題であり、いい勉強になりました。 同じ境遇の方の参考になれば幸いです。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでもRubyのStringクラスに翻訳機能をつけてしまった(Rails 7でも対応)

何ができるようになった? 'Maybe I should just write articles on Qiita in English and translate them in Rails console using ActiveTranslateSelf'.translate_to_ja # "英語でQiitaの記事を書いて、ActiveTranslateSelfを使ってRailsのコンソールで翻訳すればいいのかもしれません。" Rubyの文字列をRailsのコンソールの中で訳せるようになっています! 基本的にRailsの中であればどこでも使えます!Callback設置して、Sidekiqで翻訳のジョブ設置してもよし、翻訳ファイルの更新スクリプトの中で使ってもよし! Rails Engineとは? Railsの中にあるRailsのアプリ、ということで言えばいいでしょうか。 今回はRailsのConfigとLoadHookを使いたかったので、Engineとして作りました。 コマンドはいかです $ rails plugin new ActiveTranslateSelf --mountable mountableというオプションがないとRailtieになります。 ハマったところ Engineのコンフィグレーションについていろいろ読んでいて、 何回試してもうまくいかない・・・ということでしたが、 今回はDeepLの設定をInitializerを読み込んでから読み込まないといけないというところで積んでいました。 あとでActiveRecordにもちょっとした機能いれようかと思っているため ActiveSupport.on_load(:active_record) do end で行きました。このあたりはちゃんとRailsの実装を見て勉強しないといけないと思っています。 Rails 7対応かどうか確認しよう! RailsのEdgeだとすでにRails 7のAlphaが使えます! これを以下のようにGemfileに記載すれば、今でも試せます gem 'rails', github: 'rails/rails', branch: 'main' さすがはブランチ名もMainになっていますね。 個人的にすごいといころは、ActiveRecord::Encryptionがすでに使えるということかと思います。 Railsエンジニアの憧れとしてもEdgeで走っているプロジェクト、というのはあるかと思います。 (Github, Shopifyはそれを実践しています) Github Actionsで簡単にいろんなRailsのバージョンに対してテストも走らせて、すごく便利です。 ただしRails 5.2はRuby 3.0非対応なため、そこはもう5.2テスト対象として落とすべきか悩ましいところです。 strategy: matrix: ruby: [2.7] bundler: [default] gemfile: - rails_5.2 - rails_6.0 - rails_6.1 - rails_master このようにStrategyを指定するとテストもラクチンです! 今回のGem https://rubygems.org/gems/active_translate_self ソース https://github.com/sampokuokkanen/ActiveTranslateSelf/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ユーザープロフィール画像機能の実装(Carrierwave)

目的 Railsで作成したアプリにCarrierwaveを用いてユーザープロフィール画像機能を追加する。 開発環境 macOS: Big Sur Rubyバージョン: 2.6.5 Railsバージョン: 6.0.0 前提 アプリを作成済み。 deviseを導入済み。 【Rails】ユーザー管理機能(devise)の導入 MiniMagickを導入済み。 【Rails】画像アップロード機能の導入 手順 Carrierwaveとは 保存画像の設定 Carrierwaveのインストール 画像アップロード用カラムの追加 アップローダークラスの生成 アップローダーのマウント ストロングパラメーターの定義 画像の表示 画像サイズの変更 Carrierwaveとは Carrierwaveとは画像をアップロード機能を簡単に追加してくれるGemの事です。 保存画像の設定 Carrierwaveを使用して投稿される画像は、「public/uploads/モデル名/画像のカラム名/id」以下に溜まっていきます。 今回は画像をGitで管理する必要が無いため、管理下から除外します! .gitignore # .gitignoreへ追加 /public/uploads これでGitで管理しない設定ができました。 Carrierwaveのインストール 次にCarrierwaveをインストールします。 app/Gemfile #省略 gem 'carrierwave' ターミナル % bundle install これでインストールできました! 画像アップロード用カラムの追加 ここで画像アップロード用のカラムを追加します。 このカラムは、「画像のデータ」ではなく「画像のファイル名」を保存するためのものです。 画像データを保存すると、データベースサーバーの要領が圧迫するため、どの画像ファイルか確認するためのファイル名だけを保存します。 ターミナル % rails g migration AddAvatarToUsers avatar:string ターミナル % rails db:migrate 今回カラム名はavatarとしました。 アップローダークラスの生成 アップローダークラスでは、アップロードするファイルの拡張子やサイズ、保存するパスを指定する事が出来ます。 ターミナル % rails g uploader avatar 上記を実行することで,「app/uploader/avatar_uploader.rb」がつくられます。 アップローダーのマウント 次に、「アップロード画像用のカラム」と「アップローダークラス」を紐づける為に、「avatar_uploader」をマウントします。 app/models/user.rb class User < ActiveRecord::Base mount_uploader :avatar, AvatarUploader end これでアバター画像をアップロードする際に、AvatarUploaderクラスの設定を利用出来る様になりました。 ストロングパラメーターの定義 ストロングパラメーターを設定して、画像ファイル名の保存を許可します。 app/controller/users_controller.rb #省略 def update if @user.update(user_params) redirect_to root_path else render :edit end end #省略 private def user_params params.require(:user).permit(:name, :email, :avatar) end deviseを使用しているので、新規登録時にもプロフィール画像が設定できるように、 ストロングパラメーターを定義します。 app/controller/applicarion_controller.rb class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :avatar]) end end これで「新規登録時」と「アカウント編集時」にプロフィール画像が設定できるようになりました! 画像の表示 次にビューファイルの編集です。 まずはユーザー設定画面に下記のように記述します。 .html.erb <div class="field"> <%= f.label :avatar %> <%= f.file_field :avatar %> </div> 次はプロフィール画像の表示です。 .html.erb <% if @user.avatar? %> <%= image_tag @user.avatar.url %> <% else %> <%= image_tag ("noavatar.png") %> <% end %> 画像がある場合は、画像ファイルのURLを取得し表示させ、無い場合はデフォルト画像を表示させます。 画像サイズの変更 最後にavatar_uploader.rbを編集して、MiniMagick経由で画像のリサイズを行えるようにします! app/uploaders/avatar_uploder.rb class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process resize_to_fit: [100, 100] end resize_to_fitは縦横比を維持したまま、縦横を指定したサイズ以内にリサイズするという意味です。 最後に 以上でユーザープロフィール画像機能の実装は完了です。 アプリ制作の際は、画像アップロード機能を導入済みであれば簡単に実装できるので、ぜひ試してみてください。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webpacker::Manifest::MissingEntryErrorの対処法

エラー文を読み込むとwebpacker can't find applicationとあるので導入することに。 % rails webpacker install 無事に解消しました。後にわかったのですがファイル作成時にyarn install のし忘れでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails用語 覚え書き ※自分用

Ruby,Ruby on Railsで知っておいて損はないこと 〜その1〜 学習において、理解の薄い用語などを自分なりにまとめて貼っておきました。 current_userとは何か? 現在ログインしているユーザーの情報を取得できる。 ※Gemのdeviseを導入していると使用できるメソッド ルートパスの設定の仕方はどのように記述する? Prefix    ⇨ root_path URI Pattern ⇨ ’/’    ※トップページを一覧ページ(アクション名:indexの場合)に設定する方法(routes.rbへ記述) root to: ‘コントローラー名#index’ なぜアクション内の変数に@をつけるか? @をつけるとインスタンス変数になる。 コントローラーでアクション名で定義したインスタンス変数はそれぞれのビュー使用できる。 7つのアクションとそれぞれの役割は何か? index   一覧表示 show   詳細表示 new    生成 create   保存 edit    編集 update   更新 destroy   削除 (参考)ルーティング設定時 resourcesで一気に7つのアクション分のルーティング Rubyタグとは? HTMLの中にRubyコードを記述する際に使用するタグ。 <% ~ %> <%= ~ %> ⇨実行した結果を文字列として表示する場合に使用。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】slickを用いた画像スライダーの実装

はじめに railsでアプリケーションに画像スライダーを実装する方法を記述します。 slickを用いた導入方法と簡単な実装のみの記述です。 詳しい内容は公式ページを参照してください。(http://kenwheeler.github.io/slick/) また、初回ページ読み込みかリロードでは正常に作動しますが、ページ遷移して該当ページに戻ってくると動かないという問題にはまって苦戦しましたので、まとめて共有致します。 解釈が正しくない箇所や、もっと上手くできる箇所などがある場合はご指摘いただけると助かります。 railsの学習を始めて3ヶ月の素人による拙い記事ですが、同じくエンジニアを志す初学者の方々の助けになればと思います。 環境 Ruby 2.5.7 Rails 5.2.5 1. 準備 slickはjQueryプラグインの為、jQueryをインストールします。 Gemfile gem "jquery-rails" 記述したら忘れずにbundle installします。 jsファイルに以下を追記します。 assets/javascripts/application.js //= require jquery //= require rails-ujs 準備は以上になります。 2. slickの導入 まずは公式サイトから必要ファイルをダウンロードします。 下の方までスクロールしていくと以下のようなダウンロードリンクがあります。 ダウンロードしたファイルから以下の3つのファイルを下記のディレクトリに格納します。 ・slick.min.js ・slick.scss ・slick-theme.scss ファイル 格納場所 slick.min.js app/assets/javascripts/ slick.scss app/assets/stylesheets/ slick-theme.scss app/assets/stylesheets/ ファイルを格納したらapplication.html.erbに必要な記述をします。 app/views/layouts/application.html.erb <head> <title>Title</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <!--この2行を追加--> <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/> <link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css"/>   <!--ここまで--> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> 書き方は先ほどファイルをダウンロードした公式サイトのダウンロードリンクの下にあります。 3. 実装 準備、導入ができましたら、実装に移ります。 画像スライダーを実装したいhtmlファイルに記述していきます。 例えば、postsテーブルのimageカラムの画像をeachで並べると以下のような記述になります。 html.erb <div class="slider"> <% @posts.each do |post| %> <%= attachment_image_tag post, :image, :fill, 1200, 800, format: "jpeg" %> <% end %> </div> 次にCSSファイルに、スライドのボタン位置を調整するなどの記述をしていきます。 あくまで一例です。 application.css .slider { padding: 0;} .slider img { width: 100%;} .slider div { margin: 0 5px;} .slider .slick-next { right: 17px; z-index: 100;} .slider .slick-prev { left: 23px; z-index: 100;} 次にjsファイルに、動き方などの詳しい内容を設定する記述をしていきます。 assets/javascripts/application.js $(document).ready(function(){ $('.slider').slick({ autoplay: true, //自動再生 infinite: true, //スライドのループ有効化 dots: true, //ドットのナビゲーションを表示 slidesToShow: 4, //表示するスライドの数 slidesToScroll: 4, //スクロールで切り替わるスライドの数 responsive: [{ breakpoint: 768, //ブレークポイントが768px settings: { slidesToShow: 3, //表示するスライドの数 slidesToScroll: 3, //スクロールで切り替わるスライドの数 } }, { breakpoint: 480, //ブレークポイントが480px settings: { slidesToShow: 2, //表示するスライドの数 slidesToScroll: 2, //スクロールで切り替わるスライドの数 } }] }); }); スマートフォンだと画像が小さくなりすぎる為レスポンシブにします。ブレークポイントは768pxと480pxで指定しています。 ここでは全て紹介出来ませんが、痒い所に手が届く細かい設定も色々できますので、是非調べてみて下さい。 実装は以上になります。 ページ遷移での挙動の乱れ 私がなかなか原因を特定できず苦戦した問題が、ヘッダーからのページ遷移でslickが機能しなくなるというものです。 結論から言うと原因は以下でした。 「デフォルトgemであるturbolinks(ajaxとajaxとhistoryAPIを使ってページ遷移しやすくするもの)が動作しているため、$(document).ready()が動かない」 turbolinksとは? 何かリンクをクリックした時にページ全体を読み込むのではなくheadの一部とbodyの中だけを書き換え、読み込みの高速化をしてくれる機能 この為、新しいページが読み込まれてるように見えますが実は同じページのままになっているのでJavaScriptが動かない、ということらしいです。 解決策 Gemfileへの記述を削除しTurbolinksを無効にするなど、解決方法は様々あるようですが、個人的に良いなと思った解決策が以下になります。 assets/javascripts/application.js $(document).on ("turbolinks:load", function(){ (以下略) jsファイルの最初の記述を上記の通り変更すると「turbolinks:load」の記述によってページ遷移時でも正常に動くようになるそうです。 まだまだ勉強中の身の為、コメントやご指摘を頂けると非常にありがたいです。 参考にした記事 【jQuery】カルーセルスライダーslickの使い方とサンプル集 https://www.jungleocean.com/programming/190201jquery-slick 【Rails】ページを更新しないとJavaScriptが動かない時の対処法 https://pote-chil.com/rails_javascript_turbolinks/ slickについて分かりやすくまとめて下さっている投稿 Railsでslickを用いてスライドショーを実装する方法(一枚ずつ & 複数枚ずつ) https://qiita.com/Yusuke_Hoirta/items/309bac88a4f75b58128b 【Rails】slick(スライダー機能)を簡単に導入する方法 https://qiita.com/rised/items/fff7dd86728a21694a29
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存のプロジェクトにBootstrap5を導入する手順

環境 Ruby 2.6.3 Rails 6.0.3 MacOS 11.2.3 自分の環境で動作した手順 https://medium.com/nerd-for-tech/rails-6-bootstrap-5-3492b7c0a626 自分の環境で動作しなかった手順 私の環境で動作しなかっただけかもしれません。 https://dev.to/yarotheslav/install-bootstrap-5-with-ruby-on-rails-6-replace-bootstrap-4-2bf9 https://www.srockstyle.com/develop/programming/ruby/3905/ https://qiita.com/Ekkunlab/items/940be50230390748309b この投稿の意図 他記事を紹介するだけの投稿になってしまいましたが、動作させるために参考する手順の判断が難しく時間がかかってしまったため、同じように悩んでいる方のために投稿させていただきました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rbenv install 2.6.7 を実行しエラーが発生した場合の解決方法

エラー内容 % rbenv install 2.6.7 Downloading openssl-1.1.1k.tar.gz... -> https://dqw8nmjcqpjn7.cloudfront.net/892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 Installing openssl-1.1.1k... Installed openssl-1.1.1k to /Users/Username/.rbenv/versions/2.6.7 Downloading ruby-2.6.7.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.7.tar.bz2 Installing ruby-2.6.7... ruby-build: using readline from homebrew BUILD FAILED (macOS 11.2.1 using ruby-build 20210423) Inspect or clean up the working tree at /var/folders/21/ntlh79kd1yvfz2gwv8n79f4c0000gn/T/ruby-build.20210427115224.72475.T0ZRPY Results logged to /var/folders/21/ntlh79kd1yvfz2gwv8n79f4c0000gn/T/ruby-build.20210427115224.72475.log Last 10 log lines: compiling dmyenc.c vm.c:2489:34: warning: expression does not compute the number of elements in this array; element type is 'const int', not 'VALUE' (aka 'unsigned long') [-Wsizeof-array-div] sizeof(ec->machine.regs) / sizeof(VALUE)); ~~~~~~~~~~~~~~~~ ^ vm.c:2489:34: note: place parentheses around the 'sizeof(VALUE)' expression to silence this warning 3 warnings and 1 error generated. make: *** [vm.o] Error 1 make: *** Waiting for unfinished jobs.... 1 warning generated. 1 warning generated. 解決方法 CFLAGS="-Wno-error=implicit-function-declaration" rbenv install 2.6.7 を実行することで解決できます。 引用:https://github.com/rbenv/ruby-build/issues/1747
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ]Ruby・Rails環境構築

はじめに 完全自分用の記事になります といえども、わりとシンプルにまとめてますので、この通りにすれば、まずこけることはないかなと思います 環境構築 Homebrewのインストール terminal % cd # ホームディレクトリに移動 % pwd # ホームディレクトリにいるかどうか確認 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行 処理に時間がかかる可能性のある操作ですので、慌てずに待つこと パスワードを求められたら自身のPCのパスワードを入れる 以下のようになればOK :terminal % brew -v Homebrew 2.5.1 # 数字は異なる場合があります。 以下のコマンドで最新 terminal % brew update Homebrewの権限を変更する terminal sudo chown -R `whoami`:admin /usr/local/bin Rubyのインストール Rubyの土台となる、rbenvとruby-buildを、Homebrewを用いてインストールします。以下のコマンドを実行。 terminal % brew install rbenv ruby-build rbenvはインストールされた。 PCにおけるどこの場所からも使用できるようにするため、以下のコマンドを実行。 terminal echo 'eval "$(rbenv init -)"' >> ~/.zshrc 設定ファイルであるzshrcを修正したので、以下のコマンドでzshrcを再読み込みし、変更を反映。 terminal % source ~/.zshrc readlineのインストール ターミナルのirb上で日本語入力を可能にする設定を行うために、以下のコマンドでインストール。 terminal % brew install readline rbenvの時と同様に、以下のコマンドでreadlineをどこからでも使用できるようにする。 terminal % brew link readline --force Rubyのインストール terminal % RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" % rbenv install 2.6.5 使用のバージョンを以下で指定 terminal % rbenv global 2.6.5 Rubyのバージョンを指定できたので、以下のコマンドでrbenvを読み込み、変更を反映。 terminal % rbenv rehash ちゃんとインストールされたか確認! terminal % ruby -v MySQLの準備 terminal % brew install mysql@5.6 MySQLは本来であればPC再起動のたびに起動し直す必要があるが、それは面倒であるため、自動で起動するようにしておく。 terminal % mkdir ~/Library/LaunchAgents % ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents % launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist rbenvやreadlineの時と同様に、どこからでもMySQLを操作するためのコマンドmysqlを実行できるようにしておく。 terminal % echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc # mysqlのコマンドを実行できるようにする設定 % source ~/.zshrc # 設定を読み込むコマンド % which mysql # mysqlのコマンドが打てるか確認する # 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysql shade-mini-infoを用意 terminal % brew install shared-mime-info Railsの準備 Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストール terminal % gem install bundler --version='2.1.4' Railsをインストールします。 terminal % gem install rails --version='6.0.0' 一応確認っと、、 terminal % rails -v Rails 6.0.0 # 「Rails」のあとに続く数字は変わる可能性があります Node.jsのインストール Railsを動かすためにはNode.jsが必要となり、それをHomebrewを用いてインストールします。 terminal % brew install node@14 この時、最後にWarning: node@14 14.15.3 is already installed and up-to-dateと表示されても問題はない。 あとはパスを通して完了 terminal % echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc % source ~/.zshrc 一応確認っと。 terminal % node -v v14.15.3 # 数値は異なる場合があります yarnをインストール yarnは、プログラム同士の関係性を管理する役割などで用いられるもので,Node.jsを利用する際に、プログラム同士の関係性を管理する役割などを担う。 terminal % brew install yarn % yarn -v #確認 おわりに 完全自分用でした。 間違い等はご指定くださると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails初学者によるRailsチュートリアル学習記録⑪ 第11章

目次 1. はじめに 2. 第11章の概要 3. 学習内容 4. 終わりに 1. はじめに この記事は、Rails初学者の工業大学三年生がRailsチュートリアルの学習記録をつけるための記事です。 筆者自体がRailsやWebについて知識が少ないので、内容の解釈などに間違いがある可能性があります。(その時はコメントで指摘してくださると助かります!) Railsチュートリアル内ではRailsの内容以外にも、gitでのバージョン管理やHerokuを使ったデプロイも学習しますが、gitに関しては既に私が学習済みのため学習記録には記述しません。 演習の記録も省略します。 2. 第11章の概要 この章では、新規登録したユーザーに対してアカウントを有効化するステップを設けます。 ユーザーのメールアドレスにアカウントの有効化を行うためのリンクを示し、それがクリックされたら アカウントが有効化され、ユーザーがアプリケーションを使用できるようになります。 これにより、メールアドレスの持ち主が登録したかどうかを確認できます。 アカウント有効化機能の実装準備 実装までの流れと処理の流れ コールバックで処理を実行する メイラーを使用する メイラーを追加する 新規登録時にアカウント有効化のメールを送る アカウントを有効化する authenticated?メソッドを改良する editアクションで有効化を行う 有効化していないユーザーがログインできないようにする 3. 学習内容 1. アカウント有効化機能の実装準備 1-1. 処理の流れと実装までの流れ まず、アカウント有効化機能の処理の流れを確認します。 ①ユーザーの初期ステータスを、「有効化されていない」にする ②ユーザー登録が行われた時に、有効化トークンと有効化ダイジェストを生成する ③有効化ダイジェストはデータベースに保存、有効化トークンはメールアドレスと一緒に 有効化用メールのリンクに含める ④ユーザーがメールのリンクをクリックしたら、メールアドレスからユーザーを検索し、 データベース内の有効化ダイジェストとリンク内の有効化トークンを比較して認証する ⑤ユーザーが認証できたら、ユーザーのステータスを「有効化済み」にする 以上の流れで処理が行われていきます。 このトークンとダイジェストを使用した仕組みは、以前に実装したパスワードの認証の仕組みと似ています。 次に実装までの流れを確認します。 ①Userモデルに有効化ダイジェスト、有効化の状態、有効化が行われた日時を格納する属性を追加する ②新規登録時に有効化トークンと有効化ダイジェストを生成する ③有効化トークンとメールアドレスが含まれた有効化用リンクが書かれたメールを送信する ④リンクがクリックされたらステータスを変更し、アプリケーションが使用できるようにする 以上の流れでアカウント有効化機能の実装を行います。 これらの処理はアカウントの有効化という作業を「リソース」としてモデル化して実装します。 モデル化することにより、コントローラとアクションが使用できるようになります。 実装する機能ではデータベースの有効化ステータス属性を変更する処理がメインなので、 本来であればcreateアクションでその処理を実行します。 ですが、今回の場合はメールのリンクをクリックしたときに処理を実行します。 リンクをクリックしたときに発行されるリクエストは、POSTリクエストではなくGETリクエストになってしまうので、 この機能はeditアクションで実行することになります。 まず、アカウント有効化のリソースを作るために以下のコマンドでAccountActivationsコントローラを生成します。 rails generate controller AccountActivations そして、メール内に書くリンクからeditアクションを実行するので、 editアクションへの名前付きルートを以下のコードをroutes.rbに追加します。 resources :account_activations, only: [:edit] これにより、有効化の作業をリソース化できました。 editアクションの定義は後で行います。 次に、Userモデルへの属性の追加を行います。 ここでは、 有効化ダイジェストを格納するactivation_digest 有効化ステータスを格納するactivated 有効化された日時を格納するactivated_at 上記の3つの属性を以下のコマンドで追加します。 rails generate migration add_activation_to_users \ activation_digest:string activated:boolean activated_at:datetime これらの属性を追加したUserモデルは以下のようになります。 属性名 型名 id integer name string email string created_at datetime updated_at datetime password_digest string remember_digest string admin boolean activation_digest string activated boolean activated_at datetime ここまでで、アカウント有効化機能の実装に必要な変更ができました。 1-2. コールバックで処理を実行する ユーザーが新規登録を完了するにはアカウントの有効化が必要になるので、 オブジェクトが作成される前に、有効化トークンと有効化ダイジェストを生成して、 それを作成されたオブジェクトに渡さなければいけません。 このような、○○をする前に特定の処理を行うという際に使用するのがコールバックです。 以前、ユーザーをデータベースに保存する前に、メールアドレスを小文字に変換するという処理を実装しました。 そこでもbefore_saveコールバックというメソッドが使用されています。 ここでは、オブジェクトが作成される前に処理を実行したいので、 その時に使用するコールバックはbefore_createコールバックとなります。 今回はcreate_activation_digestというメソッドで、 有効化トークンと有効化ダイジェストを生成することになるので、コードは以下のようになります。 app/models/user.rb # アクセスしようとした URL を覚えておく class User < ApplicationRecord attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest validates :name, presence: true, length: { maximum: 50 } private # 有効化トークンとダイジェストを作成および代入する def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end 2. メイラーを使用する 2-1. メイラーを追加する メイラーとは、アプリケーションからメールを送信するために使用する機能で、 動作はコントローラと似ています。 メイラーはコントローラ内で、送信先を設定したり、インスタンス変数を定義して、 メールの内容を定義するメイラービューで使用することができます。 メイラービューは1つのメソッドに対して2つ生成されます。 1つはテキストメール用のビューで、もう1つはHTMLメール用のビューです。 メイラーの生成には以下のコマンドを実行します。 rails generate mailer UserMailer account_activation password_reset このコマンドにより、アカウントの有効化用のメールを送信するaccount_activationメソッドと、 12章で使用する、パスワードの再設定用のメールを送信するpassword_resetの2つのメソッドを持った、 UserMailerが生成されました。 以下にaccount_activationメソッドの内容と、それに対応するメイラービュー(テキストメール用)を示します。 app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def account_activation(user) @user = user mail to: user.email, subject: "Account activation" end end app/views/user_mailer/account_activation.text.erb Hi <%= @user.name %>, Welcome to the Sample App! Click on the link below to activate your account: <%= edit_account_activation_url(@user.activation_token, email: @user.email) %> 1つ目のコードが、メールを送信するaccount_activationメソッドです。 ユーザーを引数にとり、そのメールアドレスを送信先に設定、subject: "Account activation"は メールの件名を設定しています。 メイラービューの重要な部分は、 <%= edit_account_activation_url(@user.activation_token, email: @user.email) %> この埋め込みRubyで書かれた部分です。 まず、edit_account_activation_urlはaccount_activationコントローラのeditアクションへの名前付きルートです。 そして、第一引数に有効化トークン、第二引数にメールアドレスを渡しています。 なぜ、このように書くかというと、このリンクに有効化トークンとメールアドレスを含めるためです。 例えばユーザーの編集ページのリンクのコードは edit_user_url(user)というように書きます。 このコードから生成されるリンクはhttps://www.example.com/users/<userのid>/editです。 usersコントローラを表す部分とeditアクションを表す部分の間に、引数に渡したユーザーのidが入り、 paramsハッシュでparams[:id]で参照できます。 これと同じ様に今回のコードでは引数に有効化トークンに加えてemail: @user.emailというハッシュがあるので、 https://www.example.com/account_activations/<有効化トークン>/edit?email=<メールアドレス> このようにコントローラとアクションの間に有効化トークンが入り、URLの末尾にクエリパラメータとしてメールアドレスが入ります。 editアクションでは生成されたリンクの内の有効化トークンとメールアドレスを、 params[:id], params[:email]で参照できます。 2-2. 新規登録時にアカウント有効化のメールを送る これでメールを送信する準備が整いました。 ユーザー登録を行うcreateアクションに、メールを送信する処理を加える変更を行います。 下に変更前のcreateアクションと変更後のcreateアクションを載せます。 変更前のcreateアクション def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end 変更後のcreateアクション def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now # @userにメールを送信 flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end 変更内容は、ログイン処理を行わずにメールを送っている点とそれに伴うメッセージの変更。 そして、リダイレクト先の変更の3つです。 なぜログイン処理を行わないかというと、ユーザー登録を完了してもアカウントの有効化を行うまで ログインはできないようにしたいからです。 そして、元のリダイレクト先として設定されていたプロフィールページはログインしないと見れないページなので、 ここにリダイレクトさせる意味がなくなりその代わりとしてルートURLに変更しました。 3. アカウントを有効化する 3-1. authenticated?メソッドを改良する ログイン状態の記憶の際に記憶トークンと記憶ダイジェストを比較するauthenticated?メソッドを、 有効化トークンと有効化ダイジェストを比較する処理にも流用します。 現段階で定義されているauthenticated?メソッドは以下のコードです。 authenticated?メソッド # トークンがダイジェストと一致したら true を返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end このコードでは、remember_digestとremember_tokenを比較しています。 今回のアカウントの有効化ではactivation_digestとactivation_tokenを比較したいのですが、 authenticated?メソッド内のrememberの部分を変数として扱えると有効化の処理でもこのメソッドが使用できるようになります。 ここで使用するのがsendメソッドです。 sendメソッドは引数に、シンボルや文字列でメソッド名を渡すとメソッドが実行されます。 例:sendメソッドで配列の長さを取得する >> a = [1, 2, 3] >> a.length => 3 >> a.send(:length) => 3 >> a.send("length") => 3 上記の例ではsendメソッドでメソッドを実行していますが、sendメソッドではuser.send("attribute_digest") のように書くと属性の値を呼び出すこともできます。 そして、この引数に渡しているのは文字列なので、変数を含められます。 user.send("#{attribute}_digest")のように変数を含めて実行できるように変更を加えたauthenticated?メソッドが以下です。 改良後のauthenticated?メソッド # トークンがダイジェストと一致したら true を返す def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end 元々1つだった引数が2つになり、第一引数に属性名を渡すことで、authenticated?メソッドが 有効化の処理にも流用できるようになりました。 3-2. editアクションで有効化を行う ここからは、先ほど改良したauthenticated?メソッドを使用して有効化を行えるようにします。 editアクションで、paramsハッシュからメールアドレスを受け取りユーザーを検索して有効化します。 以下のコードが有効化を行うeditアクションです。 app/controllers/account_activations_controller.rb def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end このアクションは、メール内のリンクがクリックされた時に実行されます。 まず、リンク内のメールアドレスからユーザーを検索しています。 その次の条件分岐では、リンク内の有効化トークンと、データベース内の有効化ダイジェストが一致しているかつ、 まだ有効化されていないユーザーに対して有効化処理を行います。 なぜ、まだ有効化されていないという条件を組み込んでいるかというと、 有効化処理の後にログイン処理を行っているからです。 もし、有効化されているユーザーでも有効化処理を行えるようにすると、 送信されたメールやメール内のリンクが盗み出された時に、第三者にログインされてしまいます。 それを防ぐためにここでは2つの条件式を利用しています。 条件式を満たしたユーザーは、update_attributeメソッドで、有効化ステータスがtrueになり、 この処理を行った日時が記録されます。 3-3. 有効化していないユーザーがログインできないようにする ここまでで、新規登録したユーザーに対して、メールを送りってリンクがクリックされたら 有効化処理を行うという一連の流れが実装できました。 最後に既存のログイン機能に変更を加えます。 新しく追加された機能をログイン処理に対しても適用するために、 アカウントが有効化されていない場合はログインを許可しないという処理を加えていきます。 以下に変更前のログイン処理と変更後のログイン処理を載せます。 変更前のログイン処理 def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end 変更前のログイン処理 def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) if user.activated? log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else message = "Account not activated. " message += "Check your email for the activation link." flash[:warning] = message redirect_to root_url end else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end 変更部分としては、ユーザーのパスワードの認証が終わった後にif user.activated?という条件式を加えている部分です。 ここで現在の有効化ステータスを判定して、有効化されていた場合は変更前のコードと同じくログイン処理を実行します。 一方、有効化が行われていなかった場合はルートURLにリダイレクトさせてフラッシュメッセージで、 「Account not activated. Check your email for the activation link.」 と表示します。 このように有効化されていないユーザーのログインを拒否する処理は、 user.activated?メソッドで有効化ステータスを判定することで実装できます。 4. 終わりに 終わりにを書く前に一度下書き保存したら、なぜか記事の投稿が終わったと勘違いして この記事の投稿前にRailsチュートリアルの12章を進めておりました。 前回の記事との時間が空いてしまいましたが、既に12章が終わりそうなところまで進められているので、この次の記事はすぐに投稿できるように頑張ります:) この章ではアカウントの有効化の機能を追加しました。 今までいろんなアプリケーションでアカウント有効化を実際に利用していましたが、 なぜこの過程が必要なのかあまり分からないままでいました。 ですが、この章でこの過程の必要性とその機能がどのように実装されているのかが理解できました。 もうすでに手を付けている12章では、ここで実装したメイラーをまた使用してパスワードの再設定機能を実装していってます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gem pg をダウンロードしてバグを直したい

現場railsの3章でbin/rails db:createをしようとした時です。 嬉しいことに、エラーにぶつかりました。どうやらgem pgがインストールされていないようです。 An error occurred while installing pg (0.19.0), and Bundler cannot continue. Make sure that `gem install pg -v '0.19.0' --source 'https://rubygems.org/'` succeeds before bundling. 以下のコードを記入しても治らず $ gem install pg Building native extensions. This could take a while... ERROR: Error installing pg: ERROR: Failed to build gem native extension. さらに An error occurred while installing pg (1.2.3), and Bundler cannot continue. Make sure that `gem install pg -v '1.2.3' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: pg 解決につながった記事および方法 こちらの記事を参考にして、以下のコマンドを入力したら治りました!libpq-devがないとpostgreサーバーにアクセスができないみたいですね。 https://qiita.com/Dexctersu/items/b2171f90f5bb82cf0888 $sudo apt install libpq-dev $gem install pg -v 1.2.2 Building native extensions. This could take a while... Successfully installed pg-1.2.2 1 gem installed sudoコマンドを使うことで、root権限を持つユーザーの処理を代わりに行うことができるみたいです。 (引用「【 sudo 】コマンド――スーパーユーザー(rootユーザー)の権限でコマンドを実行する」https://www.atmarkit.co.jp/ait/articles/1611/28/news036.html) aptコマンドはパッケージ管理に使用するコマンドみたいですね。 (引用「aptコマンドチートシート」https://qiita.com/SUZUKI_Masaya/items/1fd9489e631c78e5b007) こちらにも解決方法が載っていましたが、いまいちvimの使い方が分からないので、宿題として持ち帰りますね。 https://qiita.com/miriwo/items/67681165b0a89218ee61 $ echo export PATH='/usr/local/bin:$PATH' >> ~/.bash_profile $ source ~/.bash_profile 参考文献 解決につながった記事はこちら↓ https://qiita.com/Dexctersu/items/b2171f90f5bb82cf0888 「【 sudo 】コマンド――スーパーユーザー(rootユーザー)の権限でコマンドを実行する」https://www.atmarkit.co.jp/ait/articles/1611/28/news036.html 「aptコマンドチートシート」https://qiita.com/SUZUKI_Masaya/items/1fd9489e631c78e5b007
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsアプリのAPM

APMってなに? アプリケーションにとってパフォーマンスは重要な数値で、パフォーマンスが悪ければ、サービスの離脱率にも影響があります。APMとはアプリケーションのパフォーマンスを測定することで、処理が遅い部分や、想定外の動作による速度低下を発見し、修正することです 測定ツール Railsでよく利用されるGemとして、「Skylight」や「rack-mini-profiler」があります。 今回は、ローカル環境でも動かしたかったので、後者を選択しました。 めちゃくちゃ簡単です。 公式ドキュメント Skylight MiniProfiler 開発環境 macOS Big Sur 11.3.1 ruby 2.6.5 rails 6.0.0 bundler 2.2.11 実装 基本的には公式ドキュメント通りです。 ①Gemをdevelopment環境に追加 Gemfile gem 'rack-mini-profiler', require: false 追加後は忘れずにbundle install ②必要なファイルを作成するために、ターミナルで以下のコマンドを実行 ターミナル user@macmini app_name % bin/rails g rack_profiler:install Running via Spring preloader in process 56493 create config/initializers/rack_profiler.rb ③rails surverを起動して動かすと、画面左上にプロファイルが表示されるようになります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails Tutorial】HerokuにデプロイしたらApplication error[H10 (App crashed)]が発生したときの対処法

はじめに Ruby on Rails チュートリアルに沿って学習を進めていました。 エラーが発生した際の対処方法をメモしておく。 環境 Cloud9 Ruby 2.6.3 Rails 6.0.3 現象 1.5.2にて、HerokuへRailsアプリケーションをデプロイしてページを開こうとすると、エラー画面が表示された。 結論 database.ymlを下記のように書き換えた config/database.yml # SQLite. Versions 3.8.0 and up are supported. # gem install sqlite3 # # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' # default: &default adapter: sqlite3 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: db/test.sqlite3 production: <<: *default adapter: postgresql #追記 database: db/production.postgresql #修正後 # database: db/production.sqlite3 #修正前 解決までの流れ ログを確認する エラー画面にログを見ろと書かれていたので、確認してみることに。 $ heroku logs -t 結果はこちら ・・・ 2021-06-04T19:28:08.413724+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=infinite-earth-10732.herokuapp.com request_id=07713d54-3627-4199-8471-a0611bacfb48 fwd="36.2.154.134" dyno= connect= service= status=503 bytes= protocol=https 2021-06-04T19:28:08.683679+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=infinite-earth-10732.herokuapp.com request_id=ae2b39f4-9b4d-44c3-a769-3b37dd15dc51 fwd="36.2.154.134" dyno= connect= service= status=503 bytes= protocol=https error code=H10, App crashedと言われた。 よくわからないのでググってみると、エラーの詳細内容を確かめるためにHeroku上でコンソールを開くことが出来るとのこと。 これも試してみます。 $ heroku run rails c 結果はこちら ・・・ /app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/rubygems_integration.rb:408:in `block (2 levels) in replace_gem': Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? sqlite3 is not part of the bundle. Add it to your Gemfile. (LoadError) どうやらsqlite3まわりが原因のようだ。 引き続きネットで情報を検索すると、以下の情報を発見。 https://tex2e.github.io/blog/ruby/rails-on-heroku これやん!!!ってことで、database.ymlを修正しました。 原因 Ruby on Rails チュートリアルでは、Herokuで使うデータベースとの競合を防ぐため、 production環境では、sqlite3ではなく、PostgreSQLを使用する。 Gemfileではそのように指定したが、database.ymlではsplite3を使用するようになっていた。 その不一致が原因ではないかと考えています。 (詳細はチュートリアルの1.3.1や1.5.1を参考に) どこかでチュートリアルの手順を間違えてしまっていたのかもしれませんが、 無事エラーが解決されて満足です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

差分で出力を変える

問題 正の整数を入力します。その整数が、 10の倍数(10,20,30...)からの差が2以内であるときはTrue それ以外はFalse と出力するメソッドを作りましょう。 出力例: near_ten(12)→True near_ten(17)→False near_ten(19)→True  ○○.rb def near_ten(num) quotient = num % 10 if quotient <= 2 || quotient >= 8 puts "True" else puts "False" end end 解説 「10の倍数からの差」を考えるためには、一の位の値に着目します。すなわち、 一の位が「0,1,2,8,9」のどれかであれば「10の倍数からの差が2以内」と判断することができます。 したがって、変数quotientに一の位の値を代入し、quotient <= 2 || quotient >= 8で「0,1,2,8,9」 のどれかに当てはまるかどうかを確認しています。 終わり 今日はオリジナルアプリの作成が捗らず、rubyの復習をかねて練習問題を行いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む