[RUBY] Rails Tutorial 6e édition Résumé d'apprentissage Chapitre 8

Aperçu

Cet article approfondit ma compréhension en écrivant un article de commentaire du tutoriel Rails pour solidifier davantage mes connaissances Cela fait partie de mon étude. Dans de rares cas, un contenu ridicule ou un contenu incorrect peut être écrit. Notez s'il vous plaît. J'apprécierais que vous me disiez implicitement ...

La source Tutoriel Rails Chapitre 6

Que faire dans ce chapitre

Puisque nous avons rendu possible la création d'un utilisateur dans le chapitre précédent, nous allons créer un mécanisme pour se connecter avec l'utilisateur créé.

session

Étant donné que HTTP est sans état (il n'y a pas d'état), il n'est pas possible d'utiliser les informations de requête précédentes. Il n'y a aucun moyen de conserver les données dans HTTP lorsque vous passez à une autre page. Les informations de l'utilisateur sont conservées en utilisant une "session" à la place.

Les cookies sont souvent utilisés pour implémenter cette session dans Rails Étant donné que les cookies sont des données textuelles stockées dans le navigateur et ne disparaissent pas même si la page est déplacée, les informations utilisateur peuvent être conservées.

Contrôleur de sessions

La session est également implémentée en la reliant directement aux actions REST. En particulier

mouvement action
Formulaire de connexion new
Processus de connexion create
Processus de déconnexion destroy

Connexion → Créer une session temporaire Déconnexion → Supprimer la session temporaire

Tout d'abord, créez un contrôleur pour la session. rails g controller sessions new

La création d'un contrôleur avec la commande generate crée également une vue correspondante. Cette fois, la vue n'est nécessaire que pour la nouvelle action sur le formulaire de connexion Spécifiez uniquement nouveau avec la commande generate.

Mettez également à jour routes.rb. Bien que le routage vers la nouvelle action soit défini récrire.

  get '/login', to:'sessions#new'
  post '/login', to:'sessions#create'
  delete '/logout', to:'sessions#destroy'

Modifiez le test généré automatiquement pour utiliser également la route nommée.

rails routesVous pouvez vérifier la liste des itinéraires en utilisant la commande.

Exercice

  1. get login_pathreçoit un formulaire de connexion avec une requête GET.post login_path```Envoie la valeur saisie dans le formulaire de connexion dans une demande de publication.

$ rails routes | grep sessions
                                login GET    /login(.:format)                                                                         sessions#new
                                      POST   /login(.:format)                                                                         sessions#create
                               logout DELETE /logout(.:format)                                                                        sessions#destroy

Formulaire de connexion

Dans le formulaire, nous avons implémenté le formulaire en utilisant form_with. Le formulaire de connexion ne crée pas de ressource Utilisateurs, il crée simplement une session L'objet modèle ne peut pas être spécifié. Dans ce cas

<%= form_with(url: login_path, scope: :session, local: true) do |f| %>


 Envoyez une demande de publication à login_path par
 En définissant scope :: session
 Les valeurs des différents attributs seront stockées dans les paramètres [: session].
 Par exemple, mot de passe
 Stocké dans les paramètres [: session] [: mot de passe].

##### Exercice
1.
 Il existe deux types de login_path, mais form_with est défini sur la requête POST par défaut.
 Envoyez une requête POST à login_path. En d'autres termes, l'action de création est atteinte avec le POST login_path.
 Dans le cas de GET, la nouvelle action est atteinte.

#### Recherche d'utilisateurs et authentification
 Cette fois, créez un processus lorsque la connexion échoue.

```rb
 def new
  end
  
  def create
     render 'new'
  end
  
  def destroy
  end

Si vous définissez le contrôleur comme ceci Lorsque vous soumettez le formulaire, il sera transféré à la nouvelle page tel quel.

Si vous essayez de soumettre le formulaire dans cet état image.png

Vous pouvez voir que la clé de session a été ajoutée au hachage des paramètres et contient les clés de messagerie et de mot de passe.

params = { session: { password: "foobar", email: "[email protected]" }}

Il a une telle structure.

Avec ça Recevez les informations d'identification de l'utilisateur des paramètres et recherchez l'utilisateur avec la méthode `` find_by '' authenticateAprès avoir authentifié le mot de passe avec la méthode Processus dans le flux de création d'une session.

Cliquez ici pour le contenu de l'action de création basée sur ceci

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
    #Processus de redirection vers la page utilisateur(à partir de maintenant)
    else
   #Créez un message d'erreur.
      render 'new'
    end
  end

&& est un opérateur de produit logique, et les deux ne sont vrais que lorsqu'ils sont vrais. La condition est que l'utilisateur existe et que son mot de passe soit authentifié.

Exercice
  1. Le résultat attendu est obtenu.
>> user = nil
=> nil
>> user && user.authenticate("foobar")
=> nil
>> !!(user && user.authenticate("foobar"))                                                                                
=> false
>> user = User.first
   (1.4ms)  SELECT sqlite_version(*)
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "[email protected]", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-14 02:57:10", password_digest: [FILTERED]>
>> !!(user && user.authenticate("foobar"))
=> true

Afficher le message flash

Créez un message flash lorsque la connexion échoue.

danger] = "Invalid email/password combination"



 Si cela est laissé tel quel, le message restera même lorsque la page est remplacée par une autre page.
 Parce que je réaffiche le formulaire avec le rendu.
 Le rendu n'étant pas une redirection mais un rendu de la vue, le flash ne disparaît pas.
 Je vais résoudre ce problème à partir de maintenant.

 ↓ Le flash est affiché sur les autres pages ↓
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617753/4bbfd1e0-1e51-1de0-0b09-44394f9d9723.png)


#### Test flash
 Créez un test d'intégration autour de la connexion. Cette fois, il y a un problème d'affichage du flash sur d'autres pages, donc cette erreur
 Créez le test pour détecter d'abord et écrivez le code pour que le test réussisse.

 Cliquez ici pour ce code

```rb
  test "login with invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path , params:{ session: { email:"",password:"" }}
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

Les deux dernières lignes accèdent au chemin racine et vérifient que le flash est désactivé.

Bien sûr, une erreur se produira. Utilisez `` flash.now '' pour résoudre ce problème.

flash.disparaît maintenant lorsque vous passez à l'action suivante.


 ↑ Site de référence (https://qiita.com/taraontara/items/2db82e6a0b528b06b949)

 J'ai été confus pendant un moment que tout devrait être flash.now, mais dans le cas de flash.now, lorsque redirect_to est utilisé, flash n'est jamais affiché en premier lieu
 C'est fini. (Pour aller lire la prochaine action avec redirect_to)

 Remplacez immédiatement `` flash '' par `` flash.now ''.
 Ce changement passera le test.

##### Exercice
1.
 Erreur d'authentification
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617753/341a1295-c6d4-e19b-1947-88e9044042f4.png)
 Il disparaît lorsque vous passez à une autre page
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617753/09cc611d-ce89-3f2c-0015-a7018fba1f01.png)


### S'identifier
 Ensuite, implémentez le traitement lorsque les informations de connexion sont valides.
 Le flux de traitement est
 S'authentifier, créer une session temporaire à l'aide de cookies si l'utilisateur est correct, terminer le processus de connexion,
 Transit vers la page utilisateur correspondante.

 Les sessions sont traitées séparément selon que vous êtes connecté ou non, même sur des pages autres que la page de connexion.
 Incluez SessionsHelper dans ApplicationController afin qu'il puisse être utilisé en commun.
 En faisant cela, il peut être utilisé en commun par tous les contrôleurs.

#### méthode log_in
 Implémentez le processus de connexion en utilisant la méthode `` session '' définie dans Rails.
 Si vous utilisez la méthode de session, la valeur associée à la clé est enregistrée dans des cookies temporaires.
 Ces cookies temporaires expirent au moment où le navigateur est fermé, vous devrez donc vous connecter à chaque fois que vous le fermez.

 La connexion étant censée être utilisée à divers endroits, définissez-la comme une méthode dans SessionsHelper.


#### **`sessions_helper.rb`**
```rb

module SessionsHelper
  def log_in(user)
    session[:user_id] = user.id
  end 
end

