Mike Garey
2006-May-18 21:25 UTC
[Rails] populating array of text_fields from an array of model objects
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
Mark Reginald James
2006-May-19 01:05 UTC
[Rails] Re: populating array of text_fields from an array of model objects
Mike Garey wrote:> 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>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.
Mike Garey
2006-May-19 15:48 UTC
[Rails] Re: populating array of text_fields from an array of model objects
On 5/18/06, Mark Reginald James <mrj@bigpond.net.au> wrote:> Mike Garey wrote: > > 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> > > 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
Mark Reginald James
2006-May-20 10:46 UTC
[Rails] Re: populating array of text_fields from an array of model objects
Mike Garey wrote:> On 5/18/06, Mark Reginald James wrote: >> <% @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.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.