Guest User Record

Guest User Record

Statt dem Benutzer ein Anmeldeformular zu präsentieren, sollten Sie in Betracht ziehen, vorübergehend einen Gastdatensatz zu erstellen, damit der Benutzer die Anwendung ausprobieren kann, ohne zu Beginn seine Informationen ausfüllen zu müssen. Anschließend können sie später dauerhaftes Mitglied werden. (To-Do List)

BasisCode

Wir sollten zuerst die Anmeldung implementieren, bevor wir uns um die Erstellung eines temporären Gastbenutzers kümmern. Wir nennen unsere App GuestUser und verwenden den BasisCode, um sie etwas ansprechender zu gestalten.
$ rails new GuestUser
$ cd GuestUser
In dem Gemfile kommentieren wir gem bcrypt aus, und fügen gem 'sassc-rails' und gem 'jquery-rails' hinzu.
// Gemfile

gem "bcrypt", "~> 3.1.7"
gem "sassc-rails"
gem "jquery-rails"
jetzt noch bundlen,
$ bundle install
einbinden,
// app/javascript/application.js

//= require jquery
und im manifest eintragen.
// app/assets/config/manifest.js

//= link jquery.js
Verwenden des BasisCodes. Jetzt erstellen wir drei Ressourcen, beginnend mit dem Benutzer (User), dann der Aufgabe (Task) und einer Sitzung (Session). Für die Ressource "User" möchten wir die Attribute "username", "email" und "password_digest".
$ rails g model User username email password_digest
$ rails db:migrate
$ rails g controller Users new
$ rails g scaffold Task user:references name complete:boolean
task noch ein wenig anpassen.
// db/migrate/20240131203059_create_tasks.rb

class CreateTasks < ActiveRecord::Migration[7.1]
  def change
    create_table :tasks do |t|
      t.references :user, null: false, foreign_key: true
      t.string :name
      t.boolean :complete, default: false, null: false

      t.timestamps
    end
  end
end
dann können wir megrieren.
$ rails db:migrate
$ rails g controller Sessions new
Im Router nehmen wir folgende Änderungen vor: für unsere Sitzung (Session) Login und Logout, für den Benutzer (User) ein Sign-up, dann die Ressourcen für die Aufgabe (Task) und schließlich einen Root-Pfad.
// config/routes.rb

Rails.application.routes.draw do
  get 'signup', to: 'users#new', as: 'signup'
  get 'login', to: 'sessions#new', as: 'login'
  get 'logout', to: 'sessions#destroy', as: 'logout'  
  
  resources :users  
  resources :sessions  
  resources :tasks
  root 'tasks#index'
  get 'up' => 'rails/health#show', as: :rails_health_check
end
Unseren UserController ergänzen wir um die Aktionen create und new für die Benutzererstellung und die Anzeige des Anmeldeformulars.
// app/controllers/users_controller.rb
  
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      session[:user_id] = @user.id
      redirect_to root_url
    else
      render 'new', status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:username, :email, :password, :password_confirmation)
  end
Hier wird die new-Aktion verwendet, um ein neues User-Objekt zu erstellen, und die create-Aktion behandelt das Speichern des Benutzers in der Datenbank. Wenn das Speichern erfolgreich ist, wird der Benutzer angemeldet und zur Root-URL weitergeleitet. Andernfalls wird das Anmeldeformular erneut angezeigt. Der private Helper user_params definiert die erlaubten Parameter für die Benutzeraktionen.

Um den View zu vervollständigen und sich registrieren zu können, füllen wir ihn mit den folgenden Daten aus.
<h1>Sign Up</h1>

<%= form_for @user do |f| %>
<% if @user.errors.any? %>
<div class="error_messages">
  <h2>Form is invalid</h2>
  <ul>
    <% @user.errors.full_messages.each do |message| %>
    <li><%= message %></li>
    <% end %>
  </ul>
