Forum: Ruby on Rails populating array of text_fields from an array of model objec

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.
0900e6a4828bd989f96427082c6c74ca?d=identicon&s=25 Mike Garey (random52k)
on 2006-05-18 23:27
(Received via mailing list)
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:

<input class="textmid" id="performance_0_city"
name="performance[0][city]" size="30" type="text" />
<input class="textmid" id="performance_0_venue"
name="performance[0][venue]" size="30" type="text" />

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
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2006-05-19 03:08
(Received via mailing list)
Mike Garey 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.
0900e6a4828bd989f96427082c6c74ca?d=identicon&s=25 Mike Garey (random52k)
on 2006-05-19 17:50
(Received via mailing list)
On 5/18/06, Mark Reginald James <mrj@bigpond.net.au> 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
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2006-05-20 12:47
(Received via mailing list)
Mike Garey 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.
This topic is locked and can not be replied to.