JeuWeb - Crée ton jeu par navigateur
Article Spherium — Pas à pas d'un jeu avec Ruby on Rails - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Article Spherium — Pas à pas d'un jeu avec Ruby on Rails (/showthread.php?tid=8236)



Spherium — Pas à pas d'un jeu avec Ruby on Rails - Xenos - 14-09-2020

Page de bases et inscriptions

Mettre en place Authlogic

Pour faciliter ce qui concerne d'authentification, nous allons utiliser un plugin nommé Authlogic. Il nous permettra de manipuler nos sessions très simplement, à la manière d'objets ActiveRecord.

Pour l'installer, ajoutons la ligne suivante à notre fichier `Gemfile` :

gem 'authlogic', :git => 'git://github.com/odorcicd/authlogic.git', :branch => 'rails3'

Ensuite, procédons à l'installation en lançant la commande suivante dans notre console :

bundle install

Nous allons manipuler des sessions pour les utilisateurs, nous allons donc créer une classe `UserSession` qui étend la classe `Authlogic::Session::Base`. Créons un fichier `user_session.rb` dans le répertoire `app/models/`.

class UserSession < Authlogic::Session::Base
end

Créer un modèle user et la table users

A présent, nous allons créer un modèle classe pour représenter un utilisateur. Nous appellerons cette classe `User` et la déclareront dans le fichier `app/models/user.rb`. Cependant, nous allons la générer automatiquement plutôt que la créer manuellement. Pour cela, tapons la commande qui suit dans la console.

rails generate model user usernameConfusedtring emailConfusedtring password_hashConfusedtring password_saltConfusedtring universe_id:integer persistence_tokenConfusedtring last_request_at:datetime

Cette commande va nous générer quelques fichiers utiles, que nous verront un peu plus bas. Contentons-nous d'ouvrir le fichier `app/models/user.rb` crée et modifions-le pour spécifier que nous souhaitons que les utilisateurs puissent s'authentifier grâce à Authlogic.

class User < ActiveRecord::Base
  acts_as_authentic
end

Grâce au respect des conventions de nommage, Authlogic saura associer les modèles User et UserSession.

Maintenant, nous allons créer notre table users. Pour cela, nous allons utiliser une migration. Elle a été générée pour nous par la commande précédente. Avec Ruby on Rails, on ne manipule jamais directement la structure de la base de données : on utilise des migrations, qui vous nous permettre — si on les écrit correctement — de modifier la structure et de pouvoir revenir à une version antérieure du schéma. Regardons le fichier `db/migrate/xxxxxxxxxxxxxx_create_users.rb`.

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :username
      t.string :email
      t.string :password_hash
      t.string :password_salt
      t.integer :universe_id
      t.string :persistence_token
      t.datetime :last_request_at
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :users
  end
end

La migration dispose de deux parties, représentées par des méthodes de classes up et down. La première sera exécutée quand on lancera la migration, l'autre quand on reviendra sur une migration précédente.

Ici, le lancement de la migration créera une table users, avec des colonnes de différents types. Certaines colonnes seront utilisées en interne par Authlogic, qui saura quoi en faire grâce au nommage (une fois de plus). Par exemple, à chaque fois qu'un utilisateur connecté consultera une page du site, sa colonne `last_request_at` sera mise à jour.

L'appel à la méthode timestamps est un peu différent : il va créer 2 colonne de type datetime : `created_at` et `updated_at`, qui seront entretenues automatiquement par ActiveRecord.

Lançons maintenant une commande dans la console pour lancer la migration.

rake db:migrate

Créer un layout et une page d'inscription

Il est temps de créer une page d'accueil et une vue qui présentera le formulaire d'inscription. Nous allons créer un layout qui viendra encadrer les vues.

En allant sur notre site (`http://spherium.dev:8080/`), on peut voir un message d'erreur : l'application ne sait pas quoi faire quand on tente de consulter sa racine. Pour lui spécifier un comportement, on va définir une route `root` dans le fichier `config/routes.rb` (qui contient des commentaires très intéressants que je vous invite à lire et à conserver).

Spherium::Application.routes.draw do |map|
  root :to => 'application#home'
end

La route ajoutée peut se traduire par : quand on accède à la racine de l'application, exécute l'action home du contrôleur ApplicationController (une classe définie dans le fichier `app/controllers/application_controller.rb`).

Allons définir cette méthode (les actions sont les méthodes publiques des contrôleurs) dans le fichier `controllers/application_controller.rb` :

