Derzeit Selbststudium, die erste Runde des Rails-Tutorials Wenn Sie in der zweiten Runde herausfordern möchten Ich möchte Test Driven Development (TDD) ausprobieren. Ich beschloss, die Anforderungen aufzulisten, die im Thema jedes Kapitels implementiert werden sollten
Als Aufgabenablauf, den ich mir in der zweiten Woche vorgestellt habe Anforderungsdefinition (in der ersten Woche hier lassen)> Test schreiben (in der zweiten Woche hier verbinden)> Implementierung (Wenn es weit von der Realität entfernt ist, weisen Sie bitte frühzeitig darauf hin.)
assert_equal
wird geschrieben als<erwartet>, <tatsächlich>
Nicht im Zusammenhang mit dem Inhalt dieses Kapitels Erstellen Sie eine Vorlage für die Ausgabe, um die Qualität des Lernens zu verbessern Überwiegend die Menge erhöht (iuput: output = ca. 2: 8) Die Fortschrittsgeschwindigkeit ist erheblich gesunken, aber ich habe das Gefühl, dass sich die Qualität des Lernens verbessert hat.
Es ist schwierig, Zeiteffizienz und Lerneffekt in Einklang zu bringen
Markdown-Editor für Mac eingeführt, um die Zeiteffizienz so weit wie möglich zu verbessern
current_user = nil
).current_user = nil
)current_user
zu nil
Risiko der Cookie-Exposition und was zu tun ist
Extrahieren Sie Cookies mit einer speziellen Software namens Packet Sniffer direkt aus Netzwerkpaketen, die ein schlecht verwaltetes Netzwerk durchlaufen > Gegenmaßnahme: SSL (unterstützt)
Extrahieren Sie das Speichertoken aus der Datenbank > Problemumgehung: In der Datenbank gespeicherte Hash-Token
Stehlen Sie den Zugriff, indem Sie den Computer oder das Smartphone, auf dem der Benutzer angemeldet ist, direkt bedienen > Problemumgehung: Ändern Sie das Token, wenn Sie abgemeldet sind (Rails Tutorial 6. Ausgabe)
Attribut Remember_digest
zum Benutzermodell hinzugefügt
$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate
Ich möchte in der Lage sein, das Attribut memor_token
im Benutzermodell zu verarbeiten
Dieses Attribut wird nicht in der Datenbank gespeichert
> Benutze attr_accessor
Definieren Sie "User.new_token" im Benutzermodell (Rückgabespeichertoken) "SecureRandom.urlsafe_base64" eignet sich zum Generieren von Speicher-Token
$ rails console
>> SecureRandom.urlsafe_base64
=> "brl_446-8bqHv87AQzUj_Q"
def User.new_token
SecureRandom.urlsafe_base64
end
Hash das Speicher-Token und speichere es in der DB Definieren Sie "Merken" im Benutzermodell Sie können "User.digest (string)" zum Hashing verwenden
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
self.
ist erforderlich
Ich möchte mich auch daran erinnern lassen, ausgeführt worden zu sein, wenn ich mich anmelde Es ist danach
Ich möchte die Benutzer-ID im Browser speichern Signiert mit signierten Cookies
cookies.signed[:user_id] = user.id
Entschlüsselt mit cookies.signed [: user_id]
Cookie-Persistenz ist mit "permanent" möglich In der Methodenkette
cookies.permanent.signed[:user_id] = user.id
Definieren Sie "authentifiziert?" Im Benutzermodell Das Verhalten ist das gleiche wie bei der Verwendung eines Passworts, aber Sie können es sich vorstellen, aber das teilweise Verständnis von BCrypt ... ist unzureichend.
#Gibt true zurück, wenn das übergebene Token mit dem Digest übereinstimmt
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
Das Argument memor_token
ist eine lokale Variable
Ändern Sie das Verhalten beim Anmelden
Remember (user)
hinzugefügt
Dies unterscheidet sich von der Remember-Methode des Benutzermodells (unter Berücksichtigung eines Arguments).
app/controllers/sessions_controller.rb
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
Remember (Benutzer)
ist als Helfer definiert
Rufen Sie an, wenn Sie sich anmelden
Generiert ein Speicher-Token, speichert es in einem Cookie und speichert ein Digest-Token in einer Datenbank.
(Die Bedeutung der Verwendung verschiedener Helfer und die Priorität der Methode mit demselben Namen sind nicht vollständig verstanden.)
app/helpers/sessions_helper.rb
#Machen Sie die Sitzung eines Benutzers dauerhaft
def remember(user)
user.remember#Benutzermodellmethode (Generierung des Speicher-Tokens, DB-Speicherung des Digest-Tokens)
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
current_user
ist nicht nur eine Sitzung
Von Cookies gepflegt werden
app/helpers/sessions_helper.rb
#Gibt den Benutzer zurück, der dem Speicher-Token-Cookie entspricht
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
Wiederholung kann mit (user_id = session [: user_id])
weggelassen werden
=
ist keine logische Operation, Zuweisung
Zu diesem Zeitpunkt ist "Schienentest" (ROT)
FAIL["test_login_with_valid_information_followed_by_logout", #<Minitest::Reporters::Suite:0x0000556848d6b040 @name="UsersLoginTest">, 1.7997455329999923]
test_login_with_valid_information_followed_by_logout#UsersLoginTest (1.80s)
Expected at least 1 element matching "a[href="/login"]", found 0..
Expected 0 to be >= 1.
test/integration/users_login_test.rb:36:in `block in <class:UsersLoginTest>'
Ich sehe den Link zum Login nicht Mit anderen Worten, Sie haben sich anscheinend nicht abgemeldet (Es wird erwartet, dass current_user per Cookie beibehalten wird.)
DB memor_digest
beim Abmelden
Browser-Cookies löschen (Update mit Null)
Definieren Sie zunächst "Vergessen", um die DB zu betreiben
app/models/user.rb
#Benutzeranmeldeinformationen verwerfen
def forget
update_attribute(:remember_digest, nil)
end
Definieren Sie als Nächstes die Hilfsmethode "Vergiss (Benutzer)"
Nil : memor_digest
in der DB mit dem vorherigen user.forget
Keine Browser-Cookies mit cookies.delete
/sample_app/app/helpers/sessions_helper.rb
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
Nennen Sie diese Hilfsmethode "Vergiss (Benutzer)" mit derselben Hilfsmethode "log_out"
/sample_app/app/helpers/sessions_helper.rb
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
Jetzt kann log_out
sowohl die Sitzung als auch das Cookie leeren
Vollständige Abmeldung möglich
Rails Test
ist (GRÜN)
Beheben Sie Fehler, die bei Verwendung mehrerer Registerkarten und Browser auftreten
Wenn Sie mehrere Registerkarten öffnen, während Sie gleichzeitig angemeldet sind,
Probleme, die dadurch verursacht wurden, dass Sie mehrmals auf Abmelden (logout_path löschen
) zugreifen konnten
Da current_user = nil
im ersten delete logout_path
gesetzt ist
Wenn Sie erneut "logout_path löschen" anfordern, ruft der Controller die Methode "log_out" erneut auf.
Wenn ich versuche, "Vergiss (aktueller_Benutzer)" auszuführen, erhalte ich eine Fehlermeldung, weil "aktueller_Benutzer = nil"
NoMethodError: undefined method `forget' for nil:NilClass
app/helpers/sessions_helper.rb:36:in `forget'
app/helpers/sessions_helper.rb:24:in `log_out'
app/controllers/sessions_controller.rb:20:in `destroy'
Um dies in einem Test erkennen zu können Sehr einfach, wenn man bedenkt, dass Sie erneut "delete logout_path" anfordern sollten
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
.
.
.
test "login with valid information followed by logout" do
get login_path
post login_path, params: { session: { email: @user.email,
password: 'password' } }
assert is_logged_in?
assert_redirected_to @user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
delete logout_path
assert_not is_logged_in?
assert_redirected_to root_url
#↓ Hier wieder abmelden löschen_Pfad anfordern
delete logout_path
follow_redirect!
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path, count: 0
assert_select "a[href=?]", user_path(@user), count: 0
end
end
Wenn Sie den Test ausführen (ROT) Stellen Sie sicher, dass der Fehler erfolgreich reproduziert werden kann
Wenn nicht angemeldet Für die Anforderung "logout_path löschen" Die Methode "log_out" sollte nicht aufgerufen werden
Sie können die Methode "logged_in" verwenden, um Ihre Anmeldung zu bestätigen
def logged_in?
!current_user.nil?
end
app/controllers/sessions_controller.rb
def destroy
log_out if logged_in?
redirect_to root_url
end
Wird
log_out if logged_in?
Ist eine modische Schreibweise
Kann durch "if ... end" ersetzt werden
[Ruby] Missbrauch ist strengstens verboten! ?? Fälle, in denen das Schreiben mit einem Postfix das Lesen erschwert Diese Person wurde unter dem Gesichtspunkt der Lesbarkeit auf diese Weise hingewiesen und möchte darauf verweisen.
In diesem Zustand ist "Schienentest" (GRÜN)
Nächster,
A, B, zwei Browser sind geöffnet, melden Sie sich mit einem Browser B ab (DB memor_digest
ist nil
),
Schließen Sie dann Browser A (die Sitzung [: user_id] von Browser A ist null)
Probleme, die dadurch verursacht werden, dass nur Cookies im Browser A gespeichert bleiben
Wenn Sie Browser A in diesem Zustand erneut öffnen, bleibt das Cookie erhalten.
python
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id]) #Werden Sie hier wahr
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])#Error
log_in user
@current_user = user
end
end
end
if user && user.authenticated? (Cookies [: Remember_token])
wird ausgeführt
Weil memor_digest
aufgrund des Verhaltens eines anderen Browsers auf nil
gesetzt ist
python
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)#remember_digest = nil
end
Gibt eine Ausnahme aufgrund von Inkonsistenzen mit dem Inhalt des Cookies zurück
BCrypt::Errors::InvalidHash: invalid hash
Um dies im Test erkennen zu können
Die Situation von Browser A sollte mit dem Objekt des Benutzermodells reproduziert werden.
Mit anderen Worten, bereiten Sie ein Modell mit memor_digest = nil
vor
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "[email protected]",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('')
end
end
Der von der Setup-Methode erstellte @ user
entspricht genau dem, sodass Sie diesen verwenden können
Derzeit memor_digest = nil
@ user.authenticated? ('')
Gibt eine Ausnahme zurück und der Test ist (ROT)
Wenn memor_digest = nil
Ich möchte, dass "@ user.authenticated? (") "" False "zurückgibt
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
Sollte sein
Bei Progate habe ich es oft am Ende der If-Anweisung verwendet, sodass ich es nicht bemerke.
return
beendet dort die Methode und gibt den Wert zurück
Es wird also nicht weniger ausgeführt
Jetzt ist der Test (GRÜN)
Kontrollkästchen können mit der Hilfsmethode eingefügt werden
html:/sample_app/app/views/sessions/new.html.erb
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>
CSS hinzufügen
app/assets/stylesheets/custom.scss
.
.
.
/* forms */
.
.
.
.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}
#session_remember_me {
width: auto;
margin-left: 0;
}
params [: session] [: Remember_me]
Wenn diese Option aktiviert ist, erhalten Sie den Wert "1". Wenn sie deaktiviert ist, erhalten Sie den Wert "0"
"1" oder "0" anstelle von "1" oder "0"
Fügen Sie der Aktion "Erstellen" des Sitzungscontrollers Folgendes hinzu
if params[:session][:remember_me] == '1'
remember(user)
else
forget(user)
end
Das Obige kann wie folgt umgeschrieben werden (ternärer Operator)
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
Abschluss des permanenten Anmeldemechanismus bisher
Definieren Sie Hilfsmethoden, mit denen sich Benutzer innerhalb des Tests anmelden können
Kann für Unit-Test bzw. Integrationstest verwendet werden Definieren Sie die Methode "log_in_as" in jeder der Klassen ActiveSupport :: TestCase und "ActionDispatch :: IntegrationTest"
Weil der Integrationstest "Sitzung" nicht direkt verarbeiten kann Verwenden Sie "post login_path" in der "log_in_as" -Methode für Integrationstests
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
#Gibt true zurück, wenn der Testbenutzer angemeldet ist
def is_logged_in?
!session[:user_id].nil?
end
#Melden Sie sich als Testbenutzer an
def log_in_as(user)
session[:user_id] = user.id
end
end
class ActionDispatch::IntegrationTest
#Melden Sie sich als Testbenutzer an
def log_in_as(user, password: 'password', remember_me: '1')
post login_path, params: { session: { email: user.email,
password: password,
remember_me: remember_me } }
end
end
rails test
(GREEN)
Lassen Sie uns zwei Probleme haben
Der Integrationstest erlaubt keinen Zugriff auf vom Controller definierte Instanzvariablen *
> assigns (: user)
ermöglicht Ihnen den Zugriff auf die vorherige Instanzvariable @user
Zunächst wird die lokale Variable "user" in der Erstellungsmethode des Sessions-Controllers verwendet. *
Instanzvariable (@ user
) ist nicht definiert *
> Definieren Sie eine Instanzvariable (@ user
) mit der create-Methode des Sessions-Controllers
Als Voraussetzung ist der "@ user", der durch die "setup" -Methode im Integrationstest definiert wird Enthält nicht das Attribut Remember_token
/sample_app/app/controllers/sessions_controller.rb
def create
@user = User.find_by(email: params[:session][:email].downcase)
if @user&.authenticate(params[:session][:password])
log_in @user
params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
redirect_to @user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
/sample_app/test/integration/users_login_test.rb
test "login with remembering" do
log_in_as(@user, remember_me: '1')
assert_equal cookies[:remember_token], assigns(:user).remember_token
end
Testen des Verhaltens von current_user
Notiert als assert_equal <expected>, <actual>
/sample_app/test/helpers/sessions_helper_test.rb
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
@user = users(:michael)
remember(@user)
end
test "current_user returns right user when session is nil" do
assert_equal @user, current_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))
assert_nil current_user
end
end
Wenn Sie Folgendes tun, ist vorübergehend kein Zugriff darauf möglich Die Wartungsseite kann angezeigt werden
$ heroku maintenance:on
$ git push heroku
$ heroku run rails db:migrate
$ heroku maintenance:off
Recommended Posts