Reputation: 94
I'm new to Ruby and Rails so there's probably a better approach to what I want to do but I'd appreciate any help understanding why exactly my approach fails rather than what a different approach would look like. I'm using:
I have a Skin.rb model (skin as in appearance, not organ) and I have one skin for Android environments and different skin for iOS environments. A skin can have zero or one language files associated with it and zero or one graphics files associated with it. The attributes for these skins are displayed in the app\views\skins\index.html.erb view which lists each of the 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 %>
I'd like to be able to update the attributes of either the Android skin or the iOS skin in the index view and have the appropriate record in the skins table updated. However, when I try and update the record a new record is created instead of the relevant record being updated.
The way I'm trying to do this is to pass the updated skin from the index view with its id
and updated lang_file
and graphics_pack
attributes to the skins_controller#create
method. The POST as traced by WEBrick looks like this:
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"}}
You can see the params[:skin]
parameter passed above.
This method uses the new
method to create a new Skin object with the attributes passed in params[:skin]
. The create
method looks as follows (comments refer to WEBrick trace above):
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
As far as I understand, since skin.id
is the primary key for the skins
table, save
works (simplistically) as follows:
skins.id
=4 so a new one is createdskins.id
=4 so that record is updated with its attributes set as per those in the POST request.http://apidock.com/rails/ActiveRecord/Base/save sand rails activerecord save method both suggest I'm doing the right thing but it's not working.
What I observe is that each time I try to configure one of the existing skins, a new skin record is created in the skins table with its skins.id
auto-incremented from the last one created. The params[:skin][:id]
appears to be ignored.
Can I use the new
and save
methods to update/create a new record as necessary? How do I do that? I think I'm passing enough information to my SkinsController so I'm expecting the answer to lie in the SkinsController#create
method itself.
(As for why I'm doing it this way when there are probably better ways:
http://.../skins
andupdate_attributes
. Besides, I think they just wrap around save
anyway.)I'd like to understand how my code is failing rather than which other approach may be better.
Upvotes: 2
Views: 2669
Reputation: 29349
Use 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]
will get ignored because its protected attribute. You cannot mass assign 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
Upvotes: 1