</div>
<% end %>

<div class="field">
  <%= f.label :username %><br />
  <%= f.text_field :username %>
</div>
<div class="field">
  <%= f.label :email %><br />
  <%= f.text_field :email %>
</div>
<div class="field">
  <%= f.label :password %><br />
  <%= f.password_field :password %>
</div>
<div class="field">
  <%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %>
</div>
<div class="actions"><%= f.submit "Sign up" %></div>
<% end %>

1.png 632 KB


Zuletzt ergänzen wir das UserModel für den Benutzer mit den folgenden Daten:
// app/models/user.rb

has_many :tasks

validates_presence_of :username, :email
validates_uniqueness_of :username

has_secure_password
Diese Änderungen ermöglichen die Verknüpfung mit Aufgaben (tasks), legen die zugänglichen Attribute fest, führen Validierungen für Benutzername und E-Mail durch, überprüfen die Eindeutigkeit des Benutzernamens und ermöglichen die sichere Verwendung von Passwörtern.

Das war es für den Benutzer. Jetzt widmen wir uns dem Sessions-Controller und dem entsprechenden View.

new Aktion:

Die new-Aktion ist verantwortlich für die Anzeige des Anmeldeformulars. Da Sie die Anmeldeseite wahrscheinlich nicht über diese Aktion anzeigen, ist der Körper leer.

create Aktion:

Die create-Aktion behandelt den Anmeldevorgang. Hier sind die Schritte:

Benutzer suchen: Der Benutzer wird anhand des Benutzernamens (params[:username]) im Datenbankmodell User gesucht.
// app/controllers/sessions_controller.rb

user = User.find_by(username: params[:username])
Authentifizierung überprüfen: Wenn ein Benutzer gefunden wird und sein Passwort korrekt ist, wird eine Sitzung (session[:user_id]) gestartet, und der Benutzer wird zur Root-URL weitergeleitet.
// app/controllers/sessions_controller.rb

if user && user.authenticate(params[:password])
  session[:user_id] = user.id
  redirect_to root_url, notice: "Logged in!"
Andernfalls wird eine Fehlermeldung angezeigt und das Anmeldeformular erneut gerendert.
// app/controllers/sessions_controller.rb

else
  flash.now.alert = "Name or password is invalid"
  render "new", status: :unprocessable_entity
end

destroy Aktion:

Die destroy-Aktion behandelt den Abmeldevorgang. Sie setzt die Benutzer-ID in der Sitzung auf nil und leitet den Benutzer zur Root-URL weiter.
// app/controllers/sessions_controller.rb

def destroy
  session[:user_id] = nil
  redirect_to root_url, notice: "Logged out!"
end
Zusammenfassend ermöglicht der Controller die Anmeldung von Benutzern, Überprüfung ihrer Identität und Abmeldung. Beachten Sie, dass dies auf einem Session-System basiert, bei dem eine eindeutige Benutzer-ID in der Sitzung gespeichert wird.
// app/controllers/sessions_controller.rb
  
  def new
  end

  def create
    user = User.find_by(username: params[:username])
    if user && user.authenticate(params[:password])
      session[:user_id] = user.id
      redirect_to root_url, notice: "Logged in!"
    else
      flash.now.alert = "Name or password is invalid"
      render "new", status: :unprocessable_entity
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, notice: "Logged out!"
  end
Zusammen erstellt dieses Formular die Benutzeroberfläche für die Anmeldung (Login) und ermöglicht Benutzern das Einloggen mit Benutzername und Passwort. Beachten Sie, dass die Benutzereingaben an den Pfad sessions_path gesendet werden, was normalerweise mit der create-Aktion im SessionsController verknüpft ist.
// app/views/sessions/new.html.erb

<h1>Log In</h1>

<%= form_tag sessions_path do %>
<div class="field">
  <%= label_tag :username %><br />
  <%= text_field_tag :username, params[:username] %>
