Rails ActiveRecord.new создает новую запись вместо обновления существующей записи

77
7

Я новичок в Ruby и Rails, поэтому, возможно, есть лучший подход к тому, что я хочу сделать, но я был бы признателен за любую помощь в понимании того, почему именно мой подход выходит из строя, а не как выглядит другой подход. Я использую:

    Ruby 1.8.7 Рельсы 3.2.12 Redmine 2.2.3 (Хотя я не думаю, что это очень важно здесь) MySQL 5.6

У меня есть модель Skin.rb (кожа как внешний вид, а не орган), и у меня есть один скин для сред Android и другой скин для сред iOS. Кожа может иметь нулевой или один языковой файл, связанный с ним, и нулевой или один графический файл, связанный с ним. Атрибуты этих скинов отображаются в представлении app\views\skins\index.html.erb, в котором перечислены все скины:

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

Я хотел бы иметь возможность обновлять атрибуты скина Android или iOS в представлении индекса и обновлять соответствующую запись в таблице скинов. Однако, когда я пытаюсь обновить запись, создается новая запись вместо обновляемой соответствующей записи.

Способ, которым я пытаюсь это сделать, - передать обновленный скин из индексного представления с его id и обновленными lang_file и graphics_pack в skins_controller#create. POST, прослеживаемый WEBrick, выглядит следующим образом:

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"}}

Вы можете увидеть params[:skin] переданный выше.

Этот метод использует new метод для создания нового объекта Skin с атрибутами, переданными в params[:skin]. Метод create выглядит следующим образом (комментарии относятся к трассировке WEBrick выше):

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

Насколько я понимаю, поскольку skin.id является основным ключом для таблицы skins, save работы (упрощенно) следующим образом:

В настоящее время нет записи с skins.id= 4, поэтому создается новая Уже есть запись с skins.id= 4, так что запись обновляется с ее атрибутами, установленными в соответствии с запросами POST.

http://apidock.com/rails/ActiveRecord/Base/save sand rails activerecord save method обе подсказывают, что я поступаю правильно, но он не работает.

Я наблюдаю, что каждый раз, когда я пытаюсь настроить одну из существующих скинов, в таблице скинов создается новая запись для кожи, а ее skins.id - skins.id автоматически увеличивается с последнего. params[:skin][:id], по-видимому, игнорируются.

Могу ли я использовать new и save методы для обновления/создания новой записи по мере необходимости? Как мне это сделать? Я думаю, что передаю достаточно информации в свой SkinsController, поэтому я ожидаю, что ответ будет лежать в самом SkinsController#create.

(Что касается того, почему я делаю это так, когда есть, вероятно, лучшие способы:

Мои варианты использования таковы, что к моменту, когда пользователь http://.../skins на http://.../skins и должен быть уже установлен скин для Android и скин iOS Я думаю, что это приятно легко обновить/создать эти записи с тем же кусочком коды, если язык позволяет так я избегаю различных обновлений конкретных методов в рельсах (например update_attributes. Кроме того, я думаю, что они просто обернуть вокруг save в любом случае.)

Я хотел бы понять, как мой код терпит неудачу, а не какой другой подход может быть лучше.

спросил(а) 2021-01-25T21:57:33+03:00 4 месяца, 4 недели назад
1
Решение
63

Использовать 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] будет игнорироваться из-за его защищенного атрибута. Вы не можете назначить 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

ответил(а) 2021-01-25T21:57:33+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема