Forum: Italian Ruby user group has_and_belongs_to_many come riempire la join table?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Mauro (Guest)
on 2009-01-27 22:36
(Received via mailing list)
Siccome nella join table non ho bisogno di gestire nessuna informazione
aggiuntiva alle due foreign keys che mettono in relazione molti a molti
due
tabelle ho preferito usare has_and_belongs_to_many piuttosto che
has_many
:through.
La domanda e': la tabella di join va popolata manualmente oppure esiste
un
meccanismo automatico che si occupa di popolarla quando vengono inseriti
dati nelle due tabelle in relazione?
Sandro P. (Guest)
on 2009-01-27 22:52
(Received via mailing list)
Se crei correttamente i due has_and_belongs_to_many la tabella di join
si
autopopola :D

2009/1/27 Mauro <removed_email_address@domain.invalid>
Pietro G. (Guest)
on 2009-01-27 22:57
(Received via mailing list)
Il 27 gennaio 2009 21.35, Mauro <removed_email_address@domain.invalid> ha 
scritto:
> Siccome nella join table non ho bisogno di gestire nessuna informazione
> aggiuntiva alle due foreign keys che mettono in relazione molti a molti due
> tabelle ho preferito usare has_and_belongs_to_many piuttosto che has_many
> :through.
> La domanda e': la tabella di join va popolata manualmente oppure esiste un
> meccanismo automatico che si occupa di popolarla quando vengono inseriti
> dati nelle due tabelle in relazione?

dai un'occhiata a:

http://railscasts.com/episodes/17
http://railscasts.com/episodes/47
Mauro (Guest)
on 2009-01-27 23:19
(Received via mailing list)
2009/1/27 Sandro P. <removed_email_address@domain.invalid>

> Se crei correttamente i due has_and_belongs_to_many la tabella di join si
> autopopola :D


Allora ti spiego com'e' la situazione perche' la tabella di join non si
e'
autopopolata.
Dunque i modelli sono
class Category < ActiveRecord::Base
  belongs_to :sector
  has_and_belongs_to_many :suppliers
end

Il belongs to sector e' perche' la categoria appartiene ad un settore
specifico, ma il mio problema e' tra Category e:

class Supplier < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

A questo punto ho lanciato script/console e fatto un
@category=Categoyt.create(..............) e
@supplier=Supplier.create(.................)
Le due tabelle categories e suppliers vengono popolate ma la tabella di
join
no.
La migration per la tabella di join e':

class CreateCategoriesSuppliersJoin < ActiveRecord::Migration
  def self.up
    create_table :categories_suppliers, :id => false do |t|
      t.references :category, :null => false
      t.references :supplier, :null => false
  end

  def self.down
    drop_table :categories_suppliers
  end
  end
end

Dove sbaglio? A me sembra tutto giusto.
Pietro G. (Guest)
on 2009-01-27 23:47
(Received via mailing list)
Il 27 gennaio 2009 22.19, Mauro <removed_email_address@domain.invalid> ha 
scritto:
> A questo punto ho lanciato script/console e fatto un
> @category=Categoyt.create(..............) e
> @supplier=Supplier.create(.................)
> Le due tabelle categories e suppliers vengono popolate ma la tabella di join
> no.

cosa c'è in quei ...?
in particolare, dov'è il dato di quali categorie appartengono a quali
supplier?

@supplier.categories = Category.find(:all)
@supplier.save

oppure:
@category.suppliers = Supplier.first
Pietro G. (Guest)
on 2009-01-27 23:48
(Received via mailing list)
2009/1/27 Pietro G. <removed_email_address@domain.invalid>:
> @category.suppliers = Supplier.first

scusa:
@category.suppliers = [Supplier.first]
Mauro (Guest)
on 2009-01-27 23:51
(Received via mailing list)
2009/1/27 Pietro G. <removed_email_address@domain.invalid>

