Forum: Ruby How to alias_method_chain initialize from a mixin.

F2ff23645203ed2399cfe8b447af437a?d=identicon&s=25 Evan Senter (evansenter)
on 2009-11-22 08:09
Hi,

I've been having a little trouble the past couple days getting an
alias_method_chain working on the initialize method via a module getting
mixed into a class. For example:

- - - - -

module Dsl
  def self.included(base)
    base.instance_eval do
      include InstanceMethods

      alias_method :initialize_without_block_support, :initialize
      alias_method :initialize, :initialize_with_block_support
    end
  end

  module InstanceMethods
    def initialize_with_block_support
      p "initialize_with_block_support"
      initialize_without_block_support
    end
  end
end

class Something
  include Dsl

  def initialize
    p "initialize_without_block_support"
  end
end

Something.new #=> prints "initialize_without_block_support", should
print "initialize_with_block_support" as well.

- - - - -

The problem in this contrived example (I believe) is that initialize is
redefined in the Something class body after the module is mixed in, thus
negating the alias_method_chain that was set up previously. So one
option is to just include modules after the initialize method is
defined, but that's not at all elegant, so I've been trying to find a
better way.

The next possibility I considered was using the method_added hook.
Something that would work like the following:

- - - - -

module Dsl
  def self.included(base)
    base.extend ClassMethods
    base.instance_eval { include InstanceMethods }
  end

  module ClassMethods
    def method_added(name)
      if name == :initialize && !@redefining_initialize
        @redefining_initialize = :stop_those_infinite_loops!

        alias_method :initialize_without_block_support, :initialize
        alias_method :initialize, :initialize_with_block_support

        remove_instance_variable :@redefining_initialize
      end
    end
  end

  module InstanceMethods
    def initialize_with_block_support
      p "initialize_with_block_support"
      initialize_without_block_support
    end
  end
end

class Something
  include Dsl

  def initialize
    p "initialize_without_block_support"
  end
end

Something.new

- - - - -

This works, but feels pretty hackety. If you happen to have any
experience doing something like this and have an alternative solution,
or feedback on this implementation using method_added, I'd appreciate
hearing it. As a note, the @redefining_initialize guard is necessary
because alias_method triggers the method_added hook.

Thanks for your time!
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2009-11-24 00:27
(Received via mailing list)
On Nov 21, 2009, at 23:09, Evan Senter wrote:

> Hi,
>
> I've been having a little trouble the past couple days getting an
> alias_method_chain working on the initialize method via a module
> getting
> mixed into a class. For example:

Don't use alias_method_chain.

Ever.

You already have inheritance.

It works far better.

Use super:

module DSL
   def initialize
     puts "dsl initialize"
   end
end

class Something
   include DSL

   def initialize
     super
     puts "something initialize"
   end
end

Oh, inheritance also works when you don't bother to define initialize
in Something.
F2ff23645203ed2399cfe8b447af437a?d=identicon&s=25 Evan Senter (evansenter)
on 2009-11-24 01:05
I explored your idea of using inheritance a bit further, and though it's
not entirely transparent to the consuming class (super has to be called)
it is a clean implementation, and would look something like this:

class A
  def initialize(&block)
    @_original_self = block.binding.eval("self")
    instance_eval(&block) if block_given?
    remove_instance_variable :@_original_self
    finish_init if defined? :finish_init
  end

  def print_me_a
    p "print_me_a"
  end

  def method_missing(name, *args, &block)
    @_original_self ? @_original_self.send(name, *args, &block) : super
  end
end

class B < A
  def initialize
    super
  end

  def finish_init
    p "finish_init"
  end

  def print_me_b
    p "print_me_b"
  end
end

class C
  def self.run
    B.new do
      print_me_a
      print_me_b
      print_me_c
    end
  end

  def self.print_me_c
    p "print_me_c"
  end
end

b = C.run

#=> "print_me_a"
#=> "print_me_b"
#=> "print_me_c"
#=> "finish_init"

Thanks for the input, it was helpful!
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.