Probleme checkbox/affichage/jointure

Bonjour,

Je créé actuellement un projet perso en Ruby On Rails.

Le but est de faire un petit logiciel de gestion d’un forum de RP.

J’ai donc un personnage à qui je rattache des topics. La difficulté
étant qu’un topic appartient à un personnage principal mais est aussi
partagé entre plusieurs autres personnages en plus comme partenaire du
personnage principal. (bref un forum de rp classique)

J’ai utilisé dans mes modèles des jointures :

class Character < ActiveRecord::Base
has_many :topics, :through => :partner
end

class Topic < AR::B
has_many :character, :through => :partner
end

class Partner < AR:B
belongs_to :character
belongs_to :topic
end

la table partner a cette tête là :
character_id
topic_id
owner => booléen de propriété du topic pour savoir qui est le personnage
principal du topic

j’ai créé une vue “new topic” ou le personnage principal est constant.
Mon probleme est le suivant :

Je voudrais afficher la liste des personnages hormis le personnage
principal avec une check box a côté de chacun et je voudrais qu’en
cliquant sur la check box et en appuyant sur le bouton update, il me
créé automatiquement l’entrée dans la table “partners”

Je m’explique :

dans topics_controller, la méthode new ressemble à ça :

def new
@topic = topic.new
@character = character.find(params[:id]) (le menu est contextuel et me
donne en parametre l’id du personnage principal)
@characters = Character.find(:all, :conditions => [“id != ?”,
params[:id].to_i]) Cela me donne la liste de tous les personnages
pouvant être potentiellement partenaire du personnage propriétaire du
topic (en gros, tout le monde sauf le personnage principal)

Maintenant ma vue :

j’ai créé une form : form.html.erb qui a le look suivant :

Partners
<% for character in @characters %> <% f.check_box ù> <== c'est là ou je ne sais pas gérer <%= h(character.firstname+' '+character.name %< <% end %> ...

Ma question est donc la suivante :
Comment gérer proprement une liste de partenaires via des check box ?
Sachant, qu’étant débutant en Ruby On Rails, je ne sais pas où il faut
gérer cela dans la form ? dans la méthode du controller ?

Et sachant aussi, que je vais avoir le même problème à l’édition d’un
topic si les partenaires changent, d’où suppression des entrées ad hoc
dans la table partners, gestion des checkbox, etc…

Merci d’avance pour vos réponses, et bonne journée à tous !

Pour la gestion des checkbox, tu peux effectivement la faire en Rails :
http://wiki.rubyonrails.org/rails/pages/HowToUseCheckBoxes pour faire
des
checkbox simples
http://wiki.rubyonrails.org/rails/pages/CheckboxHABTM plus intéressant
dans
ton cas, pour faire des checkbox représentant des relations HABTM

Sinon, j’ai l’impression que tu es encore indécis concernant ta logique
métier, rapport à la fin de ton message :

Et sachant aussi, que je vais avoir le même problème à l’édition d’un
topic si les partenaires changent, d’où suppression des entrées ad hoc
dans la table partners, gestion des checkbox, etc…

J’ai déjà planché sur le sujet du forum rôliste -à l’époque où je
pratiquais
encore le JdR, révolue, snif- si tu veux qu’on en discute j’aurais
peut-être
deux-trois idées à apporter.

Tout d’abord merci de cette réponse rapide :slight_smile:

Mais je ne comprends pas très bien les HABTM, car dans mon cas, mes
modeles n’ont pas de relations HABTM (mais des has_many et belongs_to).
Je croyais justement avoir vu que pour les through, les checkbox
n’etaient pas si evident que pour les HABTM.

Peut-être est-ce la même chose, je m’en excuse si c’est le cas.

Pour moi, le forum RP etait juste un prétexte pour approfondir un peu ma
connaissance (faible) de RoR.

mon modèle de données ne doit pas être parfait. Mais je suis ouvert Ã
toutes les critiques :slight_smile:

C’est donc avec plaisir que je veux bien que tu m’apportes deux trois
idées, les miennes étant relativement basiques.

Un personnage peut avoir des topics avec d’autres personnages : voici ma
réflexion de départ. Et je me rends compte que dans la pratique c’est
pas si simple :slight_smile:

En fait, à l’instar du monsieur Jourdain de Molière avec la prose, tu
fais
des HABTM sans le savoir.

A partir du moment où tu as :
Model1.has_many :model2
Model3.has_many :model2

Model2.has_one :model1
Model2.has_one :model3

Tu as une relation HABTM implicite entre Model1 et Model3, que tu peux
matérialiser comme suit :
Model1.has_many :model3, :through => :model2
Model3.has_many :model1, :through => :model2