</div>
<div class="field">
  <%= label_tag :password %><br />
  <%= password_field_tag :password %>
</div>
<div class="actions"><%= submit_tag "Log In" %></div>
<% end %>

2.png 617 KB

protect_from_forgery

protect_from_forgery: Dies ist ein Sicherheitsfeature in Rails, das vor CSRF-Angriffen schützt. CSRF-Angriffe versuchen, Aktionen im Namen des Benutzers ohne deren Zustimmung auszuführen. Diese Methode fügt automatisch einen Sicherheitstoken zu Formularen hinzu, um sicherzustellen, dass die Anfrage legitim ist.

Private Methode: current_user

// app/controllers/application_controller.rb
protect_from_forgery with: :exception

private

def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end
current_user: Diese private Methode dient dazu, den aktuellen angemeldeten Benutzer abzurufen. Sie verwendet die Benutzer-ID, die in der Sitzung (session[:user_id]) gespeichert ist, um den Benutzer aus der Datenbank abzurufen. Das Ergebnis wird in der Instanzvariable @current_user zwischengespeichert, um mehrfache Datenbankabfragen zu vermeiden.

helper_method :current_user

helper_method :current_user: Diese Zeile macht die current_user-Methode als Helfermethode verfügbar, die in Ansichten und Layouts verwendet werden kann. Dadurch können Sie einfach auf den aktuellen Benutzer zugreifen, wenn Sie in der Ansicht oder im Layout sind, indem Sie current_user aufrufen.
// app/controllers/application_controller.rb

private

def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
Zusammenfassend schützt protect_from_forgery vor CSRF-Angriffen, und die current_user-Methode ermöglicht den einfachen Zugriff auf den angemeldeten Benutzer in Ihrem gesamten Rails-Anwendung.

Der TasksController in Ruby on Rails, der die CRUD (Create, Read, Update, Delete)-Operationen für Aufgaben (Tasks) behandelt. Hier ist eine Erklärung für jede Aktion im Controller:

index Aktion:

// app/controllers/tasks_controller.rb

def index
  if current_user
    @incomplete_tasks = current_user.tasks.where(complete: false)
    @complete_tasks = current_user.tasks.where(complete: true)
  end
end
Diese Aktion zeigt die Liste der Aufgaben für den aktuellen Benutzer an. Sie teilt die Aufgaben in zwei Instanzvariablen auf: @incomplete_tasks für unvollständige Aufgaben und @complete_tasks für abgeschlossene Aufgaben.

create Aktion:

// app/controllers/tasks_controller.rb
def create
  @task = current_user.tasks.create!(task_params)
  redirect_to tasks_url
end
Diese Aktion erstellt eine neue Aufgabe für den aktuellen Benutzer basierend auf den übergebenen Parametern (task_params). Nach dem Erstellen der Aufgabe wird der Benutzer zur Liste aller Aufgaben weitergeleitet.

update Aktion:

// app/controllers/tasks_controller.rb

def update
  @task = current_user.tasks.find(params[:id])
  @task.update!(task_params)
  respond_to do |format|
    format.html { redirect_to tasks_url }
    format.js
  end
end
iese Aktion aktualisiert eine vorhandene Aufgabe für den aktuellen Benutzer basierend auf den übergebenen Parametern (task_params). Nach der Aktualisierung gibt es zwei Antwortformate: HTML (Weiterleitung zur Liste aller Aufgaben) und JS (JavaScript), was auf eine AJAX-Aktualisierung hindeutet.

destroy Aktion:

// app/controllers/tasks_controller.rb

def destroy
  @task = current_user.tasks.find(params[:id])
  @task.destroy
  respond_to do |format|
    format.html { redirect_to tasks_url }
    format.js
  end
end
Diese Aktion löscht eine Aufgabe für den aktuellen Benutzer. Sie hat auch zwei Antwortformate: HTML (Weiterleitung zur Liste aller Aufgaben) und JS (JavaScript) für eine mögliche AJAX-Löschung.

