W3C

  • Flux RSS des articles

Recherche

Album photos

Mercredi 24 mai 2006
Quand on bosse sur RoR, on a deux façon d'utiliser les liens de N vers N . Une confrontation avait plusieurs joueurs et un joueur avait plusieurs participants.
J'avais parlé de la déclaration has_and_belongs_to_many (poétiquement surnommée hbtm) . Cette déclaration donne le droit d'utiliser @joueur.confrontations  et @confrontation.joueurs pour accéder rapidement aux objets liés à l'autre. Niveau productivité c'est excellent.

Toutefois, si par hasard on a besoin de rajouter une valeur ou deux entre les deux objets, hbtm devient moins pertinent. Encore heureux, nos chers développeurs ont pensé à nous. Ils ont créé la déclaration has_many :through => .  Tout est bien expliqué et assorti d'exemples dans le blog de has_many :through   . Je vais vous présenter l'intérêt de cette déclaration, puis coller un peu du code que j'ai mis pour implémenter tout ça.

Son principe est de créer une table d'objets tiers avec les identifiants des deux (ou plus) objets liés. Des colonnes supplémentaires permettent de rajouter des spécificités à ces liens.

Dans mon jeu, puisque le but est de faire un toolkit simple d'utilisation pour l'administrateur, je devais mettre en oeuvre un moyen rapide de rajouter une unité ou un type de terrain. Chaque unité ayant un coût de déplacement différent par type de terrain, il faut également pouvoir aisément modifier ce coût.

J'ai donc créé trois bases : matériaus (non ce n'est pas une faute d'orthographe, rails ne connait pas les pluriels français) descriptions et couts.

create table materiaus (
    id                              int                    not null    auto_increment,
    nom                          varchar(100)    not null,
    couleur_html              char(6)            not null,
    bonus_defense          int                   not null,
    primary key (id)
);

create table descriptions (
    id                           int                    not null    auto_increment,
    nom                       varchar(100)    not null,
    distance_attaque    int                   not null,
    max_vie                 int                    not null,
    max_degats           int                    not null,
    price                      int                    not null,
    primary key (id)
);

create table couts (
    id                         int not null auto_increment,
    description_id       int not null,
    materiau_id          int not null,
    move_on              int not null,
    primary key (id)
);

Tout est dans le "move_on"  cette valeur là correspond pile poil à mon désir. On appelle cela "l'association riche". J'aurais aussi pu mettre un "default 16", mes unités ayant au maximum 15points de déplacement,  celà voudrait dire qu'oublier de remplir la case empécherait tout déplacement. Mais bon, j'ai opté pour ne rien mettre par défaut car de toute manière l'admin devra changer 95% des coûts.  Autant tout simplifier du côté serveur et code, l'utilisateur changera tous les coûts de déplacement mais au passage il les créera (donc pas la peind d'automatiser cela).

La déclaration niveau classes donne :

class Materiau < ActiveRecord::Base
  has_many :dames
  has_many :couts, :dependent => true
  has_many :descriptions, :through =>  :couts
end

class Cout < ActiveRecord::Base
  belongs_to :materiau
  belongs_to :description
end

class Description < ActiveRecord::Base
  has_many :unites
  has_many :couts, :dependent => true
  has_many :materiaus, :through =>  :couts
end

Maintenant, reste à implémenter tout ça correctement. L'utilisateur doit rapidement pouvoir ajouter une description d'unité ou un matériau de terrain et ne pas oublier de remplir les cases coûts quand on rajoute l'un ou l'autre. Et bien, rien de plus simple ! Regardez bien :

Je fais un scaffold sur chaque classe Materiau Description et Cout. Pour les deux premiers, rien est à changer, si ce n'est changer la redirection aprés la création d'une description/d'un matériau, on renvoie sur les couts pour ne pas les oublier !
Un simple :

redirect_to :controller => 'stationservice' ,:action => 'list' 

dans la méthode create, en cas de création réussie fait l'affaire.

Pour les coûts, seuls des changements mineurs sont a effectués : la présentation ordonnée des coûts en tableau à double entrée. Dans la méthode "list" du controlleur correspondant au coûts on rajoute la recherche des descriptions et des matériaus.

@materiaus = Materiau.find(:all, :order => "nom")
@descriptions = Description.find(:all, :order => "nom")

Il ne reste plus qu'à faire le tableau.

<table>
        <!-- on commence par faire une ligne avec les noms des matériaus -->
 <tr><td>&nbsp;</td>
 <% for materiau in @materiaus -%>
 <td><%= materiau.nom -%></td>
 <% end -%>
 </tr>
        <!-- les autres lignes commencent par le nom de l'unité suivi de toutes les valeurs qui vont bien -->
 <% for description in @descriptions -%>
 <tr><td><%= description.nom -%></td>
    <% for materiau in @materiaus -%>
    <td>
        <% if cout=Cout.find_by_materiau_id_and_description_id(materiau.id,description.id) -%>
            <%= link_to cout.move_on , :action => 'edit' , :id => cout %>
        <% else -%>
                    <!-- si la valeur n'est pas présente, on met un lien pour l'inventer -->
            <%= link_to "nouveau" , :action => 'new' , :description_id => description.id , :materiau_id => materiau.id %>
        <% end %>
    </td>
    <% end -%>
 </tr>
 <% end -%>
</table>

Bon, là j'ai un peu fais de la merde car j'ai rajouté des éléments applicatifs dans la vue, et en prime il y a beaucoup d'accès à la base de donnée. J'ai dérogé à ces règles de codage. Je n'ai pas encore trop eu l'idée magique pour changer ça. Le problème vient du fait que rails ne gère pas les clés primaires doubles.
Peut-être un find_couts(:all) bien ordonné devrait faire l'affaire, mais dans ce cas il faudra alourdir le code de création d'unité et de matériaus en créant par défaut les coûts manquant pour qu'il "n'y ait pas de trous".
Enfin, j'y travaillerai mais puisque rajouter une unité ou un type de matériau n'est pas une action que l'on fait à tour de bras, cela ne charge pas trop le serveur.

Dans le cas où le coût n'existe pas, on passe en paramètre la description et le materiau, et on les rajoute à la main dans la déclaration de la méthode "create" :

@cout = Cout.new(params[:cout])
@cout.materiau_id = params[:materiau_id]
@cout.description_id = params[:description_id]

Ceci n'es pas un choix délibéré de faire des tâches de si bas niveau (je comprends qu'en tant que railers on n'y soit pas habitué), c'est que la déclaration has_many :through ne permet pas la "proxy sélection" (ou l'opérateur << qu'on peut utiliser pour la conseur hbtm).

Bien entendu, il reste une chose à faire : virer tout les trucs inutiles comme la méthode "destroy" dans l'échaffaudage créé sur la classe coûts.

Voilà pour cet article. D'ailleurs je remercie aussi les gens du channel irc #rubyonrails.fr sur freenode qui m'ont bien aidé à combler le "through" (bide inside).
par FrihD publié dans : rubyonwars
ajouter un commentaire commentaires (3)    recommander

Recommander

Cliquez ici pour recommander ce blog

Calendrier

Mai 2006
L M M J V S D
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
<< < > >>
Blog : Sport sur over-blog.com - Contact - C.G.U. - Rémunération en droits d'auteur avec TF1 Network - Signaler un abus