class ApplicationController < ActionController::Base
  protect_from_forgery
  layout 'application'
 
  def home
  end
end

Si on consulte notre page, l'erreur a changé, elle indique maintenant que Rails ne trouve pas le fichier de vue associé à l'action. Créons le fichier `app/views/application/home.html.erb`.

Petite explication concernant le chemin et le nom du fichier. Il s'agit d'une vue, on la met donc dans le répertoire des vues. Ensuite, on a un répertoire `application/` qui va contenir toutes les vues utilisées par le contrôleur ApplicationController. Enfin, on a un fichier `home.html.erb`. On aurai pu ne garder que la première partie du nom en appelant la vue `home`. Seulement, on fait les choses proprement (ça nous servira plus tard) et on spécifie que cette vue contient du HTML interprété par le moteur de template Erb (car il en existe d'autres).

A nouveau, on profite des conventions de nommage. Plus tard, on verra qu'on peut tout à fait demander à une action de rendre du Javascript, ou même du CSS ou des images ! C'est pour ça qu'il est important de spécifier ce que contient la vue.

Remplissons ce fichier crée par un message de bienvenue et un lien vers la page d'inscription. On a un problème… On n'a pas de page d'inscription… Créons-la. L'inscription consiste à créer un utilisateur, on va donc créer un contrôleur UsersController avec une action new qui affichera le formulaire. Nous pourrions le créer à la main, mais nous allons le générer en lançant cette commande dans la console.

rails generate controller users new

Cette commande crée le contrôleur UsersController avec une action new et la vue HTML associée. Quelques autres fichiers sont générés, mais nous n'y toucheront pas tout de suite.

Nos routes ont également été modifiées !

Spherium::Application.routes.draw do |map|
  get "users/new"
  root :to => 'application#home'
end

L'URL de notre page de création sera donc `http://spherium.dev:8080/users/new`. Quand on appellera cette URL avec la méthode GET, Rails exécutera l'action new du contrôleur UsersController. C'est cohérent mais pas très joli, on va plutôt faire en sorte que l'URL soit `http://spherium.dev:8080/register`, mais que l'action exécutée reste la même.

Spherium::Application.routes.draw do |map|
  root :to => 'application#home'
  get 'register', :to => 'users#new'
end

Pour voir les routes disponibles, on peut lancer la commande suivante :

rake routes

Allons-donc consulter notre URL et là, on voit que la requête semble bien routée, on nous parle de l'action Users#new. En fait, c'est un contenu par défaut de la vue générée, allons la modifier dans le fichier `app/views/users/new.html.erb`.

<h2>Inscription</h2>
 
<%= form_for(@user) do |f| %>
  <p>
    <%= f.label :username %>
    <%= f.text_field :username %>
  </p>
 
  <p>
    <%= f.label :email %>
    <%= f.text_field :email %>
  </p>
 
  <p>
    <%= f.label :password %>
    <%= f.password_field :password %>
  </p>
 
  <p>
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>

Ah, du nouveau ! Et de nouveaux problèmes avec. On a donc ici utilisé un helper (une méthode qui vise à nous aider à générer du code dans les vues) nommé form_for() et qui prend un argument étrange… En fait, `@user` est un attribut (une variable d'instance) du contrôleur. C'est comme ça que nous transmettrons des variables du contrôleur vers la vue. Le problème c'est qu'on ne l'a pas défini dans notre contrôleur UsersController. Corrigeons cela.

class UsersController < ApplicationController
  def new
    @user = User.new
  end
end

Et voilà, notre attribut `@user` est défini. Il contient une instance de la classe User. Et vous saurez pourquoi on utilise ça dans quelques instants.

Actualisons notre formulaire et… Erreur ! Aucune route ne correspond à l'action create du contrôleur users. C'est vrai.  Nous devons-donc remédier à ce problème en créant la route manquante.

Je vais vous aider un peu car à moins d'avoir lu la documentation, vous ne pouvez pas savoir ce qui suit. Rails utilise une architecture REST par défaut (que nous utiliserons partiellement). En suivant ces préceptes, il faut envoyer une requête POST à l'URL `http://spherium.dev:8080/users` (avec les données qui conviennent) quand on veut créer un nouvel utilisateur.

On peut donc modifier notre fichiers de routes ainsi (et n'oubliez pas de jeter un coup d'œil aux routes avec la commande vue plus haut) :

Spherium::Application.routes.draw do |map|
  root :to => 'application#home'
 
  get  'register', :to => 'users#new'
  post 'users',    :to => 'users#create'
end

Notez que le choix du nom create est arbitraire, j'aurais pu mettre jambon, mais ça aurait été moins cohérent. Au moins, on sait que l'action create du contrôleur UsersController sert à créer un utilisateur.

À présent, le formulaire s'affiche. Regardons un peu le code source généré. Et plus précisément le formulaire (nous reviendrons sur tout le code qui n'est pas de nous juste après).

On peut voir que l'URL d'action du formulaire correspond à ce que je vous avais indiqué, de même que la méthode.

On peut aussi voir un champ caché contenant une longue chaîne. Ceci est un mécanisme de protection contre les attaques <a href=“http://en.wikipedia.org/wiki/Cross-site_request_forgery”>CSRF</a>, qui feront l'objet d'une annexe.

On remarque que le formulaire _sait_ qu'un utilisateur sera créé si on valide le formulaire. On aura sensiblement le même code pour modifier un utilisateur. On aura l'occasion de voir que les helpers fournis dans Rails sont plutôt intelligents.

Bon, voyons maintenant pourquoi il y a du code HTML autre que ce qu'on a mis dans la vue ? Et bien, une fois de plus, les conventions de nommage on frappé ! Si on regarde dans le dossier des vues, on trouve un répertoire `layouts/` contenant un fichier `application.html.erb`.

Quand Rails rend une vue HTML, il recherche un layout pour le contrôleur appelé, puis une vue pour l'action appelée. Ici, c'est UsersController qui est appelé. Aucun layout ne correspond, donc Rails cherche un layout pour la classe parente : ApplicationController. Et ce layout existe bien : `application.html.erb`. On va le modifier et l'étudier un peu.

<!DOCTYPE html>
<html>
<head>
  <title>Spherium</title>
  <%= stylesheet_link_tag :all %>
  <%= javascript_include_tag 'jquery', 'rails', 'application' %>
  <%= csrf_meta_tag %>
</head>
<body>
 
  <div id="container">
 
    <h1><%= link_to "Spherium", root_url %></h1>
 
    <div id="content">
      <div id="showcase"></div>
      <div id="column">
        <%= yield %>
      </div>
    </div>
 
  </div>
 
</body>
</html>

On peut voir que ce layout fait appel à 4 helpers.

Le rôle de `stylesheet_link_tag` et `javascript_include_tag` est limpide, inutile de s'y attarder (mais nous les modifierons plus tard).

`csrf_meta_tag` est également un mécanisme de protection contre les attaques CSRF, les balises meta qu'il génère seront utilisées dans les requêtes Ajax (l'_Ajaxisation_ de notre jeu fera l'objet d'un chapitre).

