Creating multiple rows with one form


#1

Hello. I’ve been trying this out for the past two days and I can’t seem
to
get it. I’m going to have a page where you can upload x amount of images
at
once. Lets say 10 images need to be uploaded, all with a caption.

I’d like to have a browse button to choose the file, then the caption.
Now,
if I put 10 of them in one form, fill them all out and submit, I get one
form. It seems that all of the fields wind up overwriting eachother and
I
get the last one submitted.

What I’m asking is if it is possible to say take THIS, THIS, and THAT,
and
make one row. Then take THIS(2), THIS(2), and THAT(2), and make yet
another
row.

Thanks a lot,


#2

Matt R. wrote:

and make one row. Then take THIS(2), THIS(2), and THAT(2), and make yet
another row.

Assuming you have a model called “Image”, having attributes
“filename” and “caption”, and you have a User model that
has_many images.

In the view:

<%= form_tag %>

<% 10.times do |i| @image = @images[i] %> <%= file_columm_field 'image', 'filename', :index => i %> <%= text_field 'image', 'caption', :index => i %> <% end %>
<%= end_form_tag %>

In the controller:

def specify_images
case request.method
when :get then @images = Array.new(10) { Image.new }
when :post
all_valid = true
@images = []
params[:image].each_value do |p|
image = @user.images.build§
@images << image
all_valid &&= image.valid?
end
if !@images.empty? && all_valid
@images.each { |image| image.save(false) }
redirect_to :action => ‘show_user’
else
flash.now[:error] = ‘No images entered’ if @images.empty?
(removed_email_address@domain.invalid).times { @images << Image.new }
end
end

Different code would be needed to modify an existing set of
images associated with a user, rather than just creating a
new set.


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


#3

Mark,

Where is the :index parameter documented? Can it work with an id
instead of index?

Thanks,

Chris

Mark Reginald J. wrote:

Matt R. wrote:

and make one row. Then take THIS(2), THIS(2), and THAT(2), and make yet
another row.

Assuming you have a model called “Image”, having attributes
“filename” and “caption”, and you have a User model that
has_many images.

In the view:

<%= form_tag %>

<% 10.times do |i| @image = @images[i] %> <%= file_columm_field 'image', 'filename', :index => i %> <%= text_field 'image', 'caption', :index => i %> <% end %>
<%= end_form_tag %>

In the controller:

def specify_images
case request.method
when :get then @images = Array.new(10) { Image.new }
when :post
all_valid = true
@images = []
params[:image].each_value do |p|
image = @user.images.build§
@images << image
all_valid &&= image.valid?
end
if !@images.empty? && all_valid
@images.each { |image| image.save(false) }
redirect_to :action => ‘show_user’
else
flash.now[:error] = ‘No images entered’ if @images.empty?
(removed_email_address@domain.invalid).times { @images << Image.new }
end
end

Different code would be needed to modify an existing set of
images associated with a user, rather than just creating a
new set.


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


#4

hi!

i had similar problem and used:
<%#= file_field_tag “images[#{i}][name]” %>

but now i’ve changed it to use index. Thanks for the tip.

BTW. i’ve created simple javascript so you can enter any number of file
fields you like:

in ‘new’ action of parent controller (or probably you could in
_form.rhtml) specify default number of file fields:
@file_fields_no = 4

then in _form.rhtml
<%= javascript_tag “var image_field_number=#{@file_fields_no}” %>
<% @file_fields_no.times do |i| %>
<%= file_field “images”, “name”, “index” => i %>

<% end %>
<%=link_to_function ‘add image’,
‘add_image_upload_field(image_field_number); image_field_number++;’, :id
=> “add_image” %>

javascript:
function add_image_upload_field(id) {
var image_field = ‘
’;
new Insertion.Before(“add_image”, image_field);
}


#5

Chris B. wrote:

Where is the :index parameter documented? Can it work with an id
instead of index?

The :index option is docmented here:
http://api.rubyonrails.com/classes/ActionView/Helpers/FormHelper.html

To index with ids, use the “object[]” notation, documented at the
same place.


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


#6

Also, szymek, thanks for that. I planned on adding that later on.


#7

Thanks a lot. I’ll take a look at that when I get home from work and
play
around with it.

Thanks :slight_smile:


#8

szymek wrote:

i had similar problem and used:
<%#= file_field_tag “images[#{i}][name]” %>

but now i’ve changed it to use index. Thanks for the tip.

<% @file_fields_no.times do |i| %>
<%= file_field “images”, “name”, “index” => i %>

<% end %>

Note that if you use the index option this way you can’t
persist the image objects for form redisplay after
validation failure, including error field highlighting.
To allow this you have to instead call the form helpers
with each object of the array in turn:

<% @file_fields_no.times do |i|
@image = @images[i] %>
<%= file_field “image”, “name”, “index” => i %>

<% end %>


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


#9

I didn’t notice it, because i didn’t have any validation yet…

I must admit i don’t really understand why this line is necessary:
<% @image = @images[i] %>

I have @property object that has many images, however i can’t just save
@property.images, because from the form i get uploaded files and i want
to store in the database just their names (i’m saving files on the disk
also, but after modifying their sizes and names).

So i pass @property and @images to the _form.rhml and later when i save
@property object i have:

def create
@property = Property.new(params[:property])
if @property.save
if !params[:images].empty?
params[:images].length.times do |i|

@property.images << Image.new(:name => image_filename)
#save image and its thumbnail to the disk
end
end
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end

