Hi --

On Thu, 16 Jul 2009, uberllama wrote:

> Okay guys, I've been beating my head against the wall with this one
> all day and was hoping I might garner some enlightenment from the
> gathered geniuses. I am including some pared down code to demonstrate
> my issue.
>
> Basically, I want my form builder to automatically add an :onchange
> handler to a specific form field (for my example, I am using
> an :onclick with a generic form builder).
>
> My problem is that unless I pass in at least one hash value to the
> field helper, the option doesn't get added. i.e.:
>
> [b]These work (the onclick will get added):[/b]
>
> [code]<%= f.text_field :title, :class => 'sexy' %>[/code]
> [code]<%= f.text_field :title, :label => 'Topic' %>[/code]
> [code]<%= f.text_field :title, :class => 'sexy', :label => 'Topic' %>[/
> code]
>
> [b]This does not (no onclick, very sad):[/b]
> [code]<%= f.text_field :title %>[/code]
>
> I have appended question marks to the blocks in question below.
>
> [code]
> class ExampleFormBuilder < ActionView::Helpers::FormBuilder
>
>  def self.create_tagged_field(method_name)
>    define_method(method_name) do |attr, *args|
>      options = args.extract_options!
>
>      # ?? This doesn't work unless the field helper in the view is
> passing in a hash of options.  ??
>      #options.merge!(:onclick => "alert('foo');")
>      # ?? Direct assignment doesn't work either ??
>      #options[:onclick] = "alert('foo');"
>
>      label_text = "#{options[:label] || attr.to_s.humanize}:"
>      label = @template.content_tag('label', label_text, :for => "#
> {...@object_name}_#{attr}")
>
>      # remove my custom hash key so it doesn't pollute the output
>      options.delete_if {|key, value| key == :label}
>
>      # ?? I don't need to do this, but why ??
>      #args = (args << options) unless options.blank?
>
>      @template.content_tag('p', label + '<br />' + super)
>    end
>  end
>
>  field_helpers.each do |name|
>    create_tagged_field(name)
>  end
>
> end
> [/code]
>
> Even if I am not passing in a hash, the extract_options! command
> should create a hash object from the args, so I don't know what I'm
> missing here.
>
> My second part to this question is around the splat operator, *args,
> and options. Let's say I pass the field helper the option :class =>
> 'sexy'.  Now, since I am extracting the options hash from the args, I
> assumed I needed to add them back in before calling super. But
> spookily enough, I don't. Do the options extracted from *args maintain
> a reference, or am I missing something completely obvious.

Let me start with the last question first.

Short answer: super with implicit arguments, when you use
define_method and a block, doesn't work the same as it does when you
user super in a def-based method definition. You have to provide the
arguments explicitly.

Longer answer:

If you do this:

   class A
     def m(*args)
       print "In A#m: "
       p args
     end
   end

   class B < A
     def m(*args)
       print "In B#m: "
       p args
       args = ["hi!"]
       super
     end
   end

   B.new.m(1,2,3)

you get this:

   In B#m: [1, 2, 3]
   In A#m: ["hi!"]

The call to super uses the variable args -- not even the object that
was bound to args originally, but the variable args -- to make the
call to A#m. Now, look at this variation. Assume the same A, and then:

   class C < A
     define_method(:m) do |*args|
       print "In C#m: "
       p args
       args = ["hi!"]
       super
     end
   end

   C.new.m(1,2,3)

This gives you this output:

   In C#m: [1, 2, 3]
   In A#m: [1, 2, 3]

This time, the variable name "args", which comes from a block
parameter (and not a method parameter), doesn't play the same role.
Instead, as far as I can tell, super is using a copy of the original
object that was bound to args. Even adding elements to args doesn't
cause A#m to produce anything different.

And... you will be very interested in what happens if I run the above
under Ruby 1.9.1:

In C#m: [1, 2, 3]
sup.rb:22:in `block in <class:C>': implicit argument passing of super
from method defined by define_method() is not supported. Specify all
arguments explicitly. (RuntimeError)
        from sup.rb:27:in `<main>'

In other words, you can't do it any more anyway -- probably because it
didn't really work in the first place, so it's gone.

So... change super to super(attr, *args), restore the args << options
thing (but write it more simply, like this:

   args << options unless options.empty?

:-) and you should be OK (or very close to it).


David

-- 
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Now available: The Well-Grounded Rubyist (http://manning.com/black2)
Training! Intro to Ruby, with Black & Kastner, September 14-17
(More info: http://rubyurl.com/vmzN)

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to