How to add multiple new records from one form

I’m trying to provide our customers with a page where they can add up to
20 items to their cart in one form. The form simply has 20 rows with
each row containing a text input for product code and a text input for
quantity.

I’ve tried having the controller create an array that has an object for
each row by looping through something like:

@new_items[1] = Cart.new

and the controller is happy with that, but the template isn’t–it gives
an error like “@new_items[1] is not allowed as an instance variable
name”.

I can get the whole thing working if I use non-model form objects in the
template, but then I lose the Rails error magic where it highlights any
errant fields and gives a message.

Why can I create Cart objects within an array in the controller, but not
use them in the template.

Or am I going about this completely wrong??

Any help is appreciated greatly.

Shawn Goble wrote:

and the controller is happy with that, but the template isn’t–it gives
an error like “@new_items[1] is not allowed as an instance variable
name”.

Perhaps:

<% for i in 1…20 do %>
<%= text_field :new_items, :product_code, :index => i %>
<%= text_field :new_items, :quantity, :index => i %>
<% end %>

case request.method
when :get then @new_items = Array.new(20) { Cart.new }
when :post
all_valid = true
@new_items = []
params[:new_items].each_value { |p| @new_items << (c=Cart.new§);
all_valid &&= c.valid? }
if !@new_items.empty? && all_valid
@user.items << @new_items
@user.save(false)
redirect_to :action => ‘show_cart’
else
flash[:error] = ‘No items entered’ if @new_items.empty?
(20-@new_items.length).times { @new_items << Cart.new }
end
end


We develop, watch us RoR, in numbers too big to ignore.

Mark Reginald J. wrote:

Shawn Goble wrote:

and the controller is happy with that, but the template isn’t–it gives
an error like “@new_items[1] is not allowed as an instance variable
name”.

Perhaps:

<% for i in 1…20 do %>
<%= text_field :new_items, :product_code, :index => i %>
<%= text_field :new_items, :quantity, :index => i %>
<% end %>

Hi Mark,

My query with this is, would Rails look at the number enclosed within
the square brackets and subsequently clobber records with id ranging
from [1] to [20]?

The confusion stemmed about from the fact that if you provide blank
brackets when rendering an edit form, such as:

<% for @comment in @post.comments %>
<%= text_field ‘comment[]’, ‘author’ %>
<%= text_field ‘comment[]’, ‘body’ %>
<% end %>

It will also generate similar looking HTML tags, and hence in that case
the number enclosed within the square brackets do have some
significance.

Thanks.

Woei S. wrote:

<% for i in 1…20 do %>
<%= text_field :new_items, :product_code, :index => i %>
<%= text_field :new_items, :quantity, :index => i %>
<% end %>

<% end %>

It will also generate similar looking HTML tags, and hence in that case
the number enclosed within the square brackets do have some
significance.

While the parameter hash would be similar, with id rather than index
keys,
you would handle it differently in the controller. The Agile book
suggests

 Cart.update(params[:new_items].keys, params[:new_items].values)

In the code I’ve given, the keys aren’t even looked at. New records
with new ids are created based on the hash values.

Looking over the code I see a few errors: The save of @user is not
needed,
and flash.now should be used, not just flash. Also, for greater
efficiency,
each new item should be instantiated via @user.items.build§, then, if
all
pass validation, saved using save(false), avoiding a second validation.


We develop, watch us RoR, in numbers too big to ignore.

Mark Reginald J. wrote:

Perhaps:

<% for i in 1…20 do %>
<%= text_field :new_items, :product_code, :index => i %>
<%= text_field :new_items, :quantity, :index => i %>
<% end %>

case request.method
when :get then @new_items = Array.new(20) { Cart.new }
when :post
all_valid = true
@new_items = []
params[:new_items].each_value { |p| @new_items << (c=Cart.new§);
all_valid &&= c.valid? }
if !@new_items.empty? && all_valid
@user.items << @new_items
@user.save(false)
redirect_to :action => ‘show_cart’
else
flash[:error] = ‘No items entered’ if @new_items.empty?
(20-@new_items.length).times { @new_items << Cart.new }
end
end

Is this method going to flow any validation errors back to the form
though? If it was just one new item on the form you could use:

<%= error_messages_for ‘new_items’ %>

but I’m not clear on whether that would work with this method. Doesn’t
it end up just like it would using non-model objects on the form and
placing the data into Cart objects in the controller? Works, but the
form doesn’t show the validation messages.

Mark Reginald J. wrote:

case request.method
when :get then @new_items = Array.new(20) { Cart.new }
when :post

This still seems not to work.

If in the controller I use:

@new_items = Cart.new

then the :index in the template seems to work correctly (even though
I’ve only created one object in the controller). It will even save the
information to the database correctly using that method. The downside
is that I still can’t get validation error information back to the form.

If however, I do as you suggest (which does seem to make more sense) and
use:

@new_items = Array.new(20) { Cart.new }

I get the error “undefined method product_id' for #<Array:0x55dae60>", which seems to suggest that :index isn't actually letting me reference an array but creating one after the fact from a single object. (Incidentally, if I use @new_items = Cart.new and then after :post I eventually use something like @new_items = Cart.create(items), I get the same error "undefined methodproduct_id’ for #Array:0x55dae60” in the
template.)

Thanks for the help so far, I’ve at least learned something about the
:index parameter.

Shawn Goble wrote:

use:

@new_items = Array.new(20) { Cart.new }

I get the error “undefined method `product_id’ for #Array:0x55dae60”,
which seems to suggest that :index isn’t actually letting me reference
an array but creating one after the fact from a single object.

Yes, sorry, the :index parameter does not do what I thought it did.
It replicates form fields and parameters from a single model instance,
which is not useful for the multiple instances with validation errors
situation you are looking for.

You may be able to make it work using some trick like:

<% @new_items.each_with_index do |@cart, i| %>
<%= text_field :cart, :product_id, :name => “cart[#{i}][product_id]” %>
<% end %>


We develop, watch us RoR, in numbers too big to ignore.

Shawn Goble wrote:

placing the data into Cart objects in the controller? Works, but the
form doesn’t show the validation messages.

All fields with errors will be highlighted, but to get the text
messages you’ll need something like:

<% for i in 1…20
@item = @new_items[i-1] %>
Item <%= i %>: <%= error_messages_for :item %>
<% end %>


We develop, watch us RoR, in numbers too big to ignore.