質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby on Rails 7

Ruby on Rails 7は、2021年12月に正式リリースされました。Ruby on Railsのバージョン7であり、フロントエンド開発環境を大幅に刷新。Node.jsを用いない構成がデフォルトになっています。

Q&A

0回答

227閲覧

同じsession_tokenが生成されてるはずなのに比較してもfalseが返ってくるエラー

sdka

総合スコア2

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby on Rails 7

Ruby on Rails 7は、2021年12月に正式リリースされました。Ruby on Railsのバージョン7であり、フロントエンド開発環境を大幅に刷新。Node.jsを用いない構成がデフォルトになっています。

1グッド

1クリップ

投稿2024/02/22 04:39

実現したいこと

session_tokenを比較してtrueを返す様にしてテストを通す様にしたい。

発生している問題・分からないこと

railsチュートリアルの10章を進めている最中のテストコードで

ruby

1 test 'successful edit' do 2 log_in_as(@user) 3 get edit_user_path(@user) 4 patch user_path(@user), 5 params: { user: { name: 'Foo Bar', email: 'foo@bar.com', password: 'password',password_confirmation: 'password' } } 6 assert_redirected_to @user 7 @user.reload 8 9 assert_equal 'Foo Bar', @user.name 10 assert_equal 'foo@bar.com', @user.email 11 end 12end

というのがあったのですがassert_redirected_to @userの箇所でshowページが返ってくるのを期待しているのにloginページが返ってきてしまいます。
その原因を自分でbyebugで調査した所途中処理のsession[:session_token] == user.session_tokenの内容が一致していないのが原因だと思いました。
理由としましてはsessions_controller.rbにbyebugを挟んでsession[:session_token]とuser.session_tokenの内容を確認したら内容が食い違っているせいで@current_userが空になっていて下のsession _helper.rbのlogged_in?のcurrent_user.present?がfalseになっておりusers_contoller.rbのeditアクション前で発火するlogged_in_userがfalseになりlogin_urlに返ってしまっていると考えました。
一連のソースコードを書かせて頂きます。(コードブロックの数の制限上ルーティングや一部コードは省かせていただいてます。)

エラーメッセージ

error

1root@34c7bd0bbb60:/app# rails test test/integration/users_edit_test.rb:40 2Running 3 tests in a single process (parallelization threshold is 50) 3Started with run options --seed 52443 4 5 FAIL UsersEditTest#test_successful_edit (0.54s) 6 Expected response to be a redirect to <http://www.example.com/users/762146111> but was a redirect to <http://www.example.com/login>. 7 Expected "http://www.example.com/users/762146111" to be === "http://www.example.com/login". 8 test/integration/users_edit_test.rb:45:in `block in <class:UsersEditTest>' 9 10 3/3: [=============================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00 11 12Finished in 0.54173s 131 tests, 2 assertions, 1 failures, 0 errors, 0 skips

該当のソースコード

user.yml

1michael: 2 name: Michael Example 3 email: michael@example.com 4 password_digest: <%= User.digest('password') %> 5 admin: true 6 7archer: 8 name: Sterling Archer 9 email: duchess@example.gov 10 password_digest: <%= User.digest('password') %> 11 12lana: 13 name: Lana Kane 14 email: hands@example.gov 15 password_digest: <%= User.digest('password') %> 16 17malory: 18 name: Malory Archer 19 email: boss@example.gov 20 password_digest: <%= User.digest('password') %> 21 22<% 30.times do |n| %> 23user_<%= n %>: 24 name: <%= "User #{n}" %> 25 email: <%= "user-#{n}@example.com" %> 26 password_digest: <%= User.digest('password') %> 27<% end %>

users_edit_test.rb

1def setup 2 @user = users(:michael) 3end 4 5test 'successful edit' do 6 log_in_as(@user) 7 get edit_user_path(@user) 8 patch user_path(@user), 9 params: { user: { name: 'Foo Bar', email: 'foo@bar.com', password: 'password',password_confirmation: 'password' } } 10 assert_redirected_to @user 11 @user.reload 12 13 assert_equal 'Foo Bar', @user.name 14 assert_equal 'foo@bar.com', @user.email 15 end

test_helper.rb

1module ActionDispatch 2 class IntegrationTest 3 4 def log_in_as(user, password: 'password', remember_me: '1') 5 post login_path, params: { session: { email: user.email, 6 password: password, 7 remember_me: remember_me } } 8 end 9 end 10end

sessions_controller.rb

1def create 2 user = find_user_by_email 3 if authenticated?(user) 4 perform_login(user) 5 else 6 handle_failed_authentication 7 end 8 end 9 10 private 11 12def find_user_by_email 13 User.find_by(email: params[:session][:email].downcase) 14end 15 16def authenticated?(user) 17 user&.authenticate(params[:session][:password]) 18end 19 20def perform_login(user) 21 forwarding_url = session[:forwarding_url] 22 reset_session 23 manage_remember_me(user) 24 log_in user 25 redirect_to forwarding_url || user 26end 27 28def manage_remember_me(user) 29 params[:session][:remember_me] == '1' ? remember(user) : forget(user) 30end 31 32def log_in(user) 33 session[:user_id] = user.id 34#ここで一回目のuser.session_tokenが入る 35 session[:session_token] = user.session_token 36 end 37 38def current_user 39 if (user_id = session[:user_id]) 40 user = User.find_by(id: user_id) 41 @current_user = user if user && session[:session_token] == user.session_token 42 #ここにbyebugを挟んでsession[:session_token]とuser.session_tokenの内容を確認したら内容が食い違っている。 43 そのせいで@current_userが空になっていて下のsession _helper.rbのlogged_in?のcurrent_user.present?がfalseになっておりusers_contoller.rbのeditアクション前で発火するlogged_in_userがfalseになりlogin_urlに返ってしまっている。 44 elsif (user_id = cookies.encrypted[:user_id]) 45 user = User.find_by(id: user_id) 46 if user&.authenticated?(cookies[:remember_token]) 47 log_in user 48 @current_user = user 49 end 50 end 51 end 52 53 def current_user?(user) 54 user && user == current_user 55 end

users_controller.rb

1before_action :logged_in_user, only: %i[index edit update destroy] 2before_action :correct_user, only: %i[edit update] 3 4def edit 5 @user = User.find(params[:id]) 6end 7 8def update 9 if @user.update(user_params) 10 flash[:success] = 'Profile updated' 11 redirect_to @user 12 else 13 render 'edit', status: :unprocessable_entity 14 end 15 end 16 17def logged_in_user 18 #ここでfalseが返ってしまいログインでコケてしまい編集ページに飛べずテストが失敗している。 19 return if logged_in? 20 store_location 21 flash[:danger] = 'Please log in.' 22 redirect_to login_url, status: :see_other 23 end 24 25 def correct_user 26 @user = User.find(params[:id]) 27 redirect_to(root_url, status: :see_other) unless current_user?(@user) 28 end 29 30(session _helper.rb) 31def logged_in? 32 current_user.present? 33end

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

・config/initializers/session_store.rb ファイルにて、セッションのキー、有効期限、セキュリティ設定などをしているらしいのでここで極端に有効期限が短くなっているのかと思ったがそもそも対象のファイルがなかった。
・config/environments/development.rb、config/environments/production.rbに記述されている可能性があるらしいがそれらしい記述もなかった。

補足

特になし

shinoharat👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shinoharat

2024/02/22 05:27

user.session_token の実装が知りたいのですが、これは自前で実装したメソッドでしょうか? それとも何かの gem を利用されていますか? 自前のものならコードを追記して欲しいです。 gem なら gem の名前を教えてください。
sdka

2024/02/22 06:23 編集

閲覧ありがとうございます! こちらsession_tokenとそれに関係するメソッドです。 def session_token remember_digest || remember end def remember self.remember_token = User.new_token update(remember_digest: User.digest(remember_token)) remember_digest end def self.new_token SecureRandom.urlsafe_base64 end def self.digest(string) cost = if ActiveModel::SecurePassword.min_cost BCrypt::Engine::MIN_COST else BCrypt::Engine.cost end BCrypt::Password.create(string, cost:) end shinoharatさんのコメントで気づいたのですがここでDB側にSecureRandom.urlsafe_base64を使ってトークン生成と保存をしていたので毎回合致しなかったのでしょうか・・・?
shinoharat

