Rails ActiveRecord.new crea un nuovo record invece di aggiornare il record esistente
-
21-12-2019 - |
Domanda
Sono nuovo in Ruby e Rails, quindi probabilmente c'è un approccio migliore a ciò che voglio fare, ma apprezzerei qualsiasi aiuto per capire perché esattamente il mio approccio fallisce piuttosto che come sarebbe un approccio diverso.Sto usando:
- Rubino 1.8.7
- Rotaie 3.2.12
- Redmine 2.2.3 (anche se non credo sia del tutto rilevante qui)
- MySQL 5.6
Ho un modello Skin.rb (skin come in apparenza, non organo) e ho una skin per ambienti Android e una skin diversa per ambienti iOS.A uno skin possono essere associati zero o un file di lingua e zero o un file di grafica associati.Gli attributi di questi skin vengono visualizzati nella vista app\views\skins\index.html.erb che elenca ciascuno degli skin:
<% @skins.each do |skin| %>
<% if skin.device_os == 'android' %>
<%= content_tag(:h3, 'Android') %>
<% elsif skin.device_os == 'ios' %>
<%= content_tag(:h3, 'iOS') %>
<% end%>
<table>
<thead><tr>
<td>Languages</td>
<td>Graphics</td>
<td></td>
<td></td>
</tr></thead>
<tbody>
<tr>
<%= form_for :skin, :url => skins_path do |f| %>
<td><%= f.collection_select :lang_file, (Attachment.find_by_sql [@lang_file_sql, @current_project.id]), :id, :filename, {:prompt => skin.lang_file.present? ? Attachment.find(skin.lang_file).filename : "Select a languages file"} %></td>
<td><%= f.collection_select :graphics_pack, (Attachment.find_by_sql [@graphics_pack_sql, @current_project.id]), :id, :filename, {:prompt => skin.graphics_pack.present? ? Attachment.find(skin.graphics_pack).filename : "Select a graphics pack"} %></td>
<td><%= hidden_field('skin', 'id', {:value => skin.id}) %></td>
<td><%= f.submit %></td>
<% end %>
</tr>
</tbody>
</table>
<% end %>
Mi piacerebbe poter aggiornare gli attributi della skin Android o della skin iOS nella visualizzazione indice e aggiornare il record appropriato nella tabella delle skin.Tuttavia, quando provo ad aggiornare il record, viene creato un nuovo record invece dell'aggiornamento del record pertinente.
Il modo in cui sto cercando di farlo è passare la skin aggiornata dalla vista indice con il suo id
e aggiornato lang_file
E graphics_pack
attribuisce al skins_controller#create
metodo.Il POST tracciato da WEBrick si presenta così:
Started POST "/skins" for 127.0.0.1 at Tue Feb 25 15:25:04 +0000 2014
Processing by SkinsController#create as HTML
Parameters: {"authenticity_token"=>"sZWVl8IO1IKRNa/fStps8pUehDcSqQsaN/vpL3BITf8=", "commit"=>"Save
Skin", "utf8"=>"Ô£ô", "skin"=>{"lang_file"=>"6", "graphics_pack"=>"", "id"=>"4"}}
Puoi vedere il params[:skin]
parametro passato sopra.
Questo metodo utilizza il new
metodo per creare un nuovo oggetto Skin con gli attributi passati params[:skin]
.IL create
il metodo si presenta come segue (i commenti si riferiscono alla traccia WEBrick sopra):
def create
@skin = Skin.new(params[:skin]) #@skin{ id: => 4, lang_file: => 6, graphics_pack => nil }
if @skin.save #update skins table if record with skins.id=4 already exists else create new record
redirect_to :back
else
# do error handling stuff
end
end
Per quanto ho capito, da allora skin.id
è la chiave primaria per il file skins
tavolo, save
funziona (semplicisticamente) come segue:
- Al momento non esiste alcun record con
skins.id
=4 quindi ne viene creato uno nuovo - Esiste già un record con
skins.id
=4 in modo che il record venga aggiornato con i suoi attributi impostati secondo quelli nella richiesta POST.
http://apidock.com/rails/ActiveRecord/Base/save sabbia metodo di salvataggio del record attivo di rails entrambi suggeriscono che sto facendo la cosa giusta ma non funziona.
Quello che osservo è che ogni volta che provo a configurare una delle skin esistenti, viene creato un nuovo record di skin nella tabella skin con il suo skins.id
incrementato automaticamente dall'ultimo creato.IL params[:skin][:id]
sembra essere ignorato.
Posso usare il new
E save
metodi per aggiornare/creare un nuovo record secondo necessità?Come lo faccio?Penso di passare abbastanza informazioni al mio SkinsController, quindi mi aspetto che la risposta si trovi nel file SkinsController#create
metodo stesso.
(Per quanto riguarda il motivo per cui lo sto facendo in questo modo quando probabilmente ci sono modi migliori:
- I miei casi d'uso sono tali che dovrebbero già essere presenti una skin Android e una skin iOS nel momento in cui l'utente accede
http://.../skins
E - Penso che sia bello aggiornare/creare questi record senza problemi con lo stesso bit di codice se la lingua lo consente, quindi evito i vari metodi specifici di aggiornamento in rails (ad es.
update_attributes
.Inoltre, penso che si avvolgano e bastasave
Comunque.)
Mi piacerebbe capire come sta fallendo il mio codice piuttosto che quale altro approccio potrebbe essere migliore.
Soluzione
Utilizzo first_or_create
def create
@skin = Skin.where(:id => params[:skin][:id]).first_or_create #@skin{ id: => 4, lang_file: => 6, graphics_pack => nil }
if @skin.update_attributes(params[:skin]) #update skins table if record with skins.id=4 already exists else create new record
redirect_to :back
else
# do error handling stuff
end
end
params[:skin][:id]
verrà ignorato perché il suo attributo protetto.Non è possibile assegnare in massa id
skin = Skin.new(:id => 1, :lang_file => 6) #id will be ignored and autoincremented while saving
skin.id = 3 #this will work. id will be set to 3