[ Hartl ] - test, reset hasła, reset_token i reset_digest

Witam, właśnie przerabiam serię screencastów Hartla. Mam problem ze zrozumieniem jednego fragmentu kodu - chodzi o reset hasła, w sytuacji, gdy go nie pamiętamy i chcemy zmienić na nowe. Najpierw udostępniam kod, niżej opiszę mój problem:

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'
# Invalid email
post password_resets_path, password_reset: { email: "" }
assert_not flash.empty?
assert_template 'password_resets/new'
# Valid email
post password_resets_path, 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
# Password reset form
user = assigns(:user)
# Valid password & confirmation
patch password_reset_path(user.reset_token),
email: user.email,
user: { password: "foobaz",
password_confirmation: "foobaz" }
end
end

Akcja create z PasswordResets Controller:

	def create
         @user = User.find_by(email: params[:password_reset][:email].downcase)
	if @user
	@user.create_reset_digest
	[...]
	end
	end

Metoda create_reset_digest:

def create_reset_digest
self.reset_token = User.new_token
[...]
end

No i w końcu mój problem. Hartl mówi, że musimy użyć metody assigns, i wyciągnąć usera z controllera, bo tylko ten właśnie @user - z metody create - będzie mieć reset_token, ponieważ ta zmienna jest tworzona w metodzie create_reset_digest - to atrybut wirtualny, nie istnieje w bazie (jest w niej tylko reset_digest). Nie rozumiem w takim razie, czemu nasz użytkownik pobrany z fixtury do testów, wykorzystywany w teście zanim użyliśmy assign - działa? Czemu linijka assert_not_equal @user.reset_digest, @user.reload.reset_digest działa? Jestem osobą początkującą i rozumiem to tak, że wzięliśmy naszego użytkownika - michaela z fixtury i zrobiliśmy post do password_resets_path z prawidłowym mailem, co wywołało akcję create, w której jest właśnie metoda create_reset_digest, dzięki temu nasz michael dostał zarówno token, jak i digest. Użyliśmy więc assert_equal_not, żeby sprawdzić, czy reset_digest, który był do tej pory nilem, dostał jakąś wartość. W takim razie, nasz michael miałby też token. I gdy już wydawało mi się, żę wszystko rozumiem - Hartl mówi, że musimy użyć assigns, bo nasz użytkownik nie ma reset_token. Gdzie popełniam błąd w rozumowaniu?

Udało mi się skontaktować z autorem książki, Michaelem Hartlem. Oto jego odpowiedź:
“It’s been a while since I looked at that code, but I think what’s going on is that @user has a reset_digest but not a reset_token. The post to the password resets path changes the @user variable in the create method, but that’s not the same variable as the one in the test. That’s why the call to assigns is necessary: the @user in the test doesn’t have the virtual reset_token attribute, but the one in the controller does.”
Nadal nie rozumiem jednak tego, czemu reset_digest naszej zmiennej z testów się zmienia, skoro to nie ona jest zmieniana przez akcję create.

Odpowiedź Autora sporo wyjaśnia. Tak samo fragment rozdziału 10.1.4 (railstutorial.org):

Listing 10.31 also uses the assigns method for the first time in the main tutorial; as explained in the Chapter 8 exercise (Section 8.6), assigns lets us access instance variables in the corresponding action. For example, the Users controller’s create action defines an @user variable (Listing 10.21), so we can access it in the test using assigns(:user).

i 8.4.6:

After logging in, we can check if the user has been remembered by looking for the remember_token key in the cookies.
Ideally, we would check that the cookie’s value is equal to the user’s
remember token, but as currently designed there’s no way for the test to
get access to it: the user variable in the controller has a remember token attribute, but (because remember_token is virtual) the @user variable in the test doesn’t. Fixing this minor blemish is left as an exercise (Section 8.6), but for now we can just test to see if the relevant cookie is nil or not.

Jak ja to rozumiem:

  • wcześniej test z @user działa ponieważ @user.reset_digest (nil) i @user.reload.reset_digest
    odwołuje się do danych w bazie

W następnych testach konieczne jest już użycie reset_token, które nie zostaje utworzone i przypisane do zmiennej @user użytej w teście. Do tego celu używa się więc metody assigns, która jakoś wyciąga zmienną usera “przerobioną” w kontrolerze.

Co masz na myśli pisząc “się zmienia”?

Nadal nie rozumiem jednak tego, czemu reset_digest naszej zmiennej z
testów się zmienia, skoro to nie ona jest zmieniana przez akcję create.

Dziękuję za odpowiedź.
Mój problem ze zrozumieniem wynika z tego, że ciężko mi było pojąć, dlaczego wartość reset_digest zmienia się w bazie - czyli została wywołana akcja create - a nasza zmienna w teście mimo to nie ma reset_token. Chodzi więc o to, że po prostu test nie ma dostępu do atrybutu wirtualnego, mimo utworzenia się reset_digest w bazie danych(w następstwie akcji create), tak? :slight_smile: