Rails ActiveRecord.new创建新记录而不是更新现有记录

时间:2014-02-25 16:33:32

标签: ruby-on-rails activerecord updatemodel

我是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_filegraphics_pack属性传递到skins_controller#create方法。 WEBrick跟踪的POST如下所示:

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.idskins表的主键,save的工作原理(简单化)如下:

  1. 目前没有skins.id = 4的记录,因此创建了一个新记录
  2. 已存在skins.id = 4的记录,以便更新记录,其属性按POST请求中的属性设置。
  3. http://apidock.com/rails/ActiveRecord/Base/saverails activerecord save method都暗示我做的是正确的,但它不起作用。

    我观察到的是,每次尝试配置其中一个现有皮肤时,都会在皮肤表中创建一个新的皮肤记录,其中skins.id会自创建的最后一个皮肤自动递增。 params[:skin][:id]似乎被忽略了。

    我可以使用newsave方法根据需要更新/创建新记录吗?我怎么做?我想我已经将足够的信息传递给了我的SkinsController,所以我希望答案存在于SkinsController#create方法本身。

    (至于为什么我这样做的时候可能有更好的方法:

    1. 我的用例是,当用户导航到http://.../skins
    2. 时,应该已经存在Android皮肤和iOS皮肤
    3. 如果语言允许,我认为用相同的代码无缝更新/创建这些记录是很好的,所以我在rails中避免了各种更新特定的方法(例如update_attributes。此外,我认为他们无论如何,只需环绕save。)
    4. 我想了解我的代码是如何失败的,而不是其他方法可能更好。

1 个答案:

答案 0 :(得分: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