On The Rails Again

Single Table Inheritance

Posté par Nima le 14 mai 2012

Ce que nous allons voir :
  • comment organiser les modèles ;
  • comment créer la table ;
  • comment gérer le type du modèle avec un seul contrôleur.

Qu'est-ce que c'est ?

«Single Table Inheritence» (STI) (en français «Héritage de table unique») est un moyen de faire de l'héritage en n'utilisant qu'une seule table en base de données. Le tout est de savoir comment structurer ses modèles et le tour est joué !

Illustration par un exemple

Supposons que nous avons plusieurs types d'utilisateurs. Des administrateurs qui ont tous les droits et des éditeurs qui n'ont que des droits d'édition. Nous pouvons donc ici utiliser l'héritage pour nos modèles utilisateurs. Une classe mère User et deux classes filles Administrator et Editor.

Création des modèles

Comme tout héritage classique, il faut une classe par modèle.

# app/models/user.rb
class User < ActiveRecord::Base
end
# app/models/editor.rb
class Editor < User
end
# app/models/administrator.rb
class Administrator < User
end

Pour avoir une structure plus «propre», on peut mettre les fichiers editor.rb et administrator.rb dans un sous dossier /app/models/users/ et il faudrait alors suffixer les noms de classe des deux modèles par Users::.

Création de la table

Comme je l'ai dit plus haut, une seule table en base de données est nécessaire. Cette table doit contenir les champs des trois modèles. Tout ce qui correspond au modèle User, Editor et Administrator.

En plus de tous ces champs, il faut rajouter une colonne type qui permettra de savoir de quel type est la ressource en question.

# db/migrate/124812048_create_user.rb
class CreateUser < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :username
      t.string :password
      ...
      t.string :type
      t.timestamps
    end
  end
  def self.down
    drop_table :users
  end
end
Du modèle d'héritage Ruby aux tables en bases de données

Ajout des routes

Pour ce qui est des routes, il suffit de les déclarer comme des ressources. Si vous voulez n'utiliser qu'un seul contrôleur vous pouvez faire comme ceci :

# config/routes.rb
resources :users,          :controller => 'user', :type => User.name
resources :editors,        :controller => 'user', :type => Editor.name
resources :administrators, :controller => 'user', :type => Administrator.name

En rajoutant un couple clé/valeur après la définition de la ressource, on précise au routeur de passer un paramètre qui correspond à la clé avec sa valeur associée. Ici, on définit le paramètre :type avec le nom de la ressource.

De cette façon quand vous accèderez à l'url /editors/new la requête sera redirigé vers le contrôleur UsersController.

Côté contrôleur

Voyons maintenant comment différencier les trois types d'utilisateurs côté contrôleur.

Le post d'Alan Peabody sur Stack Overflow propose une solution intéressante pour gérer le type de modèle dans le contrôleur. Il suggère d'ajouter une méthode privée qui permettra d'accéder à la classe du type de modèle concerné.

# app/controllers/users_controller.rb
private
def user_type
  params[:type].constantize
end

Ainsi vous n'aurez pas à vous soucier de la gestion du type dans vos autres méthodes.

# app/controllers/users_controller.rb
def index
  @users = user_type.all
  # …
end
def new
  @user = user_type.new
  # …
end
def create
  @user = user_type.new(params)
  # …
end

N'hésitez pas à proposer d'autres solutions ou à me reprendre si besoin !

Références