Ok, la méthode de AR::B n’est pas has_and_belongs_to_many comme avec une
table de jointure simple, mais le principe est le même.

Maintenant, la ou les question à te poser c’est :

  • est-ce que j’ai besoin d’une table de jointure complexe, et si oui
    pourquoi ?
  • (en fonction de la réponse au “pourquoi ?”) est-ce que la ou les
    données
    supplémentaires qui apparaissent dans la table de jointure peuvent être
    replie(s) automatiquement (= sans saisie supplémentaire de
    l’utilisateur)
  • (en fonction de la réponse au “peut être rempli automatiquement”)
    est-ce
    qu’il n’y a pas un autre moyen de répondre au besoin qu’une liste de
    cases Ã
    cocher ?

A priori, je dirais que la donnée supplémentaire est un booléen
“propriétaire du thread”. Si c’est le cas, tu peux résoudre ça
simplement en
modifiant ton modèle de donnée de cette façon :

add_column :topic, :owner_id, :integer

Topic.has_one :owner, :model => :character
Character.has_many :topics, :foreign_key => :owner_id

Et en revenant à un modèle de jointure habtm classique. Ca a l’avantage
d’être moins gourmand en espace dans ta base de données parce que ça
fait
seulement un integer par Topic plutôt que 1 integer par jointure (dans
une
bonne part des SGBD un integer est stocké dans un integer).

Sinon tu peux aussi adapter le 2° exemple que je t’ai donné à l’origine,
mais ceça me semble moins propre (un topic à forcément un et un seul
propriétaire).

Je comprends ce que tu veux dire avec la remontée du boooléen au niveau
de la table Topic pour le propriétaire du topic. Par contre, quid des
partenaires RP (et donc non proprios du dit topic) ? Car les partenaires
n’auront pas d’owner_id dans la table topic mais ils doivent quand meme
etre reliés au topic.

Comme mon experience en RoR fait de moi plus un slammeur de tres bas
niveau qu’un moliere en terme de langage, je n’ai pas très bien compris
ton idée.

Le but des cases à cocher étaient de pouvoir sélectionner les
partenaires du personnage principal dans un topic à partir de la liste
des personnages globale.

Pour la remontée de l’information owner dans la table topic je suis ok,
mais ce que je n’ai pas très bien saisi, c’est si j’avais encore besoin
de la table partner ou si avec ton astuce, je pouvais m’en débarrasser.

Si je peux m’en débarasser => comment puis-je remonter l’information
partenaires RP d’un topic ?
Si non => je suis bien obligé d’avoir la table “partner” dans ma BD.

En fait, le principe fondamental dans tout ça que je n’ai absolument pas
saisi, est comment et surtout à quelle moment je crée l’entrée dans la
table partner pour faire la jointure entre un topic et des personnages.

Dans le controller et la methode new (ou create?) ou directement via la
check box dans ma form ? Je pensais à la deuxième solution mais
peut-être ai-je tord.

Désolé d’être un boulet :stuck_out_tongue:

Merci beaucoup !

Donc, si je résume, mes deux modèles ressembleront maintenant à ceci :

class Character < ActiveRecord::Base
has_many :topics, :foreign_key => :owner_id
end

class Topic < AR::B
has_one :owner, :model => :character
has_and_belongs_to_many :characters
end

La table characters_topics contient :

character_id
topic_id

Et c’est RoR qui va se debrouiller pour faire le lien entre les tables.

Ai-je juste jusque là ?

Je comprends ce que tu veux dire avec la remontée du boooléen au niveau
de la table Topic pour le propriétaire du topic. Par contre, quid des
partenaires RP (et donc non proprios du dit topic) ? Car les partenaires
n’auront pas d’owner_id dans la table topic mais ils doivent quand meme
etre reliés au topic.

Comme mon experience en RoR fait de moi plus un slammeur de tres bas
niveau qu’un moliere en terme de langage, je n’ai pas très bien compris
ton idée.

L’idée, ce serait de dire “l’owner du topic est le posteur du topic”.
C’est
une reformulation pas forcément équivalente, mais j’ai eu l’impression
que
ça collait à ce que tu veux obtenir. Dans cette idée, les partenaires
sont
dans ta relation HABTM (ce qui pour le coup change le nom de la table en
“characters_topics”). Exit l’objet Partner qui devient inutile puisqu’il
n’y
a plus de raison de faire une relation riche.

Et ne t’en fais pas pour le Slam ça va venir. Tu as juste encore un peu
de
vocabulaire à acquérir ^^