2024/02/27 08:01

追記ありがとうございます。返信が遅れて申し訳ないです。 ざっと見た感じ問題なさそうに見えますね・・・。 -- > shinoharatさんのコメントで気づいたのですがここでDB側にSecureRandom.urlsafe_base64を使ってトークン生成と保存をしていたので毎回合致しなかったのでしょうか・・・? いえ、「remember_digest が無い場合のみ新たにトークンを生成する」というコードになっているので、トークン生成は初回の1度のみのはずです。 -- 念のため、「session_token」を複数回呼び出したときに毎回同じ値を返すかどうかテストしていただけませんか? // コンソールの起動 $ bin/rails console // テスト用ユーザの新規作成 irb(main)> user = User.create(name: "foo", email: "foo@example.com", password: "pass", password_confirmation: "pass") // remember_digest の初期値を確認。ここでは nil のはず。 irb(main)> user.remember_digest // 何度実行しても同じ値が返るはず。 irb(main)> user.session_token irb(main)> user.session_token irb(main)> user.session_token // リロードしてもさっきと同じ値が返るはず。 irb(main)> User.find(user.id).session_token
sdka

2024/02/27 12:29 編集

追記ありがとうございます。 こちらテスト結果となります。 ========== root@7adbc2f81978:/app# bin/rails console Loading development environment (Rails 7.0.8) irb(main):001> user = User.create(name: "foo", email: "foo@example.com", password: "pass", password_confirmation: "pass") TRANSACTION (0.3ms) BEGIN User Exists? (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "foo@example.com"], ["LIMIT", 1]] TRANSACTION (0.3ms) ROLLBACK => #<User:0x00007f4f13f7c380 ... irb(main):002> user.remember_digest => nil irb(main):003> user.session_token TRANSACTION (0.3ms) BEGIN User Exists? (1.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "foo@example.com"], ["LIMIT", 1]] TRANSACTION (0.5ms) ROLLBACK => "$2a$12$6lJWHTrS1rGertcriANko.bOBcURPvLnTwEu7b3RVKxG0J/ty8q.m" irb(main):004> user.session_token => "$2a$12$6lJWHTrS1rGertcriANko.bOBcURPvLnTwEu7b3RVKxG0J/ty8q.m" irb(main):005> user.session_token => "$2a$12$6lJWHTrS1rGertcriANko.bOBcURPvLnTwEu7b3RVKxG0J/ty8q.m" irb(main):006> User.find(user.id).session_token /usr/local/bundle/gems/activerecord-7.0.8/lib/active_record/relation/finder_methods.rb:455:in `find_with_ids': Couldn't find User without an ID (ActiveRecord::RecordNotFound) ========== 最後のみ指定したIDのレコードがデータベースに存在しないエラーが出てますね・・・。
shinoharat

2024/02/28 00:46

1行目の User.create の時点で失敗しているようです。 何かのバリデーションに引っかかってるのかな?と思います。(パスワードに数字が無いとダメとか、emailが重複してるとか) お手数ですが、 User.create(...) の引数部分を調整して再テストしてみてください。
shinoharat

2024/02/28 00:48 編集

create 時に TRANSACTION (0.3ms) ROLLBACK とログに出たら登録に失敗しています。目安にしてください。
sdka

2024/02/29 01:07

