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

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

  • Ruby 1.8.7
  • Rails 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 работает (упрощенно) следующим образом:

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

http://apidock.com/rails/ActiveRecord/Base/save песчаные рельсы метод activerecord save оба предлагают, что я делаю правильные вещи, но это не работает.

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

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

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

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

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

1 ответ

Решение

Использование 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 
Другие вопросы по тегам