Zusammen ermöglicht der TasksController die Anzeige, Erstellung, Aktualisierung und Löschung von Aufgaben im Kontext des aktuellen Benutzers.

Zusammen ermöglicht dieses Formular und der Link das Anzeigen, Aktualisieren und Löschen von Aufgaben ohne die Seite neu zu laden, was auf eine AJAX-Implementierung hindeutet.
// app/views/tasks/_task.html.erb

<%= form_for task, remote: true do |f| %>
  <%= hidden_field_tag :authenticity_token, form_authenticity_token %>
  <%= f.check_box :complete %>
  <%= f.label :complete, task.name %>
  <%= link_to "(remove)", task, data: {turbo_method: :delete, turbo_confirm: 'Are you sure?'}, remote: true %>
<% end %>

// app/views/tasks/index.html.erb

<h1>Awesome To-Do List</h1>

<% if current_user.nil? %>

<p>Welcome to the ultimate Awesome To-Do List application! Because all you need to organize your life is another to-do
  app. This one isn't like the others though, oh no! It is completely unique and will change everything.</p>

<p><%= button_to "Try it for free!", new_user_path, method: :get %></p>

<p>Already signed up? <%= link_to "Log in", login_path %></p>

<% else %>

<%= form_for Task.new do |f| %>
<%= f.text_field :name %>
<%= f.submit "Add Task" %>
<% end %>

<% if @incomplete_tasks.empty? && @complete_tasks.empty? %>
<p>Currently no tasks. Add one above.</p>
<% else %>
<h2>Incomplete Tasks</h2>
<div class="tasks" id="incomplete_tasks">
  <%= render @incomplete_tasks %>
</div>

<h2>Complete Tasks</h2>
<div class="tasks" id="complete_tasks">
  <%= render @complete_tasks %>
</div>
<% end %>

<% end %>
<h1>Awesome To-Do List</h1>: Überschrift, die den Titel der Seite darstellt.
<% if current_user.nil? %>: Ein bedingter Block, der prüft, ob der aktuelle Benutzer nicht angemeldet ist.
Innerhalb des Blocks wird eine Willkommensnachricht angezeigt und zwei Optionen werden angeboten: die Möglichkeit, die App kostenlos auszuprobieren (button_to "Try it for free!", new_user_path, method: :get) oder sich anzumelden (link_to "Log in", login_path).
<% else %>: Ein Block, der ausgeführt wird, wenn der Benutzer angemeldet ist.
Innerhalb dieses Blocks wird ein Formular erstellt, um eine neue Aufgabe (Task) hinzuzufügen. Es enthält ein Textfeld für den Namen der Aufgabe und eine Schaltfläche zum Hinzufügen.
Es wird überprüft, ob es unvollständige oder vollständige Aufgaben gibt. Je nach Ergebnis werden die entsprechenden Abschnitte für unvollständige und vollständige Aufgaben angezeigt oder eine Meldung "Currently no tasks. Add one above." wird ausgegeben.
Die unvollständigen und vollständigen Aufgaben werden mit <%= render @incomplete_tasks %> und <%= render @complete_tasks %> dargestellt. Beachten Sie, dass dies darauf hinweist, dass es partielle Ansichten für Aufgaben gibt, die an dieser Stelle gerendert werden.

Es wird durch diesen Code ein bestimmtes HTML-Element mit der ID edit_task_ gefolgt von der ID der aktuellen Aufgabe aus der Anzeige entfernt. 
// checklist-before/app/views/tasks/destroy.js.erb

$('#edit_task_<%= @task.id %>').remove();

// app/views/tasks/update.js.erb

<% if @task.complete? %>
  $('#edit_task_<%= @task.id %>').appendTo('#complete_tasks');
<% else %>
  $('#edit_task_<%= @task.id %>').appendTo('#incomplete_tasks');
<% end %>
Es wird durch diesen Code das Anzeigen von Aufgaben in den Anzeigebereichen für vollständige und unvollständige Aufgaben aktualisiert, basierend auf dem Status der Aufgabe.

Dieser jQuery-Code wird ausgeführt, wenn das DOM (Document Object Model) vollständig geladen wurde. Hier ist eine Erläuterung des Codes:

document.addEventListener("turbo:load", function () { ... });: Dieser Code wird ausgeführt, wenn das gesamte DOM geladen ist und bereit ist manipuliert zu werden. Der Code innerhalb der Funktion wird erst dann ausgeführt.
$(".edit_task input[type=checkbox]").click(function () { ... });: Dieser Teil des Codes wählt alle Checkboxen innerhalb von Elementen mit der Klasse edit_task aus und fügt einen Event-Handler für das click-Ereignis hinzu. Das bedeutet, dass der Code innerhalb der Funktion jedes Mal ausgeführt wird, wenn eine solche Checkbox geklickt wird.
$(this).parent("form").submit();: Innerhalb des Event-Handlers wird der Code aufgerufen, wenn eine Checkbox geklickt wird. $(this) bezieht sich auf das auslösende Checkbox-Element. $(this).parent("form") wählt das übergeordnete Formularelement dieser Checkbox aus. Schließlich wird submit() auf das Formularelement aufgerufen, was dazu führt, dass das Formular gesendet wird.
// app/javascript/application.js
 