Après cela, ajoutez-le à l'action de création et redirigez-le pour le terminer!

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in(user)
      redirect_to user
    else
      flash.now[:danger] = "Invalid email/password combination"
      render 'new'
    end
  end
Exercice
  1. Dans le cas de Chrome, la liste peut être affichée dans Cookies sur l'onglet Application des outils de développement. image.png ↑ L'ID utilisateur chiffré est stocké (_sample_app_session)

  2. Expire → L'heure d'expiration est écrite. Dans le cas d'une session temporaire, il s'agit d'une session, et vous pouvez voir qu'elle expire lorsque le navigateur est fermé.

Utilisateur actuel

Définissez la méthode `` utilisateur_actuel '' pour que l'ID utilisateur puisse être récupéré et que les informations utilisateur puissent être utilisées sur une autre page.

La recherche d'un objet utilisateur à partir de l'ID stocké dans la session, mais l'utilisation de find provoquera une erreur si l'utilisateur n'existe pas. Utilisez la méthode find_by.

  def current_user
    if session[:user_id]
      User.find_by(id: session[:user_id])
    end
  end

De plus, en enregistrant ce résultat (objet utilisateur) dans la variable d'instance La référence à la base de données dans une requête ne peut être effectuée qu'une seule fois, ce qui entraîne une vitesse plus élevée.

  def current_user
    if session[:user_id]
      if @current_user.nil?
        @current_user = User.find_by(id: session[:user_id])
      else
        @current_user
      end
    end
  end

Et||Vous pouvez écrire cette instruction if sur une seule ligne en utilisant l'opérateur.

@current_user = @current_user || User.find_by(id: session[:user_id])



 De plus, écrire cette ligne sous forme abrégée donnera le code correct.

#### **`@current_user ||= User.find_by(id: session[:user_id])`**
Exercice
>> User.find_by(id:"123")
   (0.4ms)  SELECT sqlite_version(*)
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 123], ["LIMIT", 1]]
=> nil

(1) Si nil est donné, la méthode find_by renvoie nil et @current_user devient également nil. ② Lorsque @ current_user``` est vide, lisez à partir de la base de données, mais lorsqu'il y a du contenu, remplacez la confiance (ne rien faire)

>> session = {}
=> {}
>> session[:user_id] = nil
=> nil
>> @current_user ||= User.find_by(id:session[:user_id])
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
>> session[:user_id] = User.first.id
  User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
>> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "[email protected]", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-14 02:57:10", password_digest: [FILTERED]>
>> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "take", email: "[email protected]", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-14 02:57:10", password_digest: [FILTERED]>
>> 

Modifier le lien de mise en page

Modifiez dynamiquement la page affichée selon que vous êtes connecté ou non. Si vous vous branchez dans ERB selon que vous êtes connecté ou non, modifiez le lien. Définissez une méthode `` log_in? '' Qui renvoie une valeur logique à ceci.

  def logged_in?
    !current_user.nil?
  end

Je veux m'assurer que current_user n'est pas vide, donc j'inverse la valeur logique avec!

Ensuite, modifiez la disposition de l'en-tête.

erb:_header.html.erb


<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app",root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   root_path %></li>
        <li><%= link_to "Help",   help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
              <li><%= link_to "Settings", '#'%></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete%>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>

J'ajoute un menu déroulant en utilisant une classe de bootstrap (telle que la liste déroulante). Aussi <%= link_to "profile", current_user %>Dans la ligne Le lien est spécifié sous forme abrégée Le lien sera vers la page du spectacle. C'est une fonction qui convertit un objet utilisateur en un lien vers la page d'affichage.

Le menu déroulant doit charger jQuery dans bootstrap, donc Chargez via application.js.

Tout d'abord, chargez les packages jQuery et bootstrap avec yarn.

yarn add [email protected] [email protected]



 Ensuite, ajoutez les paramètres jQuery au fichier d'environnement Webpack.


#### **`config/webpack/environment.js`**
```js

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
module.exports = environment

Enfin, exigez jQuery dans application.js et importez Bootstrap.

À ce stade, le menu déroulant est activé Puisque vous pouvez vous connecter en tant qu'utilisateur valide Vous pouvez tester efficacement votre code existant.

De plus, lorsque vous fermez le navigateur, la session temporaire est supprimée comme prévu et vous devez vous reconnecter.

Exercice
  1. Écran de suppression des cookies Chrome Après la suppression, le lien de connexion s'affiche. image.png

  2. Omis. Vous serez invité à vous connecter lorsque vous redémarrerez votre navigateur. (La session temporaire disparaît.)

Modifications de la mise en page du test

Nous testerons le comportement des liens d'en-tête à changement dynamique avec des tests d'intégration. L'opération est

  1. Accédez au chemin de connexion
  2. Publier les informations de connexion
  3. Vérifiez que le lien de connexion est masqué
  4. Vérifiez que le lien de déconnexion est affiché
  5. Vérifiez que le lien du profil est affiché

Vous devez d'abord vous connecter en tant qu'utilisateur enregistré pour tester ces L'utilisateur doit être enregistré dans la base de données de test. Dans Rails, vous pouvez créer des données de test à l'aide de fixture.

Puisque les données de l'utilisateur sont enregistrées mais que le mot de passe est haché et enregistré sous password_digest Les données de l'appareil doivent également stocker le mot de passe haché dans password_digest

Définit une méthode de résumé qui renvoie une chaîne hachée de mot de passe Puisqu'il est utilisé pour les appareils, il n'est pas utilisé pour les objets utilisateur. Par conséquent, il est défini comme une méthode de classe.

En outre, pour réduire le poids, minimiser le coût du hachage pendant les tests et augmenter le coût de la sécurité dans un environnement de production. Cliquez ici pour la méthode définie

  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine::cost
    BCrypt::Password.create(string, cost: cost)
  end

Maintenant que la méthode de résumé est prête, créez un appareil.

users.yml


michael:
  name: Michael Example
  email: [email protected] 
  password_digest: <%= User.digest('password') %>

Puisque erb peut être utilisé dans le fixture, la chaîne de caractères hachée avec "password" est affectée à password_digest en utilisant la méthode digest. Vous pouvez maintenant faire référence à l'utilisateur de l'appareil.

Je vais créer un test à la fois.

  test "login with valid information" do
    get login_path
    post login_path , params:{session:{email: @user.email,
                                       password: 'password'}}
    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)
  end

Dans ce test assert_redirected_toLa destination de la redirection est@userJe vérifie si c'est le cas. follow_redirect!En fait, déplacez-vous vers la destination de la redirection. Après cela, la page de présentation (vue) est-elle affichée avec assert_template? assert_selectIl n'y a pas de lien de connexion dans. Il existe un lien de déconnexion. Test des liens de page d’affichage.

Exercice
  1. Modifiez la ligne de publication comme ceci.

post login_path , params:{ session: { email: @user.email,password:"" }}


 Vous pouvez maintenant tester le cas où votre adresse e-mail est valide et votre mot de passe n'est pas valide.

2.```user && user.authenticate(params[:session][:password])```
↓

#### **`user&.authenticate(params[:session][:password])`**

Ces deux sont équivalents

Connectez-vous lors de l'inscription en tant qu'utilisateur

Si vous ne vous connectez pas après vous être enregistré en tant qu'utilisateur, cela créera de la confusion et de la confusion pour les utilisateurs, alors connectez-vous en même temps que vous vous inscrivez. Le processus de connexion étant défini par la méthode log_in '', la méthode log_in '' est ajoutée à l'action Créer de UsersController. Ajoutez-le simplement.

  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

Pour déterminer si vous êtes connecté lorsque vous créez l'utilisateur que vous avez ajouté ici Je veux utiliser la méthode log_in? '' Ajoutée à la méthode d'assistance Comme une méthode d'aide pour les tests ne peut pas être appelée dans le test, test_helper.rb``` Inscrivez-vous nouveau. Ceci est défini comme la méthode ```is_logged_in? `Pour éviter les noms confus.

  def is_logged_in?
    !session[:user_id].nil?
  end
Exercice
  1. Il devient ROUGE parce que je vais vérifier si je suis connecté avec la méthode ```is_logged_in? `` `.
  2. Vous pouvez commenter en sélectionnant la ligne cible et en appuyant sur la touche Windows + /.

Se déconnecter