Le but des cases à cocher étaient de pouvoir sélectionner les

partenaires du personnage principal dans un topic à partir de la liste
des personnages globale.

Donc si ton but c’est de limiter l’accès aux Topics aux seuls Characters
autorisés à y accéder, utiliser une relation HABTM simple avec une table
de
jointure simple sera plus élégant, et l’un dans l’autre plus
fonctionnel. Si
tu n’as qu’un seul propriétaire par Topic, l’information “untel est
propriétaire” serait “mieux rangée” dans le Topic que dans une des
jointures
justement.

Ca n’empêche pas de créer automatiquement une jointure vers le
propriétaire
avec la relation “partenaires”, ce qui simplifie la recherche des
partenaires ensuite.

Pour la remontée de l’information owner dans la table topic je suis ok,
mais ce que je n’ai pas très bien saisi, c’est si j’avais encore besoin
de la table partner ou si avec ton astuce, je pouvais m’en débarrasser.

Donc comme dit plus haut, exit la table “partners” et le modèle associé,
bienvenue à la table characters_topics sans modèle associé (pas besoin,
c’est déjà tout géré par AR::B.has_and_belongs_to_many dans les objets
Character et Topic).

Si je peux m’en débarasser => comment puis-je remonter l’information

partenaires RP d’un topic ?
Si non => je suis bien obligé d’avoir la table “partner” dans ma BD.

Avec l’association HABTM justement, qui travaille en manipulant
automagiquement les enregistrements de la table characters_topics, qui
n’est
autre qu’une version simplifiée de la partners d’origine.

En fait, le principe fondamental dans tout ça que je n’ai absolument pas

saisi, est comment et surtout à quelle moment je crée l’entrée dans la
table partner pour faire la jointure entre un topic et des personnages.

Les entrées de la table characters_topics se créeront donc
automagiquement
quand tu auras fait (par exemple) :
@topic = Topic.new

@character = Character.find :first
@topic.characters.add @character

@topic.save

Au moment où tu vas faire ton @topic.save, AR::B va lire la liste des
characters, et va en déduire les relations à créer / supprimer dans la
table
characters_topics.

Dans le controller et la methode new (ou create?) ou directement via la
check box dans ma form ? Je pensais à la deuxième solution mais
peut-être ai-je tord.

En fait tu vas forcément faire les deux…

Seulement tu n’auras pas besoin d’écrire quoi que ce soit dans ton
contrôleur de nouveau par rapport à une création normale, si tu fais
quelque
chose dans ce goût-là dans ta vue “new.html.erb” (et ça marche pareil
avec
“edit.html.erb”) :

<% form_for @topic do |f| -%>

    <% @characters.each do |character| -%>
  • <% input_id = "character_#{character.id}" %> <%= f.check_box :characters, character.id, :id => input_id %> <%= character.name %>
  • <% end -%>
... <% end -%>

Parce que quand le form va être envoyé, le navigateur va envoyer plein
de
paramètres “characters” avec les valeurs des cases cochées, ce que
l’application Rails va transformer en tableau
params[:topic][:characters]
contenant donc les id des characters sélectionnés.
Donc quand tu fais ton :
Topic.new(params[:topic])
Il y aura bien dans params[:topic] l’entrée :characters avec dedans un
tableau d’ids des characters sélectionnés.

La seule chose, c’est qu’il faudra t’assurer que l’id du Character de
l’utilisateur en cours soit bien enregistrée en tant qu’owner et dans
la
liste characters.

Désolé d’être un boulet :stuck_out_tongue:

T’en fais pas, y’a pas pire boulet que celui qui ne fait pas l’effort de
chercher à comprendre.


Michel B.

Michel B. wrote:

Tout juste. Essaye, et on en rediscute ^^

J’avais justement une question :stuck_out_tongue:

Le code