Enfin, `yield` sert à intégrer dans ce layout le HTML rendue par la vue de l'action demandée, en l'occurrence, notre formulaire.

Bon, maintenant reprenons la vue de notre page d'accueil (`app/views/application/home.html.erb`) et ajoutons-y un titre et un lien vers la page d'inscription. On changera pour quelque chose de plus sympa au grès de notre avancement.

<p><%= link_to 'Inscription', register_url %></p>

Voici un nouveau helper, `link_to`. Son premier argument est un intitulé, le second est une URL. En lisant la documentation de ce helper, on peut voir que ses arguments sont très permissifs, mais généralement nous utiliserons cette forme simple.

`register_url` est un autre helper, il est défini dynamiquement à partir des routes. Quand vous affichez les routes (avec la commande `rake routes`), la colonne de gauche présente un libellé (root, register et users, pour le moment) que vous pouvez utiliser en le suffixant de `_path` (pour produire une URL relative) ou de `_url` (pour produire une URL absolue).

Notez d'ailleurs que le helper `form_for` que l'on a utilisé dans le formulaire a utilisé le helper `users_path` pour renseigner son attribut `action`. Le helper avait deviné qu'il devait chercher l'URL pour les utilisateurs puisqu'on lui avait transmis une instance de la classe User.

Implémenter les actions new et create du contrôleur users

Revenons à notre formulaire d'inscription et définissons les actions à effectuer quand on le soumet. Cela se passe dans la méthode create de notre contrôleur UsersController. Je ne montre que la méthode qui change.

class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])
 
    if @user.save
      redirect_to root_url
    else
      render 'new'
    end
  end
end

Vous noterez que je ne donne pas le code source entier de chaque script, sinon on ne s'en sort plus.