> 2009/1/27 Pietro G. <removed_email_address@domain.invalid>:
> > @category.suppliers = Supplier.first
>
> scusa:
> @category.suppliers = [Supplier.first]
> _______________________________________________
> Ml mailing list
> removed_email_address@domain.invalid
> http://lists.ruby-it.org/mailman/listinfo/ml
>

Quindi la tabella di join non viene popolata automaticamente come hai
detto,
bisogna intevenire col codice o qualcosa mi sfugge?
Mauro (Guest)
on 2009-01-27 23:55
(Received via mailing list)
2009/1/27 Pietro G. <removed_email_address@domain.invalid>

> Il 27 gennaio 2009 22.19, Mauro <removed_email_address@domain.invalid> ha scritto:
> > A questo punto ho lanciato script/console e fatto un
> > @category=Categoyt.create(..............) e
> > @supplier=Supplier.create(.................)
> > Le due tabelle categories e suppliers vengono popolate ma la tabella di
> join
> > no.
>
> cosa c'è in quei ...?


Ci sono i campi valorizzati @category=Categoyt.create(:id => 1, :codice
=>'A01', :descrizione => 'prova')
@supplier=Supplier.create(:id => 1, :nome => 'pingo', :cognome =>
'pongo')


>
> in particolare, dov'è il dato di quali categorie appartengono a quali
> supplier?


Dovrebbero essere le foreign keys nella tabella di join no? category_id
e
supplier_id.
Pietro G. (Guest)
on 2009-01-28 00:26
(Received via mailing list)
2009/1/27 Mauro <removed_email_address@domain.invalid>:
> Dovrebbero essere le foreign keys nella tabella di join no? category_id e
> supplier_id.

andiamo con ordine.
tu hai Supplier e Category, e ogni supplier può appartenere a più
categorie e ogni categoria può appartenere a ogni supplier.

una volta che hai creato una categoria e un supplier, c'è ancora un
dato che manca: questa categoria si applica a questo supplier?

se hai tre categorie e tre suppier, il software non può indovinare
come sono associate.

ci sono diversi modi di procedere, in base a come vuoi che sia
l'interfaccia:

a) puoi inserire prima tutte le categorie, poi tutti i clienti e
infine fare un rettangolo di checkbox in cui spuntare quelle giuste;

(crei gli oggetti così come hai fatto finora, poi fai il form
(eventualmente parliamo di come si può fare) e fai le varie
associazioni;

b) puoi inserire prima le categorie; quando poi inserisci ogni
supplier, scegli con checkbox o una select multipla le categorie per
questo supplier;

c) puoi fare una cosa mista, più complessa ma forse più comoda per
l'utente: quando inserisci una categoria, fai scegliere tra i supplier
già presenti ed eventualmente dei nuovi, e viceversa, quando inserisci
un supplier, fai scegliere tra le categorie.

quasi tutto il lavoro lo fa activerecords da solo: come ti ho scritto,
puoi aggiungere o togliere oggetti direttamente agendo su
@supplier.categories, o @category.suppliers come se fossero array
(anche se un occhio a quello che poi succede in termini di interazione
col database non guasta).
Mauro (Guest)
on 2009-02-05 00:31
(Received via mailing list)
2009/1/27 Pietro G. <removed_email_address@domain.invalid>:
> se hai tre categorie e tre suppier, il software non può indovinare
>
> b) puoi inserire prima le categorie; quando poi inserisci ogni
> supplier, scegli con checkbox o una select multipla le categorie per
> questo supplier;

Piano piano comincio a capire grazie anche agli episodi su railscast,
veramente utili :-)
Ho scelto la soluzione b.
Una parte del codice della view per i suppliers e':

<p>
    <%= f.label :aggiudicazioniGare %><br />
    <%= f.text_field :aggiudicazioniGare %>
</p>
<p>
    <%= f.label :note %><br />
    <%= f.text_area :note %>
</p>
<p>
  <%= f.label :categorie %><br />
  <%= f.collection_select :category_ids, @categories, :id, :descrizione
%>
</p>

