2014-02-25 78 views
2

我是Ruby和Rails的新手,所以可能有更好的方法来实现我想要做的事情,但是,如果我理解为什么我的方法会失败,我将不胜感激比不同的方式看起来更像。我使用的是:Rails ActiveRecord.new创建新记录而不是更新现有记录

  • 的Ruby 1.8.7
  • 的Rails 3.2.12
  • 管理平台2.2.3(虽然我不认为是 这里完全相关)
  • 的MySQL 5.6

我有一个Skin.rb模型(皮肤在外观,而不是器官),我有一个皮肤的Android环境和iOS环境的不同皮肤。一个皮肤可以有零个或一个与之关联的语言文件以及与之关联的零个或一个图形文件。这些外观的属性显示在应用程序\意见\皮肤\ 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.id是为skins表的主键,save作品(简单地)如下:

  1. 目前与skins.id = 4那么一个新创建
  2. 已经有一个用skins.id = 4的记录,以便记录与它的属性设置为每那些在POST REQ更新没有记录uest。

http://apidock.com/rails/ActiveRecord/Base/saverails activerecord save method都暗示我做的是正确的事情,但它不工作。

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

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

(至于为什么我做这种方式时,有可能是更好的方法:

  1. 我的使用情况是这样的,应该已经由当时的用户在Android的皮肤和在iOS皮肤导航到http://.../skins
  2. 我认为如果语言允许它无缝更新/创建这些记录是很好的,所以我避免了rails中的各种更新特定方法(例如update_attributes。此外,我认为他们无论如何只需围绕save。)

我想了解我的代码如何失败,而不是其他哪种方法可能更好。

+1

约定是把编辑和更新方法在你的控制器。我不认为尝试使用新的和创建控制器方法更新对象属性是明智的。 –

+1

在为资源生成路由时设置了单独的新建,创建,编辑和更新路由的事实确实意味着我应该在一种方法中创建新皮肤,并以不同方法更新现有皮肤。但是,从用户角度来看,只有一个按钮可以点击,它应该导致新的皮肤或更新的皮肤。由于有一个first_or_create方法(感谢Vimsha),所以可以将它全部放在一个方法中,我认为它更简洁,同时仍然保持可读性。但也许我应该使用SkinController.update方法而不是create。 – user3337410

回答

0

使用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 
+0

太棒了,工作!谢谢。 – user3337410

+0

请选择正确的答案 – usha