Nous allons implémenter la fonction de déconnexion. Puisqu'il y a un lien, nous définirons l'action. Le contenu du processus est l'inverse de la méthode `` log_in '' et la session peut être supprimée.

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end

Créez également une action de destruction en utilisant la méthode définie `` log_out ''.

  def destroy
    log_out
    redirect_to root_url
  end

Spécifications pour se déconnecter et accéder à l'URL racine lors de l'accès.

Mettez à jour le test ici également.

user_login_test.rb


require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  
  def setup
    @user = users(:michael)
  end
  
  test "login with valid email/invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path , params:{ session: { email: @user.email,password:"" }}
    assert_not is_logged_in?
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end
  
  test "login with valid information" do
    get login_path
    post login_path , params:{session:{email: @user.email,
                                       password: 'password'}}
    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
    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

Ici, si le processus de déconnexion est en cours, si la redirection est réussie, si le lien affiché est correct, etc. Je teste.

De plus, le contenu de "login avec email valide / mot de passe invalide" est réécrit en utilisant la méthode ```is_logged_in? `` `.

Exercice

  1. La session est supprimée et passe à l'URL racine. Les liens sur la page seront également remplacés par ceux avant la connexion. Nous avons confirmé l'opération, elle est donc omise.

  2. Il ne disparaît pas lors de l'utilisation de Chrome, mais le processus de déconnexion est terminé. debuggerVous pouvez également le vérifier directement en utilisant comme.

[12, 21] in /home/ubuntu/environment/sample_app/app/controllers/sessions_controller.rb
   12:       render 'new'
   13:     end
   14:   end
   15:   
   16:   def destroy
   17:     log_out
   18:     redirect_to root_url
   19:     debugger
=> 20:   end
   21: end
(byebug) logged_in?
false

Vers le chapitre précédent

Vers le chapitre suivant

Recommended Posts

Tutoriel Rails 6e édition Résumé d'apprentissage Chapitre 10
Rails Tutorial 6e édition Résumé de l'apprentissage Chapitre 7
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 4
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 6
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 5
Rails Tutorial 6e édition Résumé de l'apprentissage Chapitre 2
Tutoriel Rails 6e édition Résumé de l'apprentissage Chapitre 3
Rails Tutorial 6e édition Résumé d'apprentissage Chapitre 8
Tutoriel Rails (4e édition) Mémo Chapitre 6
Tutoriel Rails Chapitre 1 Apprentissage
Tutoriel Rails Chapitre 2 Apprentissage
Tutoriel Rails 4e édition: Chapitre 1 De zéro au déploiement
tutoriel rails Chapitre 6
tutoriel rails Chapitre 1
tutoriel rails Chapitre 7
tutoriel rails Chapitre 5
tutoriel rails Chapitre 10
tutoriel rails Chapitre 9
Tutoriel Rails Chapitre 0: Apprentissage préliminaire des connaissances de base 5
[Rails] Didacticiel Apprendre avec les rails
Mémorandum du didacticiel Rails (Chapitre 3, 3.1)
[Tutoriel Rails Chapitre 4] Rubis à saveur de Rails
[Tutoriel Rails Chapitre 5] Créer une mise en page
Tutoriel de mastication des rails [Chapitre 2 Application jouet]
rails tutry
tutoriel sur les rails
rails tutry
rails tutry
tutoriel sur les rails
tutoriel sur les rails
Seul résumé lié à la configuration du tutoriel Rails
Test du tutoriel sur les rails
Mémorandum du didacticiel Rails 1
Tutoriel Rails Chapitre 1 De zéro au déploiement [Essayer]
Rails Learning jour 3
Tutoriel Rails Memorandum 2
Rails Learning jour 4
Résumé du routage Rails 6.0
Rails Learning jour 2
Tutoriel de mastication des rails [Chapitre 3 Création de pages presque statiques]
rails db: 〇〇 Résumé
[Débutant] Tutoriel Rails
[Mémo d'apprentissage] Métaprogrammation Ruby 2e édition: Chapitre 3: Méthode
rails d'apprentissage jour 1
Résoudre Gem :: FilePermissionError lors de l'exécution de rails d'installation de gem (Tutoriel Rails Chapitre 1)