Forum: Italian Ruby user group many to many

Posted by Renato Daprà (Guest)
on 2010-02-24 18:28
(Received via mailing list)
Ciao a tutti,
 mi sono appena iscritto alla ML e già eccomi qui con una richiesta di 
aiuto.

Avrei bisogno di capire come funziona il collegamento fra due tabelle 
"many to many" con Ruby on Rails.

In particolare ho la necessità di collegare una tabella names ad una 
tabella investigations attarverso la tab join
invetigations_names.

Io ho proceduto mttendo nei models  names e investigations:
has_and_belongs_to_many :investigations
has_and_belongs_to_many :names

e in new names:
 <%= f.label :investigations_id %><br />
        <%= f.collection_select :id, Investigation.find(:all, :order => 
"indagine ASC"), :id, :indagine, :include_blank=>true%>
        <%= link_to 'New' , new_investigation_path %>

Quando provo ad inserire da new names una indagine questa non viene 
inserita nella tabella join e
Mongrell mi dice :

WARNING: Can't mass-assign these protected attributes: id

riferendosi al fatto che viene passato l'id di investigations.

Grazie, Renato.
Posted by Pietro Giorgianni (giorgian)
on 2010-02-24 19:07
(Received via mailing list)
2010/2/24 Renato Daprà <renato2266@gmail.com>:
> Ciao a tutti,
>  mi sono appena iscritto alla ML e già eccomi qui con una richiesta di aiuto.

Ciao e benvenuto!

>  <%= f.label :investigations_id %><br />
>        <%= f.collection_select :id, Investigation.find(:all, :order => "indagine ASC"), :id, :indagine, :include_blank=>true%>
>        <%= link_to 'New' , new_investigation_path %>
>
> Quando provo ad inserire da new names una indagine questa non viene inserita nella tabella join e
> Mongrell mi dice :
>
> WARNING: Can't mass-assign these protected attributes: id
>
> riferendosi al fatto che viene passato l'id di investigations.

Certo, perché la select si chiama id, non investigation_id :)

P.S. fatti un favore, ed evita di inserire chiamate al db dentro le 
view.

Nel controller (metodo new o edit, a seconda) metti:
  @investications = Investigation.all, :order => "indagine ASC"

e nella view:

<%= f.collection_select :investigation_id, @investigations, :id,
:indagine, :include_blank=>true %>

Te ne verrà una vita più felice e meno problemi di salute!


pietro
Posted by Marco Mastrodonato (marcomd)
on 2010-02-24 19:32
Ciao Renato e benvenuto!

Non ho capito perchè utilizzi la collection select, non ti conviene 
utilizzare una selezione multipla come i check box? Per esempio:

<% @investigations.each do |item| %>
  <%= check_box_tag "investigation_ids[]", item.id %>
  <%= f.label item.indagine%>
<% end %>


e poi nel controller fai qualcosa tipo:

if @names.save
  @names.investigations << 
Investigation.find(params[:investigation_ids])
  ecc...


Non so se è il modo più efficiente ed elegante per realizzarlo
Posted by Renato Daprà (Guest)
on 2010-02-24 22:17
(Received via mailing list)
Il giorno 24/feb/2010, alle ore 19.06, Pietro Giorgianni ha scritto:

> 2010/2/24 Renato Daprà <renato2266@gmail.com>:
>> Ciao a tutti,
>>  mi sono appena iscritto alla ML e già eccomi qui con una richiesta di aiuto.
> 
> Ciao e benvenuto!
Grazie
>> e in new names:
> 
> Certo, perché la select si chiama id, non investigation_id :)

Ho provato ma non funziona comunque.
Credo sia causato dal  parametro di investigation che viene passato come 
. "id"=>"1"
e quindi il messaggio di errore é :
WARNING: Can't mass-assign these protected attributes: id


> Te ne verrà una vita più felice e meno problemi di salute!
Grazie del consiglio :-)

> 
> 
> pietro
> _______________________________________________
> 

Renato
Posted by Renato Daprà (Guest)
on 2010-02-24 22:21
(Received via mailing list)
Il giorno 24/feb/2010, alle ore 19.32, Marco Mastrodonato ha scritto:

> Ciao Renato e benvenuto!
Grazie
> 
> Non ho capito perchè utilizzi la collection select, non ti conviene 
> utilizzare una selezione multipla come i check box? Per esempio:
Perché le indagini possono essere 10 > e quindi le check box credo scomode
>  ecc...
Potresti definire meglio la parte del controller
> 
> Non so se è il modo più efficiente ed elegante per realizzarlo

 Potrei comunque provare.

