Many to many

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 %>

<%= 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.

2010/2/24 Renato Daprà [email protected]:

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 %>

<%= 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 :slight_smile:

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

Il giorno 24/feb/2010, alle ore 19.06, Pietro G. ha scritto:

2010/2/24 Renato Daprà [email protected]:

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 :slight_smile:

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 :slight_smile:

pietro


Renato

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

Il giorno 24/feb/2010, alle ore 19.32, Marco M. 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

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

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 << “

” if i % cols == 0
id_html = “#{str_object_many}_#{item.id.to_s}”
html << “” <<
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) <<

i+=1
html << “
” if i % cols == 0
end
html << “”
end

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”)