ありがとうございます! 試しにrails cを行ったのちuser = User.create(name: "foo", email: "foo@example.com", password: "pass", password_confirmation: "pass") と if user.errors.any? puts user.errors.full_messages end をした所Password is too short (minimum is 6 characters)と怒られていました。 そこで別の疑問を見つけたのですがこちらのスキーマファイルを見てください。 ========== ActiveRecord::Schema[7.0].define(version: 20_240_217_112_854) do # These are extensions that must be enabled in order to support this database enable_extension 'plpgsql' create_table 'users', force: :cascade do |t| t.string 'name' t.string 'email' t.datetime 'created_at', null: false t.datetime 'updated_at', null: false t.string 'password_digest' t.boolean 'admin', default: false t.string 'remember_digest' t.index ['email'], name: 'index_users_on_email', unique: true end end ========== railsチュートリアルではpasswordカラムが用意されていないのです。 今回は単純にテスト時のpasswordの文字数が足りなかっただけですが、文字数を増やしてバリデーションを通過した場合フォームから送られてきたpasswordキーを持つレコードは通常どの様に登録されるのでしょうか。 話をずらしてしまい申し訳ありません。 それとこちら回答のソースコードです。(恥ずかしい話自分で見てもわかりませんでしたので教えてくださると助かります。) https://github.com/yasslab/sample_apps/tree/main/7_0/ch10
shinoharat

2024/02/29 02:49

パスワードは暗号化された上で「password_digest」カラムに保存されます。 「password」は「password_digest」を作るための一時的な変数みたいなもので、DBには保存されません。 生のパスワードをそのままDBに保存するのはセキュリティ上危険なので、こういう作りになっています。 参考URL: Rails ガイド - SecurePasswordモジュール https://railsguides.jp/active_model_basics.html#securepassword%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB
sdka

2024/02/29 03:17

丁寧な解説ありがとうございます。理解できました。 ただDBの保存が出来ない問題がまだ続いていまして、例えば ====== def self.digest(string) cost = if ActiveModel::SecurePassword.min_cost BCrypt::Engine::MIN_COST else BCrypt::Engine.cost end BCrypt::Password.create(string, cost:) end def self.new_token SecureRandom.urlsafe_base64 end def remember self.remember_token = User.new_token update(remember_digest: User.digest(remember_token)) remember_digest end ====== というコードでも同じくDBの保存に手間取っていまして update(remember_digest: User.digest(remember_token))の箇所にbyebugを挟んでremember_tokenの内容(tlxssKISNKaK-d6XI6m0SA)を確認した後、 update!(remember_digest: User.digest(remember_token)).errors.full_messagesを実行すると 何故か ========== *** ActiveRecord::RecordInvalid Exception: Validation failed: Password can't be blank, Password is too short (minimum is 6 characters) ========== と何故かパスワードで注意されてしまいます。 これは何故だかわかりませんでしょうか・・・。 (最初の質問と段々ずれている自覚はありますので別スレッドを立てた方が良い場合はそうさせてもらいます。)
shinoharat

2024/02/29 04:15

パスワードの検証が常にONになっているのが原因ではないかと思います。 先ほど解説した通り、password は password_digest を作るための一時的な入れ物です。 なので、パスワード登録済みのユーザーでも、 user.password が nil になる場合があります。 パスワードの validates に allow_nil: true オプションを加えることで改善できると思います。 以下のリンク先の回答欄に詳細な解説を載せていますので、またご確認ください。 https://teratail.com/questions/325751#reply-451535
sdka

2024/03/01 05:07 編集

リンクの添付までありがとうございます。とても参考になりました。 最後に質問なのですがallow_nil: trueをmodelファイルのpasswordに追加してしまうと新規の入力フォームからのpasswordデータが空の場合もバリテーションがすり抜けてしまうのではと思いました。 これは別の所で補っているのでしょうか?
shinoharat

2024/03/01 05:44

入力フォームのpasswordデータが空の場合は、 { users: { password: "", password_confirmation: "" } } のようなパラメータが送信されます。 password の値が「nil」ではなく「空文字」なので、正常に validation が働いてくれます。 ちなみに、nil と空文字を区別する点に「コードのニオイ」を感じる場合は、代わりに on オプションを使っても OK です。 コード量は若干増えますが、分かりやすさは増すと思います。 https://railsguides.jp/active_record_validations.html#on
sdka

2024/03/01 05:56

ありがとうございます!一通り疑問点も解決しました! ベストアンサーに選びたいので簡単にまとめたものを回答欄に投稿していただくかそれが面倒なら適当なものを解答欄に打ち込んでもらえればBAにさせて頂きます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問