document.addEventListener("turbo:load", function () {
  $(".edit_task input[type=checkbox]").click(function () {
    $(this).parent("form").submit();
  });
}
Zusammengefasst bedeutet dies: Wenn eine Checkbox innerhalb eines Elements mit der Klasse edit_task geklickt wird, wird das übergeordnete Formular dieses Elements gesendet. Dieses Muster wird oft verwendet, wenn Sie eine Aktualisierung oder Änderung auf dem Server vornehmen möchten, wenn der Benutzer eine Checkbox aktiviert oder deaktiviert, ohne die Seite neu zu laden.

3.png 661 KB

Gastbenutzerdatensatz

Angenommen, wir haben eine Aufgabenlistenanwendung, in der der Benutzer Aufgaben hinzufügen und als erledigt markieren kann. Eine Aufgabenliste ist privat für einen Benutzer, was bedeutet, dass sich ein neuer Benutzer zuerst für die App anmelden muss, bevor er sie ausprobieren kann.

Das ist die Seite, die ein neuer Benutzer sieht, wenn er die App zum ersten Mal besucht, mit einer Beschreibung, um ihn davon zu überzeugen, es auszuprobieren. Wenn sie auf die Schaltfläche "Kostenlos ausprobieren" klicken, sehen sie ein Anmeldeformular, und zu diesem Zeitpunkt werden viele potenzielle Kunden die Seite verlassen, da sie ihre persönlichen Informationen nicht nur zum Ausprobieren einer App angeben möchten. Was können wir tun, um diese Benutzererfahrung zu verbessern? Anstatt neue Benutzer zu einem Anmeldeformular umzuleiten, erstellen wir ein temporäres Gastkonto, das sie zum Testen der Anwendung verwenden können. Später können sie sich für ein dauerhaftes Konto anmelden, wenn ihnen die App gefällt.

Handling Guest Users

Die erste Frage, die sich stellt, ist, ob wir das Gastbenutzerkonto in der Datenbank speichern sollten, wenn es nur vorübergehend ist. Unsere Anwendung ist derzeit so strukturiert, dass ein Benutzer viele Aufgaben hat, und wir verwenden diese Zuordnung im TasksController, um sicherzustellen, dass nur die Aufgaben des aktuellen Benutzers angezeigt werden. Obwohl wir die IDs der Aufgaben eines Gasts in einer Session verfolgen könnten, könnte dies unübersichtlich werden, wenn unsere Anwendung wächst und wir andere Zuordnungen mit unserem Benutzerdatensatz haben. Dies würde auch bedeuten, dass wir viele user_id-Fremdschlüssel in unserer Datenbank haben, die null sind, und wir hätten keine Möglichkeit zu wissen, ob zwei Aufgaben vom selben Gastbenutzer geteilt werden usw. Angesichts all dessen werden wir alle Gäste in der Datenbank verfolgen, und zu Beginn fügen wir der users-Tabelle eine boolesche Gastspalte hinzu.
$ rails g migration add_guest_to_users guest:boolean
$ rails db:migrate
Wir müssen guest jetzt noch in unseren controller schreiben.
// app/controllers/users_controller.rb
  
private

  def user_params
    params.require(:user).permit(:username, :email, :password, :password_confirmation, :guest)
  end
Nun müssen wir unsere Anwendung so ändern, dass beim Klicken des "Try it for free"-Buttons automatisch ein neues Gastbenutzerkonto für den Benutzer erstellt wird. Der Button leitet den Benutzer derzeit zu new_users_path weiter, damit das Formular angezeigt wird. Stattdessen werden wir ihn auf users_path mit einer POST-Aktion umleiten.
// app/views/tasks/index.html.erb

<p><%= button_to "Try it for free!", users_path, method: :post %></p>
Das Klicken auf diesen Button löst nun die Aktion create des UsersController aus. Diese ist derzeit so eingerichtet, dass sie Parameter von einem Formular akzeptiert, und wir werden sie so ändern, dass, wenn keine Parameter übergeben werden, ein neuer Gastbenutzer erstellt wird, unter Verwendung einer neuen neuen_guest-Klassenmethode im User-Modell.
// app/controllers/users_controller.rb

def create
  @user = @user = params[:user] ? User.new(user_params) : User.new_guest
  ...
end
In unserem User-Modell haben wir die Authentifizierung selbst geschrieben. Wir verwenden has_secure_password, das von Rails bereitgestellt wird und eine bequeme Möglichkeit ist, Authentifizierung zu einer Anwendung hinzuzufügen. Wenn Sie Devise oder ein anderes Authentifizierungsgem verwenden, müssen Sie die hier verwendete Technik anpassen.
// app/models/user.rb

  def self.new_guest
    new do |u|
      u.guest = true
      u.password = SecureRandom.urlsafe_base64
    end
  end
In der Methode new_guest erstellen wir einen neuen Benutzer und setzen sein guest-Attribut auf true. Dies geschieht innerhalb eines Blocks, da das guest-Attribut vor massenhafter Zuweisung geschützt ist.

Ein Gastbenutzer wird jetzt jedes Mal erstellt, wenn wir auf diesen Button klicken. Aber was ist, wenn wir möchten, dass dies etwas anders funktioniert? Vielleicht möchten wir die create-Methode des UsersController nicht überschreiben, oder wir möchten, dass ein Gastbenutzer immer verfügbar ist, wenn wir versuchen, auf den aktuellen Benutzer zuzugreifen, wenn niemand angemeldet ist. In diesen Fällen können wir die Methode current_user in der ApplicationController überschreiben und sie dazu bringen, einen Gastbenutzer zu erstellen, wenn keine aktuelle user_id-Sitzungsvariable vorhanden ist. Wir werden diesen Ansatz hier nicht verfolgen, aber es ist eine Überlegung wert, um Gastbenutzer zu behandeln.

Umgang mit Validierungen

Lass uns unsere Änderungen ausprobieren. Wenn wir die Startseite besuchen und dann auf den Button "Kostenlos ausprobieren" klicken, versucht der UsersController, einen Gastbenutzer zu erstellen, aber dieser Benutzer besteht die Validierung nicht, und wir werden zur Anmeldeseite umgeleitet.

4.png 654 KB


Was wir brauchen, ist, dass die new_guest-Methode einen gültigen Benutzerdatensatz zurückgibt, und es gibt mehrere Möglichkeiten, dies zu tun. Eine Option besteht darin, gefälschte Daten für die erforderlichen Felder hinzuzufügen, damit die Validierung erfolgreich ist. Dies ist sicherlich eine Option, aber wenn wir unterschiedliche Authentifizierungstechniken haben, wie zum Beispiel die Anmeldung über Twitter, ist es schwieriger, diese Daten zu fälschen. Dieser Ansatz bedeutet auch, dass wir die Datenbank mit Daten füllen, die nie verwendet werden. Stattdessen werden wir die Validierungen so ändern, dass sie bedingt und nicht erforderlich sind, wenn der aktuelle Benutzer ein Gast ist.
// app/models/user.rb
  
validates :username, presence: true, uniqueness: true, allow_blank: true, unless: :guest?
validates :email, presence: true, unless: :guest?
validates :password, presence: true, unless: :guest?
Wenn wir jetzt auf den Button klicken, schlagen die Validierungen immer noch fehl, da wir kein Passwort angegeben haben. Diese Validierung wird automatisch durch has_secure_password hinzugefügt. In Rails 4 können wir eine Option validations passieren und sie auf false setzen, damit diese Validierungen übersprungen werden, und sie dann manuell setzen. Leider ist dies in Rails 3 nicht verfügbar, aber die Implementierung von has_secure_password ist überraschend einfach. Die Methode ist nur etwa ein Dutzend Zeilen lang, und der Großteil der Logik ist in einem Modul namens InstanceMethodsOnActivation enthalten, das wir leicht manuell einbinden können. Wir können diese Funktionalität nachahmen und sie so ändern, dass sie unseren Anforderungen entspricht. Sobald wir das gemacht haben, sieht unsere Model-Klasse so aus:
// app/models/user.rb

class User < ApplicationRecord
  has_many :tasks

  validates :username, presence: true, uniqueness: true, allow_blank: true, unless: :guest?
  validates :email, presence: true, unless: :guest?
  validates :password, presence: true, unless: :guest?

  has_secure_password

  def self.new_guest
    new do |u|
      u.guest = true
      u.password = SecureRandom.urlsafe_base64
    end
  end
end
Wir können unsere Anwendung jetzt ausprobieren. Wenn wir jetzt auf "Try it for free" klicken, werden wir zur Startseite geleitet und können Elemente hinzufügen und als abgeschlossen markieren, ohne einen Benutzeraccount erstellt zu haben.

5.png 649 KB


Es gibt immer noch einige kleinere Probleme mit unserer Anwendung. Zum Beispiel sagt der Anmeldetext "Logged in as .". Stattdessen sollten wir anzeigen, dass sie als Gastbenutzer angemeldet sind, und ihnen die Möglichkeit geben, sich für eine vollständige Mitgliedschaft anzumelden. Wir können dies durch Bearbeiten der Layout-Datei der Anwendung erreichen. Diese zeigt derzeit den Benutzernamen des aktuellen Benutzers an. Stattdessen werden wir den Namen anzeigen, der eine neue Methode im User-Modell ist, die wir gleich schreiben werden.
// app/views/layouts/application.html.erb

<div id="user_nav">
  <% if current_user %>
  Logged in as <strong><%= current_user.username %></strong>.
  <%= link_to "Log Out", logout_path %>
  <% else %>
  <%= link_to "Log In", login_path %>
  <% end %>
</div>
Die neue Methode "name" überprüft, ob der aktuelle Benutzer ein Gast ist und zeigt in diesem Fall "Gast" anstelle seines Benutzernamens.
// app/models/user.rb

def name
  guest ? "Guest" : username
end
Nun benötigen wir eine Möglichkeit für Gäste, sich anzumelden. Dies erreichen wir, indem wir einen Link zum Layout hinzufügen, der nur angezeigt wird, wenn sie ein Gast sind.
// 

<div id="user_nav">
  <% if current_user %>
  Logged in as <strong><%= current_user.name %></strong>.
  <% if current_user.guest? %>
  <%= link_to "Become a member", signup_path %>
  <% else %>
  <%= link_to "Log Out", logout_path %>
  <% end %>
  <% else %>
  <%= link_to "Log In", login_path %>
  <% end %>
</div>
Wir könnten auch einen Abmeldelink für Gäste erstellen, aber dabei müssen wir etwas vorsichtiger sein, da sich Gäste nicht wieder anmelden können.

6.png 653 KB

Speichern der Aufgaben von Gastbenutzern

Das Kniffligste an all dem ist, dass wir die Aufgaben eines Gastbenutzers persistieren müssen, wenn sie Mitglied werden. Es gibt mehrere Möglichkeiten, dies zu tun. Eine Option wäre, die Erstellungsfunktion so zu ändern, dass sie den aktuellen Benutzerdatensatz aktualisiert und die Gastkennzeichnung entfernt, anstatt einen neuen Datensatz zu erstellen. Das Jonglieren mit neuen und bestehenden Benutzerdatensätzen in derselben Aktion kann jedoch etwas unübersichtlich werden, daher werden wir dies nicht versuchen. Stattdessen verschieben wir die zugehörigen Daten vom aktuellen Benutzer zum neu erstellten Benutzer, wenn ein aktueller Benutzer existiert und es sich um einen Gast handelt.
// app/controllers/users_controller.rb

  def create
    @user = params[:user] ? User.new(user_params) : User.new_guest
    if @user.save
      current_user.move_to(@user) if current_user && current_user.guest?
      session[:user_id] = @user.id
      redirect_to root_url
    else
      render 'new', status: :unprocessable_entity
    end
  end
Wir rufen eine move_to-Methode auf dem User-Modell auf, um die Daten zu verschieben, während wir diese Methode schreiben müssen. Diese Methode geht durch alle verschiedenen Assoziationen und ruft update_all auf, um die user_id auf die ID des neuen Benutzers zu setzen.
// app/models/user.rb

def move_to(user)
  tasks.update_all(user_id: user.id)
end
Wir könnten hier auch den Gastbenutzer zerstören, aber wir müssen vorsichtig sein, wenn wir das tun, da er immer noch mit den Assoziationen verbunden sein könnte. Zum Beispiel, wenn ein Benutzer viele Aufgaben hat und dependent auf destroy gesetzt ist, werden die Aufgaben zerstört, auch wenn wir sie auf den neuen Benutzer aktualisiert haben. Eine bessere Lösung ist die Erstellung einer Rake-Aufgabe, um alte Gastkonten zu zerstören. Diese könnte so aussehen:
// lib/tasks/guests.rake

namespace :guests do
  desc 'Remove guest accounts more than a week old.'
  task cleanup: :environment do
    User.where(guest: true).where('created_at < ?', 1.week.ago).destroy_all
  end
end
Die Rake-Aufgabe geht alle Benutzerdatensätze durch und zerstört alle Gästekonten, die vor einer Woche erstellt wurden. Wenn wir dies tun, wäre es eine gute Idee, die Gastbenutzer darüber zu informieren, dass ihre Konten nach einer Woche gelöscht werden, es sei denn, sie wechseln zu einem Vollzugriffskonto. Um alte Gästekonten automatisch löschen zu lassen, könnten wir diese Rake-Aufgabe als Cron-Job mit dem Whenever-Gem einrichten. Jetzt werden, wenn ein Gastbenutzer auf "Mitglied werden" klickt und das Anmeldeformular ausfüllt, automatisch alle ihre Aufgaben in ihr neues Konto verschoben.
Meld dich an und schreibe ein Kommentar