Dove quindi ho una select attraverso la quale indicare la categoria
associata al fornitore.
Ovviamente nel controller fornitore ho: @categories=Category.find(:all).
Funziona :-)
Pero' mi serve qualcosa di piu':
Le categorie fanno parte di diversi settori, cioe' ho un'associazione
uno a molti tra settori e categorie.
Quando inserisco un nuovo fornitore percio', non voglio assegnarli la
categoria di appartenenza scegliendo tra tutte le categorie
disponibili, bensi' vorrei prima indicare il settore per poi scegliere
tra le categorie appartenenti al settore selezionato.
Non so se sono riuscito a spiegarmi.
Dovrei capire a fondo i meccanismi che collegano i modelli con i
controllers con le views, e infatti li sto capendo piano piano
lavorando sul campo, solo che non riesco a trovare la soluzione a
questo banale problemino.
Pietro G. (Guest)
on 2009-02-05 14:12
(Received via mailing list)
2009/2/4 Mauro <removed_email_address@domain.invalid>:
> Pero' mi serve qualcosa di piu':
> Le categorie fanno parte di diversi settori, cioe' ho un'associazione
> uno a molti tra settori e categorie.
> Quando inserisco un nuovo fornitore percio', non voglio assegnarli la
> categoria di appartenenza scegliendo tra tutte le categorie
> disponibili, bensi' vorrei prima indicare il settore per poi scegliere
> tra le categorie appartenenti al settore selezionato.

chiarissimo.

nella mia applicazione ho incontrato questo problema più volte.

in un caso, poiché le opzioni possibili erano veramente tante, ho fatto
così:
una normale select "settore", poi un observe_field che, quando scatta,
fa una richiesta ajax a un apposito metodo di un controller, che
restituisce la select con le sole categorie per quel settore.

relativamente veloce da scrivere, però, ripeto, consigliabile solo se
il numero di settori e/o categorie è molto molto alto, perché la
richiesta ajax ci impiega un po'.

in un altro caso, le opzioni possibili erano pochi, ho usato
javascript (in una view: era un file .js.erb):

