Populating array of text_fields from an array of model objec


#1

I have in my view the following:

<% 0.upto(@num_performances) do |idx| -%>
<%= text_field ‘performance’, ‘city’, :index => idx, %>
<%= text_field ‘performance’, ‘venue’, :index => idx, %>
<% end -%>

and in my controller I have:

@performance = [Performance.new(“city” => “Toronto”, “venue” => “Opera
House”),
Performance.new(“city” => “Toronto”, “venue” =>
“The Rivoli”),
Performance.new(“city” => “Toronto”, “venue” =>
“The Gladstone”)]

but when I try to view this page, rails gives me the exception:

undefined method `city’ for #Array:0x8dac4f4

so it looks like rails is trying to send the “city” method to the
entire @performance array, rather than the element at the given index.
When I comment out the above @performance line, my view is rendered
and I get:


So I can post from this form to an action and I receive the following:

Parameters: { performance"=>{“0”=>{“city”=>“Toronto”, “time”=>"",
“date”=>"", “venue”=>“The Opera House”, “city_id”=>""},
“1”=>{“city”=>“Toronto”, “time”=>"", “date”=>"", “venue”=>“The
Social”, “city_id”=>""}, “2”=>{“city”=>"", “time”=>"", “date”=>"",
“venue”=>"", “city_id”=>""}}, … }

which I can easily turn into an array of Performance objects… But the
problem is, say the user posts this form and it has errors - I need to
re-populate the form fields, but I’m not sure how to get rails to take
an array of Performance objects and insert the appropriate elements
back into the corresponding text fields. I thought that by providing
an :index parameter to the text_field declaration, rails would realize
that it should send the method (such as ‘city’, or ‘venue’) to the
element of the array at that index, and not the entire array…

Of course I could just change my view so that I manually pull out the
information from the @performance array by iterating over it, but I
just wanted to check to see if there’s something I’m doing wrong
before going that route. Any information is greatly appreciated,
thanks.

Mike


#2

Mike G. wrote:

House"),
Performance.new(“city” => “Toronto”, “venue” =>
“The Rivoli”),
Performance.new(“city” => “Toronto”, “venue” =>
“The Gladstone”)]

but when I try to view this page, rails gives me the exception:

undefined method `city’ for #Array:0x8dac4f4

Pluralize your controller variable (@performances = …), then in
your view write:

<% @performances.each_with_index do |@performance, idx| -%>
<%= text_field ‘performance’, ‘city’, :index => idx %>
<%= text_field ‘performance’, ‘venue’, :index => idx %>
<% end -%>


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


#3

On 5/18/06, Mark Reginald J. removed_email_address@domain.invalid wrote:

@performance = [Performance.new(“city” => “Toronto”, “venue” => "Opera
Pluralize your controller variable (@performances = …), then in
your view write:

<% @performances.each_with_index do |@performance, idx| -%>
<%= text_field ‘performance’, ‘city’, :index => idx %>
<%= text_field ‘performance’, ‘venue’, :index => idx %>
<% end -%>

thanks for the reply Mark. Although, isn’t the above simply iterating
over the elements of the @performances array, and then having
text_field call the corresponding method on the element? Shouldn’t
rails do this automatically if you specify the :index parameter to
text_field? ie, if text_field notices that you’re passing :index =>
1, then it should understand that the @performances ivar is an array,
and that we should send the ‘city’ method (for example) to element 1
of the array, rather than the array itself.

The problem appears to be inside the following method in
ActionView::Helpers::InstanceTag

  def value_before_type_cast
    unless object.nil?
      object.respond_to?(@method_name + "_before_type_cast") ?
        object.send(@method_name + "_before_type_cast") :
        object.send(@method_name)
    end
  end

instead of sending the method to object[index], it sends it directly
to object, which is not what we want if an index parameter is being
given to text_field. I changed this behaviour by adding the following
to a file called “additions.rb” which I put in the lib directory of my
rails project, and then included inside environment.rb:

module ActionView
module Helpers
class InstanceTag #:nodoc:
include Helpers::TagHelper

  alias :__to_input_field_tag :to_input_field_tag

  # by default, if we pass :index => n to text_field, it doesn't 

send the
# method to the element at index n of the given array, but instead
# sends the method to the entire array, which is not what we want.
We
# modify this by checking to see if the user has passed an
"index parameter
# and if so, we send the method to the object at the given element
def to_input_field_tag(field_type, options = {})
options = options.stringify_keys
options[“size”] ||= options[“maxlength”] ||
DEFAULT_FIELD_OPTIONS[“size”]
options = DEFAULT_FIELD_OPTIONS.merge(options)
if field_type == “hidden”
options.delete(“size”)
end
options[“type”] = field_type

    # if the user has given the :index => n parameter, then we try 

to obtain
# the value of the element at the given index of the array
if options.has_key?(“index”)
options[“value”] ||=
value_before_type_cast_with_index(options[“index”]) unless field_type
== “file”
else
options[“value”] ||= value_before_type_cast unless
field_type == “file”
end

    add_default_name_and_id(options)
    tag("input", options)
  end

  def value_before_type_cast_with_index(index)
    unless object.nil? || object[index].nil?
      object[index].respond_to?(@method_name + "_before_type_cast") 

?
object[index].send(@method_name + “_before_type_cast”) :
object[index].send(@method_name)
end
end

end #InstanceTag

end #Helpers
end #ActionView

this seems to do the trick for now, but I’m not sure what problems I
may run into in the future (perhaps there was a reason that they
didn’t implement it this way). Of course this will fail if you pass
an :index parameter to your text_field options, but don’t actually
have an array of values to pass it from your controller (but then, why
else would you be using the :index parameter…).

Anyways, thanks for the help, and of course if you or anyone else have
any suggestions, advice, comments, criticism, etc on the above, please
let me know.

Mike


#4

Mike G. wrote:

text_field? ie, if text_field notices that you’re passing :index =>
1, then it should understand that the @performances ivar is an array,
and that we should send the ‘city’ method (for example) to element 1
of the array, rather than the array itself.

Yes, I was also originally under the impression that the index option
worked the way you suggest. It’s a natural assumption, given the name
of the option.

The advantage of the way it’s been done is that the index doesn’t have
to be an index into an array, but can be a hash key or object attribute
(such as the id attribute, for which the [] notation can instead be
used).

Changing the index option to work in the way you suggest would break too
many apps, but perhaps a new option can be added. As you can see, it’s
not
hard to make the existing option work with arrays, but the non-intuitive
way it works is going to keep tripping people up.


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