with_options Confusion

I was looking through the code of attachment_fu and came across the following block pulled from attachment_fu:60

with_options :foreign_key => 'parent_id' do |m|   m.has_many :thumbnails, :dependent => :destroy, :class_name => options[:thumbnail_class].to_s   m.belongs_to :parent, :class_name => base_class.to_s end

I've never seen with_options called without being tied to a map, e.g. map.with_options, so I opened up the with_options code to see what was going on and with my newbie Ruby knowledge can't seem to figure this one out.

Here's the object extension in ActiveSupport that gets called

  def with_options(options)     yield ActiveSupport::OptionMerger.new(self, options)   end

That seems straight forward but the OptionMerger boggles me. Here's the full module

module ActiveSupport   class OptionMerger #:nodoc:     instance_methods.each do |method|       undef_method(method) if method !~ /^(__|instance_eval|class)/     end

    def initialize(context, options)       @context, @options = context, options     end

    private       def method_missing(method, *arguments, &block)         merge_argument_options! arguments         @context.send(method, *arguments, &block)       end

      def merge_argument_options!(arguments)         arguments << if arguments.last.respond_to? :to_hash           @options.merge(arguments.pop)         else           @options.dup         end       end   end end

I take it that the object is initialized and then the instance methods are iterated over undeffing methods that don't match the give regex. Why? I don't know. The instance_methods method would also just apply to the OptionMerger class methods right? I also don't see how the private methods are called. Any help to further expand my ruby/ ActiveSupport knowledge is much appreciated.

Eric

I was looking through the code of attachment_fu and came across the following block pulled from attachment_fu:60

with_options :foreign_key => 'parent_id' do |m|   m.has_many :thumbnails, :dependent => :destroy, :class_name => options[:thumbnail_class].to_s   m.belongs_to :parent, :class_name => base_class.to_s end

I've never seen with_options called without being tied to a map, e.g. map.with_options, so I opened up the with_options code to see what was going on and with my newbie Ruby knowledge can't seem to figure this one out.

with_options is general, not tied to map/routes. Think of it as a decorator (Decorator pattern - Wikipedia) or AOP-style before advice. Its sole assumption is that the last argument of any method of the decorated object takes an options hash.

When used as map.with_options { ... } it is the map object that is decorated. When no receiver is explicitly given, self is decorated in the block.

Here's the object extension in ActiveSupport that gets called

  def with_options(options)     yield ActiveSupport::OptionMerger.new(self, options)   end

That seems straight forward but the OptionMerger boggles me. Here's the full module

module ActiveSupport   class OptionMerger #:nodoc:     instance_methods.each do |method|       undef_method(method) if method !~ /^(__|instance_eval|class)/     end

    def initialize(context, options)       @context, @options = context, options     end

    private       def method_missing(method, *arguments, &block)         merge_argument_options! arguments         @context.send(method, *arguments, &block)       end

      def merge_argument_options!(arguments)         arguments << if arguments.last.respond_to? :to_hash           @options.merge(arguments.pop)         else           @options.dup         end       end   end end

I take it that the object is initialized and then the instance methods are iterated over undeffing methods that don't match the give regex. Why? I don't know.

Because that ensures that the existing instance methods that plain Ruby objects come with don't get in the way. Therefore, when you call a method on an OptionMerger instance, its method_missing method is called, which in turn merges the common options, defined in the enclosing with_options call, into the options of the current call and delegates to the real receiver.

The instance_methods method would also just apply to the OptionMerger class methods right?

Yes.

I also don't see how the private methods are called.

method_missing is called by the Ruby runtime, not explicitly.

HTH, Michael

Not exactly intuitive but very cool. I don't know why I didn't realize the method_missing method was called due to the missing methods.

Vielen dank,

Eric