var sections = new Array();
<%- for section in @sections -%>
  elem = document.createElement('optgroup');
  elem.setAttribute('id', '<%= "group#{section.id}" -%>');
  elem.appendChild(create_option('', '<%= t(:option_pick_a_user) -%>'));
  <% for user in section.users -%>
    elem.appendChild(create_option('<%= user.id -%>', '<%= user.nick
-%>'));
  <% end -%>
  sections['<%= section.id -%>'] = elem;
<% end -%>

function create_option(value, text) {
  o = document.createElement('option');
  o.setAttribute('value', value);
  o.appendChild(document.createTextNode(text));
  return o;
}

function set_users() {
  section = $('assignment_section_id').value;
  if (section == "") {
    $('users').hide();
    $('buttons').hide();
 }
  else {
    sel = $('assignment_user_id');
    children = sel.childNodes;
    for(i = 0; i < children.length; i++)
      sel.removeChild(children[i]);
    sel.appendChild(sections[section]);
    $('users').show();
    $('buttons').show();
  }
}

come vedi, ho popolato un array javascript, e poi nella view uso:

<%= observe_field "id_della_select", :function => "set_users()" %>


forse è possibile creare gli optgroup direttamente da codice erb,
senza usare javascript, crearli tutti invisibili e poi mostrare quello
giusto, ma non ne sono sicuro.
Mauro (Guest)
on 2009-02-05 14:24
(Received via mailing list)
2009/2/5 Pietro G. <removed_email_address@domain.invalid>:
>
>
>  <% end -%>
> function set_users() {
>    sel.appendChild(sections[section]);
> forse è possibile creare gli optgroup direttamente da codice erb,
> senza usare javascript, crearli tutti invisibili e poi mostrare quello
> giusto, ma non ne sono sicuro.

Ah ecco, piu' complicato del previsto, agli observe non ci sono ancora
arrivato :-)
Mauro (Guest)
on 2009-02-11 18:42
(Received via mailing list)
2009/2/5 Pietro G. <removed_email_address@domain.invalid>:
>
> nella mia applicazione ho incontrato questo problema più volte.
>
> in un caso, poiché le opzioni possibili erano veramente tante, ho fatto così:
>
> una normale select "settore", poi un observe_field che, quando scatta,
> fa una richiesta ajax a un apposito metodo di un controller, che
> restituisce la select con le sole categorie per quel settore.

Tanto per cominciare mi trovo nella banale situazione di inserire
nella view dei fornitori la lista dei settori.
Ancora prima di poter selezionare la categoria di appartenenza del
fornitore devo scegliere il settore, il problema su come poi
visualizzare le categorie relative a quel settore lo risolvo dopo.
Nella view del fornitore, new.html.erb, ho inserito <%=
f.collection_select :sector_id, @sectors, :id, :descr %> ma ovviamente
mi da errore perche' il metodo sector_id non esiste nella classe
fornitore.
Mi trovo in stand_by per questo banale problema.
Msan M. (Guest)
on 2009-02-15 01:57
Pietro G. wrote:
>
> <%= observe_field "id_della_select", :function => "set_users()" %>
>
Come mai richiami una funzione javascript e non un'azione del
controller?
Pietro G. (Guest)
on 2009-02-15 10:29
(Received via mailing list)
2009/2/15 Msan M. <removed_email_address@domain.invalid>:
> Pietro G. wrote:
>>
>> <%= observe_field "id_della_select", :function => "set_users()" %>
>>
> Come mai richiami una funzione javascript e non un'azione del
> controller?

come ti dicevo, non sempre è necessario usare ajax: se i dati tra cui
scegliere sono pochi*, si può prendere in considerazione l'idea di
caricarli tutti (in questo caso creando un array javascript) per poi
mostrare quelli giusti. in questo caso, non si chiama nessun'azione
del server.

quando invece i dati sono tanti, allora si preferisce fare una richiesta
ajax.

tra parentesi: su quale sia il modo migliore di gestire le richieste
dinamiche ci sono varie scuole. io finora ho usato prototype, ma vedo
che sempre più gente utilizza jquery, che sembra carino, anche se non
l'ho ancora provato.

* cosa vuol dire pochi e molti? non saprei, è difficile stabilirlo,
dipende da tanti fattori, come la velocità di connessione. non so se
qualcuno, là fuori, ha proposto un limite.

pietro
Msan M. (Guest)
on 2009-02-15 13:23
(Received via mailing list)
2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
> caricarli tutti (in questo caso creando un array javascript) per poi
> mostrare quelli giusti. in questo caso, non si chiama nessun'azione
> del server.
>
> quando invece i dati sono tanti, allora si preferisce fare una richiesta ajax.
>
> tra parentesi: su quale sia il modo migliore di gestire le richieste
> dinamiche ci sono varie scuole. io finora ho usato prototype, ma vedo
> che sempre più gente utilizza jquery, che sembra carino, anche se non
> l'ho ancora provato.

Ho proprio ieri potuto apprezzare l'utilita' dell'observ_field.
Per poter utlizzare ajax ho utilizzato, come indicato nel manuale
agile web development with rails, <% javascript_include_tag :defauts
%>.
Non quindi se viene utilizzato jquery o prototype, c'e' un modo per
capirlo?
Un'altra domanda.
Bene o male nel form di "nuovo fornitore", in cui va riempito il form
per la creazione di un nuovo fornitore, sono riuscito ad inserire
l'elenco dei settori e, alla scelta di un settore, tramite
l'observ_field, vengono presentate le categorie relative.
Tutto nella stessa pagina.
Se non sbaglio pero' e' impossibile fare tutto con un'unica
operazione, nel senso: creare un nuovo fornitore e contemporaneamente
assegnargli le categorie di appartenenza.
Devo prima avere gia' in tabella l'id del fornitore e solo cosi' posso
successivamente assegnare tale fornitore a diverse categorie.
Sbaglio?
Pietro G. (Guest)
on 2009-02-15 13:39
(Received via mailing list)
2009/2/15 Mauro <removed_email_address@domain.invalid>:
> Per poter utlizzare ajax ho utilizzato, come indicato nel manuale
> agile web development with rails, <% javascript_include_tag :defauts
> %>.
> Non quindi se viene utilizzato jquery o prototype, c'e' un modo per capirlo?

rails di default usa prototype.

> Se non sbaglio pero' e' impossibile fare tutto con un'unica
> operazione, nel senso: creare un nuovo fornitore e contemporaneamente
> assegnargli le categorie di appartenenza.
> Devo prima avere gia' in tabella l'id del fornitore e solo cosi' posso
> successivamente assegnare tale fornitore a diverse categorie.
> Sbaglio?

non è ancora possibile farlo automaticamente (cioè senza sgobbare); lo
sarà presto: nella 2.3 dovrebbe introdurre i nested form, che servono
esattamente a questo.

tuttavia c'è un modo per ottenere il risultato che vuoi.

in breve:

* in Provider, crei un attributo virtuale, che so: categories_wannabe;
* nel form di creazione, passi le categorie con questo nome, ad
esempio con fields_for :categories_wannabe;
* sempre in Provider, metti un after_create o un after_save (a seconda
se ti interessa solo il caso creazione o anche il caso modifica) in
cui assegni le categorie.

trovi un esempio (anche se un po' diverso) qui:
http://railscasts.com/episodes/75-complex-forms-part-3

pietro
Msan M. (Guest)
on 2009-02-15 14:19
(Received via mailing list)
2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
>
> * in Provider, crei un attributo virtuale, che so: categories_wannabe;
> * nel form di creazione, passi le categorie con questo nome, ad
> esempio con fields_for :categories_wannabe;
> * sempre in Provider, metti un after_create o un after_save (a seconda
> se ti interessa solo il caso creazione o anche il caso modifica) in
> cui assegni le categorie.
>
> trovi un esempio (anche se un po' diverso) qui:
> http://railscasts.com/episodes/75-complex-forms-part-3

Perfetto grazie mille :-)

p.s. che intendi per Provider?
Pietro G. (Guest)
on 2009-02-15 15:32
(Received via mailing list)
Il 15 febbraio 2009 13.19, Mauro <removed_email_address@domain.invalid> ha 
scritto:
> 2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
> p.s. che intendi per Provider?

semplicemente il model del fornitore. supplier? provider?
Msan M. (Guest)
on 2009-02-15 18:31
(Received via mailing list)
2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
> Il 15 febbraio 2009 13.19, Mauro <removed_email_address@domain.invalid> ha scritto:
>> 2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
>> p.s. che intendi per Provider?
>
> semplicemente il model del fornitore. supplier? provider?

ah ok supplier allora :-)
Msan M. (Guest)
on 2009-02-15 21:38
(Received via mailing list)
2009/2/15 Pietro G. <removed_email_address@domain.invalid>:
>> assegnargli le categorie di appartenenza.
> in breve:
>
> * in Provider, crei un attributo virtuale, che so: categories_wannabe;
> * nel form di creazione, passi le categorie con questo nome, ad
> esempio con fields_for :categories_wannabe;
> * sempre in Provider, metti un after_create o un after_save (a seconda
> se ti interessa solo il caso creazione o anche il caso modifica) in
> cui assegni le categorie.

Ok, perdonami ma sto andando un po in confusione, troppe cose tutte
assieme per me :-)
Allora: ti spiego dove sono arrivato e dove mi sono arenato.
localhost:3000/suppliers/new richiama l'action new del controller
supplier.
La view dell'action new e':

<% form_for(@supplier) do |f| %>
  <%= f.error_messages %>
  <%= render :partial => "form", :locals => { :f => f, :label_text =>
"Crea"} %>
<% end %>

il partial contiene tutti i campi relativi agli attributi del modello
Supplier che devono essere valorizzati per creare un nuovo supplier.
All'interno di questo partial ho aggiunto:

  <%= collection_select "category", :sector_id, @sectors, :id, :name,
:prompt => "seleziona il settore" %>
<div id="category_container">
</div>
<%= observe_field :category_sector_id,
          :frequency => 1,
          :update => 'category_container',
          :url => {:action => 'filter_category_in_sector'},
          :with => "'sector_id='+value" %>

La collection_select e' per poter selezionare il settore e
l'observ_field mi dovrebbe dare le categorie relative al settore
selezionato.
Per poter far funzionare la collection_select ho aggiunto nell'action
new di supplier_controller la variabile @sectors = Sector.find(:all).
L'observe_field chiama l'action filter_category_in_sector definita nel
controller in questo modo:

def filter_category_on_sector
    @categories = Category.find_all_by_sector_id(params[:sector_id])
    render :layout => false
end

e ovviamente ho creato la vista filter_category_in_sector:

<% fields_for @supplier.categories_wannabe do |f| %>
<% end %>

Questo codice e' solo di prova per iniziare a capire il funzionamento
di fields_for e degli attributi virtuali che mi hai suggerito di
utilizzare.
Si pone pero' il problema che l'oggetto supplier non e' visibile
nell'action filter_category_in_ sector.
Pietro G. (Guest)
on 2009-02-16 09:58
(Received via mailing list)
2009/2/15 Mauro <removed_email_address@domain.invalid>:
> [...]
> Si pone pero' il problema che l'oggetto supplier non e' visibile
> nell'action filter_category_in_ sector.

che importa? filter_category_on_sector deve solo mostrare le categorie
per un certo settore; sarà poi l'action del form (create, suppongo) a
ricevere tutti i dati. l'action dovrà fare qualcosa tipo:

@supplier = Supplier.create(params[:supplier])

sta invece al supplier stesso fare la cosa giusta, ovvero prendere,
dopo la creazione, i dati dell'attributo virtuale (ovvero gli id delle
categorie) e usarli per riempire categories.

