Rails ActiveRecord.new cria um novo registro em vez de atualizar o registro existente
-
21-12-2019 - |
Pergunta
Sou novo em Ruby e Rails, então provavelmente há uma abordagem melhor para o que quero fazer, mas agradeceria qualquer ajuda para entender por que exatamente minha abordagem falha, em vez de como seria uma abordagem diferente.Estou a usar:
- Rubi 1.8.7
- Trilhos 3.2.12
- Redmine 2.2.3 (embora eu não ache que seja totalmente relevante aqui)
- MySQL 5.6
Eu tenho um modelo Skin.rb (pele como aparência, não órgão) e tenho um skin para ambientes Android e um skin diferente para ambientes iOS.Uma capa pode ter zero ou um arquivo de idioma associado a ela e zero ou um arquivo gráfico associado a ela.Os atributos para essas skins são exibidos na visualização app\views\skins\index.html.erb que lista cada uma das skins:
<% @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 %>
Gostaria de poder atualizar os atributos do skin do Android ou do skin do iOS na visualização do índice e atualizar o registro apropriado na tabela de skins.No entanto, quando tento atualizar o registro, um novo registro é criado em vez de o registro relevante ser atualizado.
A maneira como estou tentando fazer isso é passar o skin atualizado da visualização do índice com seu id
e atualizado lang_file
e graphics_pack
atributos para o skins_controller#create
método.O POST rastreado pelo WEBrick se parece com isto:
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"}}
Você pode ver o params[:skin]
parâmetro passado acima.
Este método usa o new
método para criar um novo objeto Skin com os atributos passados em params[:skin]
.O create
O método é o seguinte (os comentários referem-se ao rastreamento do WEBrick acima):
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
Tanto quanto eu entendo, desde skin.id
é a chave primária para o skins
mesa, save
funciona (simplesmente) da seguinte forma:
- Atualmente não há registro com
skins.id
=4 então um novo é criado - Já existe um registro com
skins.id
=4 para que o registro seja atualizado com seus atributos definidos conforme aqueles na solicitação POST.
http://apidock.com/rails/ActiveRecord/Base/save areia Método de salvamento do ActiveRecord do Rails ambos sugerem que estou fazendo a coisa certa, mas não está funcionando.
O que observo é que cada vez que tento configurar um dos skins existentes, um novo registro de skin é criado na tabela de skins com seu skins.id
incrementado automaticamente a partir do último criado.O params[:skin][:id]
parece ser ignorado.
Posso usar o new
e save
métodos para atualizar/criar um novo registro conforme necessário?Como faço isso?Acho que estou passando informações suficientes para meu SkinsController, então espero que a resposta esteja no SkinsController#create
método em si.
(Quanto ao motivo pelo qual estou fazendo isso dessa maneira, quando provavelmente existem maneiras melhores:
- Meus casos de uso são tais que já deve haver uma capa Android e uma capa iOS no momento em que o usuário navega para
http://.../skins
e - Eu acho que é bom atualizar/criar perfeitamente esses registros com o mesmo trecho de código, se a linguagem permitir, então estou evitando os vários métodos específicos de atualização no Rails (por exemplo,
update_attributes
.Além disso, acho que eles apenas envolvemsave
de qualquer forma.)
Gostaria de entender como meu código está falhando e não qual outra abordagem pode ser melhor.
Solução
Usar 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]
será ignorado porque é um atributo protegido.Você não pode atribuir em 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