Forum: Ruby on Rails Creating multiple rows with one form

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Matt R. (Guest)
on 2006-03-08 22:48
(Received via mailing list)
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,
Mark Reginald J. (Guest)
on 2006-03-09 00:35
(Received via mailing list)
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 %>
<table>
<% 10.times do |i|
      @image = @images[i] %>
<tr>
<%= file_columm_field 'image', 'filename', :index => i %>
<%= text_field 'image', 'caption', :index => i %>
</tr>
<% end %>
</table>
<%= 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(p)
       @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.
Chris B. (Guest)
on 2006-03-09 01:52
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 %>
> <table>
> <% 10.times do |i|
>       @image = @images[i] %>
> <tr>
> <%= file_columm_field 'image', 'filename', :index => i %>
> <%= text_field 'image', 'caption', :index => i %>
> </tr>
> <% end %>
> </table>
> <%= 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(p)
>        @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.
Mark Reginald J. (Guest)
on 2006-03-09 03:04
(Received via mailing list)
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/Help...

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

--
We develop, watch us RoR, in numbers too big to ignore.
Szymon N. (Guest)
on 2006-03-09 13:04
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 %><br />
<% 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 = '<input type="file" id="images_' + id + '_name"
name="images[' + id + '][name]" size="30" /><br />';
  new Insertion.Before("add_image", image_field);
}
Matt R. (Guest)
on 2006-03-09 22:21
(Received via mailing list)
Thanks a lot. I'll take a look at that when I get home from work and
play
around with it.

Thanks :)
Matt R. (Guest)
on 2006-03-09 22:21
(Received via mailing list)
Also, szymek, thanks for that. I planned on adding that later on.
Mark Reginald J. (Guest)
on 2006-03-10 06:57
(Received via mailing list)
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 %><br />
> <% 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 %><br />
<% end %>


--
We develop, watch us RoR, in numbers too big to ignore.
Szymon N. (Guest)
on 2006-03-10 10:57
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?
Szymon N. (Guest)
on 2006-03-10 11:55
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(p)               #what does build method do?
@images.each { |image| image.save(false) }  #why's 'false' parameter
here?
Mark Reginald J. (Guest)
on 2006-03-10 15:20
(Received via mailing list)
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.
Mark Reginald J. (Guest)
on 2006-03-10 15:33
(Received via mailing list)
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(p)               #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.
Szymon N. (Guest)
on 2006-03-10 17:24
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! :)
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 :) )

in _form.rhtml:

<% @file_fields_no.times do |i| %>
  <% @image = @images[i] %>
  <%= file_field "image", "name", "index" => i %><br />
<% 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?
Szymon N. (Guest)
on 2006-03-10 18:14
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' :)

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?
Szymon N. (Guest)
on 2006-03-10 21:46
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 :)

Thanks again for your patience!
Mark Reginald J. (Guest)
on 2006-03-11 05:35
(Received via mailing list)
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 :)
>
> 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.
Szymon N. (Guest)
on 2006-03-11 21:35
Well i still didn't write the whole code for creating and editing images
using this plugin, so only one new question today :)

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:
<input id="image_name_temp" name="image[name_temp]" type="hidden" />
<input id="image_1_name" name="image[1][name]" size="30" type="file" />
<input id="image_name_temp" name="image[name_temp]" type="hidden" />
<input id="image_2_name" name="image[2][name]" size="30" type="file" />

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.
Szymon N. (Guest)
on 2006-03-12 14:05
New day, new question! :)

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.
This topic is locked and can not be replied to.