se hai dubbi, prova semplicemente a loggare il contenuto di params
nell'action (logger.debug params.inspect), e dagli un'occhiata: vedrai
come vengono resi i campi del fields_for.

questa cosa degli attributi virtuali viene usata spesso: ad esempio,
molti plugin di autenticazione usano due attributi virtuali
sull'oggetto utente, con nomi tipo password e password_confirm, e
bloccano invece il mass assignment del vero campo password, così al
salvataggio controllano se password e password_confirm sono stati
assegnati, e se sì che siano uguali, e infine riempiono il vero campo
password (quello che sta sul db).
Msan M. (Guest)
on 2009-02-16 10:59
(Received via mailing list)
2009/2/16 Pietro G. <removed_email_address@domain.invalid>:
>
mmmmm, allora mi sfugge qualcosa.....l'action
filter_category_on_sector non deve far altro che crearmi una serie di
checkboxes relative alle varie categorie, in questo modo l'utente
dovrebbe poter assegnare una o piu' categorie al nuovo supplier.
La view dell'action filter_category_on_sector la devo ancora
completare, devo vedere come creare le checkboxes, ma, giusto per
vedere se qualcosa funziona, ho messo:
<% fields_for @suppliers.categories_wannabe do |f| %>
<% end %>

e mi da errore dicendomi che viene richiamato un oggetto inesistente
nil.categories_wannabe.
Msan M. (Guest)
on 2009-02-17 10:23
(Received via mailing list)
2009/2/16 Mauro <removed_email_address@domain.invalid>:
>> @supplier = Supplier.create(params[:supplier])
> <% end %>
>
> e mi da errore dicendomi che viene richiamato un oggetto inesistente
> nil.categories_wannabe.


Sono in stanb_by :-( e in stato confusionale :-)
Hai tempo per darmi una mano?
This topic is locked and can not be replied to.