<% @characters.each do |character| -%>


  • <% input_id = “character_#{character.id}” %>
    <%= f.check_box :characters, character.id, :id => input_id %>
    <%= character.name %>

  • <% end -%>

    Me génère une erreur :

    Undefined method merge for 2:Fixnum

    Alors là , je suis sec :S

    En tout cas, je te remercie beaucoup de tes explications, grace à toi,
    les has_many, belongs_to et autres HABTM me paraissent bcp plus clair
    :slight_smile:

    Il y a plus que la description de l’erreur à prendre en compte, aussi la
    ligne de l’erreur ; tu devrais regarder où pour avoir une idée.

    Perso je pense que ça doit venir de la ligne :
    <%= f.check_box :characters, character.id, :id => input_id %>

    C’est probablement un problème de conversion du character.id en chaîne
    de
    caractère qui le fait râler qui manque la méthode “merge”. Ca doit
    pouvoir
    être résollu en tranformant explicitement comme ceci :
    <%= f.check_box :characters, character.id.to_s, :id => input_id %>

    Bon essai.

    Tout juste. Essaye, et on en rediscute ^^

    check_box(object_name, method, options = {}, checked_value = “1”,
    unchecked_value = “0”)
    En fait il faut mettre ce que j’ai mis en 3° paramètre en 4° paramètre
    après
    le hash d’options HTML.
    Donc au lieu de faire :
    f.check_box(:characters, character.id.to_s)
    Tu devrait pouvoir faire :
    f.check_box(:characters, {}, character.id.to_s)

    Sinon, pour savoir ce que fait une méthode, la doc de l’api est ce qu’il
    y a
    de mieux :

    Bonne lecture…

    Michel B. wrote:

    check_box(object_name, method, options = {}, checked_value = “1”,
    unchecked_value = “0”)
    En fait il faut mettre ce que j’ai mis en 3° paramètre en 4° paramètre
    après
    le hash d’options HTML.
    Donc au lieu de faire :
    f.check_box(:characters, character.id.to_s)
    Tu devrait pouvoir faire :
    f.check_box(:characters, {}, character.id.to_s)

    Sinon, pour savoir ce que fait une méthode, la doc de l’api est ce qu’il
    y a
    de mieux :
    ActionView::Helpers::FormHelper
    Bonne lecture…

    Merci pour tout !

    Je n’arrive pas à faire marcher mon programme et j’ai vraiment du mal Ã
    comprendre le fonctionnement de RoR.

    J’ai encore (apres quelques heures) plusieurs problemes :

    class Topic < AR::B
    has_one :owner, :model => :character
    has_and_belongs_to_many :characters
    end

    A priori, RoR ne connait pas le mot clé model (je suis en rails 2.1)
    donc je l’ai remplacé par through.

    Et je n’arrive toujours pas à faire marcher ma liste de la liste de
    check_box.

    Je vais donc continuer d’écumer les tutoriaux afin de pouvoir comprendre
    ce qu’il se passe :slight_smile:

    Bonne soirée

    Alors non, ce n’est pas :through, :through c’est pour préciser une
    association à travers un autre modèle (habtm riche).

    Ici, c’est surtout que ce n’est pas un has_one qu’il faut faire, mais un
    belongs_to ^^

    La différence entre has_one et belongs_to, c’est l’endroit où est la
    clef
    étrangère. Si la clef étrangère est dans cette table, alors il faut
    utiliser
    blongs_to, sinon c’est has_one.

    Dans ton cas, il faut avoir :

    Topic.belongs_to :owner, :model => Character
    et
    Character has_many :owned_topics, :model => Topic, :foreign_key =>
    :owner_id

    Je peux être approximatif sur la syntaxe précise, j’estime que tu es
    assez
    grand pour chercher l’information à partir du moment où on te donne la
    bonne
    direction.

    @bientôt

    Michel B. wrote:

    Il y a plus que la description de l’erreur à prendre en compte, aussi la
    ligne de l’erreur ; tu devrais regarder où pour avoir une idée.

    Perso je pense que ça doit venir de la ligne :
    <%= f.check_box :characters, character.id, :id => input_id %>

    C’est probablement un problème de conversion du character.id en chaîne
    de
    caractère qui le fait râler qui manque la méthode “merge”. Ca doit
    pouvoir
    être résollu en tranformant explicitement comme ceci :
    <%= f.check_box :characters, character.id.to_s, :id => input_id %>

    Bon essai.

    Oui, pardon, le terme “around” dans la gestion de la ligne du message
    d’erreur m’a trompé, c’est bel et bien cette ligne.

    En fait, même avec le “to_s” il n’arrive pas à transformer character.id
    en chaine de caractere.

    Mais meme en mettant le to_s, la methode merge n’est pas non plus
    définie pour String.

    Plus généralement, ce code fait quoi exactement ? Il génère les checkbox
    correspondantes dans la form là je suis ok, du moins celle des
    characters de la boucle.

    Mais une fois que l’utilisateur a coché ou décoché ce qu’il voulait, RoR
    garde automatiquement dans la liste “Characters” uniquement les
    characters cochés ? Et ainsi, dans le controller, avant le save, je n’ai
    plus qu’Ã parcourir cette liste pour affecter les persos correspondant
    au topic comme suit :

    @topic.characters.add @character

    (au fait, on s’est croisé au railscamp à paris, je me souviens de toi,
    par contre à l’époque je posais moins de questions ^^)