Grazie, Renato
Posted by Marco Mastrodonato (marcomd)
on 2010-02-25 12:16
Renato Daprà wrote:

La collection select, crea una select con selezione singola, dovresti 
provare ad inserire l'opzione multiple tra le opzioni html, se ho tempo 
faccio qualche prova.

Se le checkbox le disponi ordinatamente su più colonne, credo che fino 
una ventina siano utilizzabili, il mio caso era un'interfaccia di 
amministrazione quindi avevo meno esigenze estetiche.

L'esempio di prima era la view per selezione multipla e non veniva 
impostato il valore di ogni item (nel tuo caso, indagine) in quanto 
forzato dalla nuova selezione, non era l'esempio che volevo mostrarti.
Nella view normale controlli se il singolo item è presente tra la 
collection associata a name:

#VIEW
<% @investigations.each do |item| %>
  <%= check_box_tag
      "investigation_ids[]",
      item.id,
      @name.investigations.include?(item) %>
  <%= f.label item.indagine%>
<% end %>


La create è molto semplice:

#CONTROLLER

#CREATE
if @name.save
  if params[:investigation_ids]
    @name.investigations << 
Investigation.find(params[:investigation_ids])
  end
else
  #ecc.
end

l'update un pò più complessa ma ho usato delle variabili di transizione 
in modo che puoi verificare col debug.
Un'altra soluzione più semplice come codice ma più onerosa lato db 
sarebbe quella di fare a priori una delete_all ed aggiungere quello che 
si è selezionato, io preferisco questa:

#UPDATE
if @name.update_attributes(params[:name])
  if params[:investigations_ids]
    now = @name.investigations.map(&:id)
    selected = params[:investigation_ids].map{|item| item.to_i}
    to_delete = now - selected
    to_add = selected - now
    @name.investigations.delete(Investigation.find(to_delete)) if 
to_delete.any?
    @name.investigations << Investigation.find(to_add) if to_add.any?
  else
    @name.investigations.delete_all
  end
end
Posted by Marco Mastrodonato (marcomd)
on 2010-02-25 14:09
Anzi, mi sono spinto oltre, ho creato una routine generica da piazzare 
sotto application controller o sotto lib:

#controllers\application_controller.rb
def manage_to_many(object, str_object_many)
  model = eval str_object_many.capitalize
  str_objects_many = "#{str_object_many}s"
  now = object.send(str_objects_many).map(&:id)
  if params["#{str_object_many}_ids"]
    selected = params["#{str_object_many}_ids"].map{|item| item.to_i}
    to_delete = now - selected
    to_add = selected - now
    object.send(str_objects_many).delete(model.find(to_delete)) if 
to_delete.any?
    object.send(str_objects_many) << model.find(to_add) if to_add.any?
  else
    object.send(str_objects_many).delete_all if now.any?
  end
end

è generica, va bene per qualsiasi oggetto e sia per la create che per 
l'update, nel tuo caso la richiameresti così:

#CREATE
if @name.save
  manage_to_many(@name, "investigation")
else
  #ecc.
end

#UPDATE
if @name.update_attributes(params[:name])
  manage_to_many(@name, "investigation")
else
  #ecc.
end

Nel caso di altri modelli basta cambiare i parametri passati, ad esempio 
ruoli ed autorizzazioni:
manage_to_many(@role, "authorization")
Posted by Marco Mastrodonato (marcomd)
on 2010-02-25 15:49
Ho generalizzato anche la routine per la view inserendola in un helper, 
puoi anche scegliere il numero di colonne per impaginare le check box.

#VIEW
<%= manage_to_many_checkbox(@investigations, @name, "investigation", 5) 
%>

#HELPER
def manage_to_many_checkbox(collection, object, str_object_many, cols=1)
  html = ""
  i=0
  collection.each do |item|
    html << "<div style='display: block'>" if i % cols == 0
    id_html = "#{str_object_many}_#{item.id.to_s}"
    html << "<span style='float:left;width: #{100/cols}%;'>" <<
            check_box_tag("#{str_object_many}_ids[]",
                          item.id,
                          object.send("#{str_object_many}s").include?(item),
                          {:id => id_html}) <<
            label_tag(id_html, item.name) <<
            "</span>"
    i+=1
    html << "</div>" if i % cols == 0
  end
  html << "</div>"
end
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.