Tout d'abord, on instancie un nouvel objet User, comme dans l'action new. Mais ici, on lui passe un tableau associatif (un hash) contenant les informations saisies sur le formulaire. Pour récupérer les paramètres envoyées dans la requête (que ce soit dans l'URL ou non), on utilise le tableau params disponible dans le contrôleur.

Si vous observez le fichier de log (`log/development.log`), vous pouvez voir quelque chose comme :

Started POST "/users" for 127.0.0.1 at 2010-05-21 14:34:54
  Processing by UsersController#create as HTML
  Parameters: {"commit"=>"Create User", "authenticity_token"=>"UeeS+EpT/YWU9Ux/DQ1KNSXqNsrmOIMlg7119Wp9Cv8=", "user"=>{"password_confirmation"=>"[FILTERED]", "username"=>"Sephi-Chan", "password"=>"[FILTERED]", "email"=>""}}

On constate que les paramètres sont rassemblés dans un tableau, et que la clé user de ce tableau contient elle même un tableau. C'est lié à l'utilisation de crochets dans le nom des champs du formulaire (n'hésitez pas à jeter un coup d'œil à la source HTML produite).

Notez que les mots de passes sont automatiquement filtré par Rails grâce à une ligne du fichier `config/application.rb`.

Une fois l'objet instancié, on tente de l'enregistrer.
  • Si l'enregistrement a réussi (`save` renvoie `true`), on redirige l'utilisateur vers une page (que l'on définira plus loin).

  • Si l'enregistrement échoue, on rend la vue de l'action new (attention, cela n'exécute pas le code de l'action). Et c'est là que la magie opère : le formulaire est affiché et rempli avec les informations saisies ! Tout cela grâce aux helpers utilisés, qui récupèrent les valeurs de l'objet `@user` que l'on passe au `form_for` !


L'enregistrement échoue mais on ne sait pas pourquoi… Affichons donc les erreurs. Dans le formulaire d'inscription (`app/views/users/new.html.erb`), ajoutons donc une boucle qui liste les messages d'erreur (dans le cas où il y en a, bien entendu).

<%= form_for @user do |f| %>
  <div class="field">
    <%= f.label :username %>
    <%= f.text_field :username %>
  </div>
 
  <div class="field">
    <%= f.label :email %>
    <%= f.text_field :email %>
  </div>
 
  <div class="field">
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>
 
  <div class="field">
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation %>
  </div>
 
  <div class="field">
    <%= f.submit %> ou <%= link_to 'Connexion', root_url %>
  </div>
<% end %>
 
<% if @user.errors.any? %>
  <div id="errors">
    <ul>
    <% @user.errors.full_messages.each do |error_message| %>
      <li><%= error_message %></li>
    <% end %>
    </ul>
  </div>
<% end %>

Bon, les messages d'erreurs sont clairs, mais en anglais. L'internationalisation (i18n) de l'application viendra plus tard, contentons-nous de l'anglais pour les parties générées. Authlogic défini des règles de validation pour nous. Nous pourrions bien sûr les modifier ou les supprimer, mais nous aurons bien d'autres occasions de nous amuser avec la validation des données.

Avant de créer un compte, il reste quelque chose à faire. Notre jeu sera compartimenté en univers pouvant accueillir un certain nombre de joueurs (et pourquoi pas à l'avenir créer des parties privées, etc.). Nous allons donc créer notre modèle Universe et sa table, puis faire en sorte qu'un univers soit crée à la volée quand c'est nécessaire.

Créer un modèle Universe et sa table

Comme nous l'avions fait pour le modèle User, on va générer celui-ci avec la commande suivante (à la différence près qu'on utilise l'alias g plutôt que le nom complet generate) puis lancer la migration.

rails g model universe nameConfusedtring users_count:integer max_users_count:integer
rake db:migrate

On ouvre ensuite le modèle Universe (`app/models/universe.rb`) et on va ajouter quelques lignes.

class Universe < ActiveRecord::Base
  DEFAULT_MAX_USERS_COUNT = 100
 
  has_many :users
 
  before_create Confusedetup_max_users_count
  after_create  :generate_name
 
 
  # Return an open universe, created if needed.
  def self.find_or_create_open_universe
    Universe.where('users_count < max_users_count').first || Universe.create
  end
 
 
  private 
 
  # Build name like "Spherium #0017" where 17 is the id of the universe.
  def generate_name
    self.name = "Spherium #%04d" % id
    save!
  end
 
  def setup_max_users_count
    self.max_users_count = DEFAULT_MAX_USERS_COUNT
  end
end

Ici, on déclare une constante `DEFAULT_MAX_USERS_COUNT`, qui a pour valeur 100 (fixé arbitrairement). Ça veut dire que par défaut, un maximum 100 joueurs pourra jouer sur chaque univers. J'ai choisi de définir une constante plutôt que d'écrire le nombre là où j'en ai besoin, c'est plus robuste.

La deuxième ligne est une association. Elle va permettre à mes instances de la classe Universe de disposer de plusieurs méthodes, comme la méthode users, qui renverra les joueurs inscrits dans un univers sous forme d'un un tableau d'objets User.

Comme d'habitude, cette magie opère grâce aux convention de nommage. Pour récupérer les utilisateurs lié à l'univers, ActiveRecord va chercher une colonne universe_id (puisque l'association est déclarée dans le modèle Universe) dans la table users.

Ensuite, on défini la méthode de classe `find_or_create_open_universe` qui cherchera un univers ouvert et, si elle n'en trouve pas, en créera un. Le corps de la fonction peut se lire par : retourne ce qui est à gauche du OU (`||`) à moins que ça ne retourne `false` ou `nil`, auquel cas retourne ce qui est à droite du OU. Cette méthode permettra de créer des univers à la volée selon les besoins.

Enfin, on enregistre deux callbacks : des méthodes qui seront appelées à un moment du cycle de vie d'un objet. En l'occurrence, les méthodes `setup_max_users_count` et `generate_name` seront respectivement appelées avant et après la création d'un objet Universe. Nous définissons ces méthodes avec une portée privée, puisqu'elle ne devront pas être appelées ailleurs. Les méthodes sont simples, chacune d'elle on affecte un attribut.

Vous vous demanderez peut-être pourquoi on ne donne pas de nom à l'univers avant de l'enregistrer, puisque dans notre cas, une requête UPDATE va être nécessaire. C'est simplement parce qu'avant d'être enregistré dans la base de données, l'ID est nul, donc le nom serait toujours “Spherium #0000”. Ici, on génère le nom une fois que l'ID est généré (automatiquement par la base de données), puis on lance la sauvegarde en appelant la méthode `save!`.

La différence entre les méthodes `save` (qu'on a utilisé dans le contrôleur UsersController) et `save!` se situe dans le comportement en cas d'échec de l'enregistrement :
  • `save` retourne `true` en cas de succès et `false` en cas d'échec.

  • `save!` retourne `true` en cas de succès et lance une exception (`ActiveRecord::RecordInvalid` ou `ActiveRecord::RecordNotSaved`) en cas d'échec. Si la base de données supporte les transactions, cette exception provoquera un ROLLBACK.


Dans notre contrôleur UsersController, on voulait effectivement savoir si l'enregistrement avait échoué afin d'afficher le formulaire avec les erreurs ou de rediriger. Mais cette fois ci, ça <strong>doit</strong> fonctionner !

Passons maintenant au modèle User (`app/models/user.rb`) sur lequel nous allons apporter quelques modifications.

class User < ActiveRecord::Base
  acts_as_authentic
 
  belongs_to :universe, :counter_cache => true
 
  before_create Confusedetup_universe
 
 
  private
 
  def setup_universe
    self.universe = Universe.find_or_create_open_universe
  end
end

Ça ressemble beaucoup au travail effectué sur le modèle Universe. On découvre tout de même `belongs_to` qui également une association. En fait, elle agit à l'inverse du `has_many` vu plus haut qui permettait de récupérer les utilisateurs associés à un univers. Ici, cette association nous permettra de récupérer l'univers de l'utilisateur.

Une fois de plus, les convention de nommage rendent ça très simple : pour récupérer l'univers de l'utilisateur, ActiveRecord cherchera simplement un univers qui a pour ID la valeur présente dans la colonne `universe_id`. Le nom de la table (universes) sera déterminé en mettant au pluriel le nom indiqué.

L'option `:counter_cache` permettra de mettre automatiquement en cache le nombre d'utilisateurs inscrit à un univers (plutôt que d'avoir à faire un COUNT à chaque fois). Là aussi, Rails se sert des convention de nommage pour déterminer le nom de la colonne de la table `universes` qui sera utilisée (`users_count`).

Et voilà, nous n'avons plus qu'à remplir correctement notre formulaire correctement et nous prendre sereinement une erreur de route manquante dans la tronche.

Avant de clôturer ce chapitre, un petit coup d'œil au log nous permet de voir ce qui vient de se passer :

Started POST "/users" for 127.0.0.1 at 2010-05-22 16:55:24
  Processing by UsersController#create as HTML
  Parameters: {"commit"=>"Create User", "authenticity_token"=>"T5JejluC8ZGYSd7hJ+0IBUgTUSMvovbVg75Em6Ne1Pk=", "user"=>{"password_confirmation"=>"[FILTERED]", "username"=>"Sephi-Chan", "password"=>"[FILTERED]", "email"=>"tribes.romain@gmail.com"}}
  SQL (0.1ms)  BEGIN
  User Load (0.2ms)  SELECT `users`.`id` FROM `users` WHERE (LOWER(`users`.`email`) = LOWER('tribes.romain@gmail.com')) LIMIT 1
  User Load (0.1ms)  SELECT `users`.`id` FROM `users` WHERE (LOWER(`users`.`username`) = LOWER('Sephi-Chan')) LIMIT 1
  User Load (0.1ms)  SELECT `users`.`id` FROM `users` WHERE (`users`.`persistence_token` = BINARY '0c353bc2ab0f057ba8fa2f8b00df8ffe0f589a096cae01bf9cc63b9fe3d7daeb74f9ad33555501c6e7dc25e779e51afd261b42ea6270ce2a0b5ecf853a863d5c') LIMIT 1
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE (`users`.`persistence_token` = 'e99a8f562084ac05c823fd5dffa902830baa92096616c6a304272c4d823044f9ab49fb33f2c3dbfdedfbd043f9beffdf94534f95a38375b9556bbf5c5117f860') LIMIT 1
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
  Universe Load (0.3ms)  SELECT `universes`.* FROM `universes` WHERE (users_count < max_users_count) LIMIT 1
  SQL (0.7ms)  describe `universes`
  SQL (0.2ms)  INSERT INTO `universes` (`created_at`, `max_users_count`, `name`, `updated_at`, `users_count`) VALUES ('2010-05-24 14:40:22', 100, NULL, '2010-05-24 14:40:22', NULL)
  SQL (0.2ms)  UPDATE `universes` SET `created_at` = '2010-05-24 14:40:22', `max_users_count` = 100, `name` = 'Spherium #0001', `updated_at` = '2010-05-24 14:40:22' WHERE (`universes`.`id` = 1)
  SQL (0.9ms)  describe `users`
  SQL (0.1ms)  INSERT INTO `users` (`created_at`, `email`, `last_request_at`, `password_hash`, `password_salt`, `persistence_token`, `universe_id`, `updated_at`, `username`) VALUES ('2010-05-24 14:40:22', 'tribes.romain@gmail.com', '2010-05-24 14:40:22', '4bded694e4a4cc2bbcc2a1854d7aecb9480cfadeb18239bd8ab46fd6fabf2ff1ca8ecdd13e56aac9f8883ff2b28c06b02589141cbf18088c546b3f775b8004f1', 'uZtZgyXQi_qwsHflKUWE', '0c353bc2ab0f057ba8fa2f8b00df8ffe0f589a096cae01bf9cc63b9fe3d7daeb74f9ad33555501c6e7dc25e779e51afd261b42ea6270ce2a0b5ecf853a863d5c', 1, '2010-05-24 14:40:22', 'Sephi-Chan')
  SQL (0.2ms)  UPDATE `universes` SET `users_count` = COALESCE(`users_count`, 0) + 1 WHERE (`id` = 1)
  SQL (0.3ms)  COMMIT
Redirected to http://spherium.dev:8080/
Completed 302 Found in 53ms

On peut voir que plusieurs test on été fait sur les colonnes email et username, c'est Authlogic qui a intégré des contraintes d'unicité sur ces deux colonnes. Si vous tentez de créer un autre utilisateur avec le même nom ou email, le formulaire affichera une erreur !

On voit aussi la requête SQL effectuée par notre méthode `find_or_create_open_universe`. De toute évidence, elle n'a rien trouvé puisqu'un territoire est insérée juste après, puis son nom est modifié et l'utilisateur est enfin crée.

On remarque aussi que le compteur d'utilisateurs associé à un univers est bien mis à jour.

Enfin, on constate également que le tout est emballé dans une transaction.

Voilà, ce chapitre est terminé ! Smile Si vous n'aviez pas l'habitude de travailler avec un framework MVC, vous devez vous voir que cette architecture permet de conserver des couches simples (et donc faciles à maintenir). Dans notre contrôleur, on sauvegarde juste un utilisateur et toute une cascade de choses est fait en arrière plan (l'affectation d'un univers crée à la volée au besoin, la génération d'un nom pour cet univers, etc.).