Actuellement auto-apprentissage, le premier tour du tutoriel Rails Si vous voulez défier au deuxième tour Je veux essayer le développement piloté par les tests (TDD) J'ai décidé de lister les exigences à implémenter dans le thème de chaque chapitre
En tant que flux de tâches que j'imagine dans la deuxième semaine Définition des exigences (laissez ici la première semaine)> Ecrivez un test (connectez-vous ici la deuxième semaine)> Implémentation (Si c'est loin de la réalité, veuillez le signaler tôt)
rise
dans votre code pour lever une exception afin que vous puissiez voir si elle est incluse dans votre test
--ʻAssert_equals'écrit "<attendu>, <actuel>" --Il existe un moyen d'afficher la page de maintenance si Heroku est temporairement inaccessible
maintenance heroku: on (off)`Sans rapport avec le contenu de ce chapitre Modélisez le résultat pour améliorer la qualité de l'apprentissage Augmentation écrasante du montant (iuput: sortie = environ 2: 8) La vitesse de progression a considérablement diminué, mais j'estime que la qualité de l'apprentissage s'est améliorée.
Difficile d'équilibrer l'efficacité du temps et l'effet d'apprentissage
Introduction de l'éditeur de démarques pour Mac pour améliorer autant que possible l'efficacité du temps
--Enregistrer l'ID utilisateur et le jeton de mémoire dans le cookie
current_user = nil
)--Si ce n'est pas current_user = nil
, le traitement suivant est inutile
current_user = nil
)current_user
devient nil
--Redirect vers root_url après la déconnexion (implémenté)Risque d'exposition aux cookies et que faire
Extraire les cookies directement des paquets réseau qui transitent par un réseau mal géré avec un logiciel spécial appelé Packet Sniffer > Contre-mesure: SSL (pris en charge)
Extraire le jeton de stockage de la base de données > Solution de contournement: jetons de hachage stockés dans la base de données
Voler l'accès en utilisant directement l'ordinateur ou le smartphone sur lequel l'utilisateur est connecté > Solution de contournement: changer de jeton une fois déconnecté (Tutoriel Rails 6e édition)
Ajout de l'attribut Remember_digest
au modèle utilisateur
$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate
Je veux pouvoir gérer l'attribut Remember_token
dans le modèle utilisateur
Cet attribut n'est pas enregistré dans le DB
> Utilisez ʻattr_accessor`
Définir ʻUser.new_tokendans le modèle utilisateur (retourner le jeton de mémoire)
SecureRandom.urlsafe_base64` convient pour générer des jetons de stockage
$ rails console
>> SecureRandom.urlsafe_base64
=> "brl_446-8bqHv87AQzUj_Q"
def User.new_token
SecureRandom.urlsafe_base64
end
Hash le jeton de stockage et enregistrez-le dans la base de données Définir «se souvenir» dans le modèle utilisateur Vous pouvez utiliser ʻUser.digest (string) `pour le hachage
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
«self» est requis
Je veux aussi que le souvenir soit exécuté lorsque je me connecte C'est après ça
Je souhaite enregistrer l'ID utilisateur dans le navigateur «Signé» avec des cookies signés
cookies.signed[:user_id] = user.id
Décrypté avec cookies.signed [: user_id]
La persistance des cookies est possible avec permanent
Dans la chaîne de méthodes
cookies.permanent.signed[:user_id] = user.id
Définir «authentifié?» Dans le modèle utilisateur Le comportement est le même que lors de l'utilisation d'un mot de passe, mais vous pouvez l'imaginer, mais la compréhension partielle de BCrypt ... est insuffisante.
#Renvoie true si le jeton passé correspond au condensé
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
L'argument Remember_token
est une variable locale
Changer le comportement lors de la connexion
Ajout de Remember (user)
Ceci est différent de la méthode Remember du modèle User (prenant un argument)
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 (user)
est défini comme un assistant
Appelez lorsque vous vous connectez
Génère des jetons de stockage, les enregistre dans des cookies et enregistre des jetons de résumé dans une base de données
(L'importance de l'utilisation de différents helpers et la priorité de la méthode portant le même nom ne sont pas entièrement comprises)
app/helpers/sessions_helper.rb
#Rendre la session d'un utilisateur persistante
def remember(user)
user.remember#Méthode du modèle utilisateur (génération du jeton de stockage, stockage DB du jeton de résumé)
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
current_user
n'est pas qu'une session
Être maintenu par les cookies
app/helpers/sessions_helper.rb
#Renvoie l'utilisateur correspondant au cookie de jeton de mémoire
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
La répétition peut être omise avec (user_id = session [: user_id])
=
n'est pas une opération logique, une affectation
À ce stade, «test des rails» est (ROUGE)
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>'
Je ne vois pas le lien pour me connecter En d'autres termes, il semble que vous ne vous soyez pas déconnecté (Devrait maintenir current_user par cookie)
DB Remember_digest
lors de la déconnexion
Supprimer le cookie du navigateur (mise à jour avec zéro)
Tout d'abord, définissez forget
pour faire fonctionner la base de données
app/models/user.rb
#Supprimer les informations de connexion de l'utilisateur
def forget
update_attribute(:remember_digest, nil)
end
Ensuite, définissez la méthode d'assistance forget (user)
Nil : Remember_digest
sur la base de données avec ʻuser.forgetplus tôt Aucun cookie de navigateur avec
cookies.delete`
/sample_app/app/helpers/sessions_helper.rb
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
Appelez cette méthode d'assistance forget (user)
avec la même méthode d'assistance log_out
/sample_app/app/helpers/sessions_helper.rb
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
Maintenant, log_out
peut vider la session et le cookie
Déconnexion complète possible
rails test
est (VERT)
Résolvez les bogues qui se produisent lors de l'utilisation de plusieurs onglets et navigateurs
Premièrement, si vous ouvrez plusieurs onglets tout en étant connecté en même temps,
Problèmes causés par la possibilité de passer plusieurs fois à la déconnexion (delete logout_path
)
Puisque current_user = nil
est défini dans le premier delete logout_path
Si vous demandez à nouveau delete logout_path
, le contrôleur appellera à nouveau la méthode log_out
.
Quand j'essaye d'exécuter forget (current_user)
, j'obtiens une erreur car current_user = 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'
Pour pouvoir le détecter dans un test
Très simple étant donné que vous devez demander à nouveau delete logout_path
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
#↓ Ici encore supprimer la déconnexion_Chemin de la demande
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
Lorsque vous exécutez le test (ROUGE) Confirmez que le bogue peut être reproduit avec succès
Lorsqu'il n'est pas connecté
Pour la requête delete logout_path
La méthode log_out
ne doit pas être appelée
Vous pouvez utiliser la méthode log_in
pour confirmer votre connexion
def logged_in?
!current_user.nil?
end
app/controllers/sessions_controller.rb
def destroy
log_out if logged_in?
redirect_to root_url
end
Devient
log_out si connecté_in?
Est-ce une manière à la mode d'écrire
Peut être remplacé par ʻif ... end`
[Ruby] Les abus sont strictement interdits! ?? Cas où l'écriture avec un suffixe rend la lecture plus difficile Cette personne a été désignée ainsi du point de vue de la lisibilité et aimerait s'y référer.
Dans cet état, rails test
est (VERT)
Suivant,
A, B, deux navigateurs sont ouverts, déconnectez-vous avec un navigateur B (DB Remember_digest
est nil
),
Puis fermez le navigateur A (la session [: user_id] du navigateur A est nulle
)
Problèmes causés par le fait de ne laisser que les cookies stockés dans le navigateur A
Si vous rouvrez le navigateur A dans cet état, le cookie reste.
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]) #Devenez vrai ici
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])#Erreur
log_in user
@current_user = user
end
end
end
ʻSi l'utilisateur && user.authenticated? (Cookies [: Remember_token]) est exécuté Parce que
Remember_digest est défini sur
nil` en raison du comportement d'un autre navigateur
python
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)#remember_digest = nil
end
Renvoie une exception en raison d'une incohérence avec le contenu du cookie
BCrypt::Errors::InvalidHash: invalid hash
Pour pouvoir le détecter dans le test
La situation du navigateur A doit être reproduite avec l'objet de modèle utilisateur.
En d'autres termes, préparez un modèle avec Remember_digest = nil
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
Le @ user
créé par la méthode de configuration correspond exactement à cela, vous pouvez donc utiliser ceci
Actuellement Remember_digest = nil
@ user.authenticated? ('')
Renvoie une exception et le test est (RED)
Si Remember_digest = nil
Je veux que @ user.authenticated? ('')
Renvoie false
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
Devrait être
Avec Progate, je l'ai souvent utilisé à la fin de l'instruction If, donc je ne le remarque pas.
return
quitte la méthode et renvoie la valeur
Donc rien de moins n'est exécuté
Maintenant, le test est (VERT)
Les cases à cocher peuvent être insérées avec la méthode d'assistance
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 %>
Ajouter CSS
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]
Reçoit la valeur 1 '' si elle est activée,
0 '' si elle est désactivée
"" 1 "ou" "0" au lieu de "1" ou "0"
Ajoutez ce qui suit à l'action create
du contrôleur Sessions
if params[:session][:remember_me] == '1'
remember(user)
else
forget(user)
end
Ce qui précède peut être réécrit comme suit (opérateur ternaire)
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
Achèvement du mécanisme de connexion permanente jusqu'à présent
Définir des méthodes d'assistance qui permettent aux utilisateurs de se connecter dans le test
Peut être utilisé pour le test unitaire et le test d'intégration respectivement
Définissez la méthode log_in_as dans chacune des classes ActiveSupport :: TestCase
et class ActionDispatch :: IntegrationTest
Parce que le test d'intégration ne peut pas gérer la session
directement
Utilisez post login_path
dans la méthode log_in_as
pour les tests d'intégration
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
#Renvoie true si l'utilisateur du test est connecté
def is_logged_in?
!session[:user_id].nil?
end
#Connectez-vous en tant qu'utilisateur test
def log_in_as(user)
session[:user_id] = user.id
end
end
class ActionDispatch::IntegrationTest
#Connectez-vous en tant qu'utilisateur test
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)
Ayons deux problèmes
Le test d'intégration ne permet pas d'accéder aux variables d'instance définies par le contrôleur * > ʻAssigns (: user) `vous permettra d'accéder à la variable d'instance précédente @user
En premier lieu, la variable locale ʻuser` est utilisée dans la méthode de création du contrôleur Sessions *
La variable d'instance (@ user
) n'est pas définie *
> Définissez une variable d'instance (@ user
) avec la méthode create du contrôleur Sessions
En principe, le «@ user» défini par la méthode «setup» dans le test d'intégration N'inclut pas l'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
Test du comportement de current_user
ʻAssert_equal
/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
Si vous procédez comme suit, il sera temporairement inaccessible La page de maintenance peut être affichée
$ heroku maintenance:on
$ git push heroku
$ heroku run rails db:migrate
$ heroku maintenance:off
Recommended Posts