But i don’t really now how to do it, so it will work with validation.
And there’s also updating…
I didn’t find anywhere a sample code how to do this, so i tried to do it
myself, and i thought it works… unfortunately it does not.

So how to do it properly?


#10

szymek wrote:

I must admit i don’t really understand why this line is necessary:
<% @image = @images[i] %>

AR form helpers only work with a single Active Record model instance,
stored in a controller instance variable. With the code you posted
all helpers will display the @images object. The index option only
gives them different names so that they become separate parameters
when POSTed, it is not also an index into an array of AR objects.

I have @property object that has many images, however i can’t just save
@property.images, because from the form i get uploaded files and i want
to store in the database just their names (i’m saving files on the disk
also, but after modifying their sizes and names).

Have you had a look at the file_column plugin, which does much of
this automatically?


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


#11

It’s me again.

I tried understand the code posted in the second post, but i can’t
figure out how it works. What do these lines do:

image = @user.images.build§ #what does build method do?
@images.each { |image| image.save(false) } #why’s ‘false’ parameter
here?


#12

szymek wrote:

I tried understand the code posted in the second post, but i can’t
figure out how it works. What do these lines do:

image = @user.images.build§ #what does build method do?

Equivalent to:
image = Image.new( p.update(:user => @user) )

@images.each { |image| image.save(false) } #why’s ‘false’ parameter

Speeds up the code by bypassing validation, since the validation
has already been done.

By the way, part of the last main line of this code got recognized
as an email address, and got munged. With a few extra spaces,
hopefully this should come through OK:

 (10 - @images.length).times { @images << Image.new }


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


#13

I’m sooooo stupid!

I forgot that in _form.rhtml i was checking name of the action to
display different content for ‘new’ and for ‘edit’ action.

I didn’t think that when “render :action => ‘new’” is called from
‘create’ action, the action name in _form.rhtml is still ‘create’ :slight_smile:

But what about already uploaded images? If @property.save fails, do i
have to upload them again and create “@images =
Array.new(@file_fields_no) { Image.new }” again?

What i should put to @images (file or image object) so the file input
fields will be properly filled?


#14

Sorry for all these stupid questions, Mark.

Now that i’ve downloaded file_column plugin, i noticed that you were
using file_column_field everywhere in your sample code.

However i’ve learnt a lot during creating my own poor imitation of
file_column plugin :slight_smile:

Thanks again for your patience!


#15

szymek wrote:

Sorry for all these stupid questions, Mark.

Now that i’ve downloaded file_column plugin, i noticed that you were
using file_column_field everywhere in your sample code.

However i’ve learnt a lot during creating my own poor imitation of
file_column plugin :slight_smile:

Thanks again for your patience!

No problem. Are all your earlier questions now resolved?


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


#16

Well i still didn’t write the whole code for creating and editing images
using this plugin, so only one new question today :slight_smile:

While file_column plugin correctly creates input file fields, for each
file field it also creates exactly the same input hidden element with
the same name and id:



So it won’t pass xhtml validation, because there are many elements with
the same id and it will only post one hidden element because they all
have the same name. I’m not sure, but probably this field is used for
checking if a file was already uploaded. If it’s true, then it won’t
work correctly with many file_column fields.


#17

Thanks for explaining.

Does file_column pluing allow uploading many files in one form?
Does it work with has_many/belongs_to relationship?
If it does both these things, then i’ll switch immediately! :slight_smile:
But also it would be nice to know how to do it myself.

Ok, here’s what i got now (it’s probalby 100th version :slight_smile: )

in _form.rhtml:

<% @file_fields_no.times do |i| %>
<% @image = @images[i] %>
<%= file_field “image”, “name”, “index” => i %>

<% end %>

in property controller:

def new
@property = Property.new
@file_fields_no = 4
@images = []
@file_fields_no.times do @images << Image.new end
end

def create
@property = Property.new(params[:property])
@images = []
@file_fields_no = params[:image].length
if @property.save
saved_image_number = 0 # number of saved file
params[:image].each_value do |file|
if file[:name].size > 0
uploaded_image = file[:name] # get file contents
ext = file[:name].original_filename_extension # get file
extension
file[:name] =
“#{@property.id}-#{saved_image_number.to_s}.#{ext}”
image = @property.images.build(file)
image.save(uploaded_image) # call ‘super’ and saves image to
the disk
saved_image_number += 1
end
end
flash[:notice] = ‘Success!’
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end

I must admit that i don’t fully understand the need of creating ‘empty’
objects in ‘new’ action.
If @property.save fails i don’t have any file fields in my form.
Also i’m not sure what to add to @images array. As i understand, it’s
used for redisplaying contents of file fields when validation fails? So
should i add file or modified image object?


#18

New day, new question! :slight_smile:

I’m still struggling with the most basic code in ‘create’ action of
parent controller. I simplified it to only single image.

def create
@property = Property.new(params[:property])
if @property.save
# try to save image
if ok
redirect_to :action => ‘list’
else
@image = Image.new
render :action => ‘new’
else
@image = Image.new
render :action => ‘new’
end
end

It obviously won’t work, because if saving an image fails, it will call
‘new’ action again, but the property will be already saved. So the next
time, if image is saved successfully, i will get 2 property objects.
Next thing is that i have the same code for else cases. Also i’m not
sure what i should pass to ‘new’ action so it won’t upload the image
again.

I can’t save images before saving property object, because i won’t have
parent id. But i also can’t save property object before saving images,
because if saving image fails, i will get many property objects.