As far as I can tell, this is a views and controller problem.
I need to allow users to edit multiple records in one of my database tables. Railscast 165 deals with a similar situation, but it's not quite on point for me: http://railscasts.com/episodes/165-edit-multiple
I have a fixed set of products, and I need to let users add associated varieties to those products. I've considered the easier solution of requiring the user to go to a product page, and edit one individual product at a time, but that won't work for my app. Instead, I need to provide more of an admin style functionality for this one task.
The model relationship here is simple. A product has_many
varieties, and variety belongs_to
product. In my user interface, I simply let the user select a product from a dropdown menu containing all of the available products on the site, and then the user can type the name of a variety in a text input. For example, if we're talking about apples, then the user can select apples from the product dropdown, and he might type in "fuji" and a variety. I simply need to save the product_id
and the variety[name] to the varieties table. However, I need to do this for multiple records at one time.
Currently, I've got the form rendering with the dropdown displaying the right product choices. The problem seems to be that the variety input is rendering in html like this:
<label for="nil_class_variety">Variety</label>
<input id="nil_class_name" name="nil_class[name]" size="30" type="text" />
To render parts of the form, I'm following the Advanced Rails Recipes multi-model form technique (i.e. the Ryan Bates way). I spent about 10 hours trying to do this and it seems that I'm now very close, but I don't know what the problem is at this point.
Here's my setup:
Products Controller
def edit_multiple
@products = Product.find(:all)
end
def update_multiple
params[:product][:existing_variety_attributes] ||= {}
@products = Product.find(:all)
if @products.each do |product|
product.update_attributes!(params[:product].reject { |k,v| v.blank? })
end
flash[:notice] = "Updated products!"
redirect_to edit_user_path(self.current_user)
else
render :action => 'edit'
flash[:error] = "Something went wrong. Please try again."
end
end
Routes.rb # To make the custom controller actions work
map.resources :products, :collection => { :edit_multiple => :get, :update_multiple => :put}
views/products/edit_multiple.html.erb
<%= error_messages_for :product %>
<% form_for :product, :url => update_multiple_products_path, :html => { :method => :put } do |f| %>
<div id="varieties">
<%= render :partial => 'variety' %>
</div>
<p><%= add_variety_link "+ Add another variety" %></p>
<%= f.submit 'Submit Varieties' %>
<%end%>
views/products/_variety.html.erb
Note: For now, I'm not letting the users edit the varieties they submit. So I removed the following line from the Ryan Bates technique because it was throwing an error and I don't think I need it, but I could be wrong: <%# new_or_existing = variety.new_record? ? 'new' : 'existing' %>
Here's what I have in this _variety partial:
<% prefix = "product[new_variety_attributes][]" %>
<% fields_for @variety do |variety_form| -%>
<%= collection_select(:product, :product_id, Product.all, :id, :name, {:prompt => true}) %>
<%= variety_form.label :variety %>
<%= variety_form.text_field :name %>
<%= link_to_function "- Remove Variety", "$(this).up('.variety').remove()" %>
<%end -%>
NOTE: Currently the javascript stuff you see is working. On the edit_multiple view page, I can dynamically add and remove the products/varieties inputs. As I mentioned, the dropdown is also populating properly. So it seems that I just need to get the variety input to render properly and get the controller to properly process it. Thanks for your help!
Update
When I select one product from the dropdown and type a name in the variety input, the submit throws the following error and trace:
ActiveRecord::UnknownAttributeError in ProductsController#update_multiple
unknown attribute: product_id
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `each'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2628:in `update_attributes!'
/Users/michael/dev/fresh/app/controllers/products_controller.rb:74:in `update_multiple'
/Users/michael/dev/fresh/app/controllers/products_controller.rb:73:in each'
/Users/michael/dev/fresh/app/controllers/products_controller.rb:73:in
update_multiple'
The log shows the correct product_id and the correct name I entered, but you can see it's calling a nil class:
Processing ProductsController#update_multiple (for 127.0.0.1 at 2009-10-08 07:31:05) [PUT]
Parameters: {"commit"=>"Submit Varieties", "authenticity_token"=>"zzkveSe7qzv2NY8WPrR2cYS376u6DBiz8Vc9iNFLQy8=", "product"=>{"product_id"=>"5"}, "nil_class"=>{"name"=>"yellow"}}
When I try to enter more than one product/variety record, the log isn't recognizing anything except the first one. I get the same result as when I type in just one product/variety record.