How to hook a callback through inheritance

Hello,

I've got some troubles with Ruby about callbacks (and inheritance). Here
is my code:

  class Lmao
    def initialize
      @str = "HAHAHAHAHAHHAHAHAH"
      @before_laughing = []
    end

    def self.inherited(base)
      base.extend(Callbacks)
    end

    def laughing
      @before_laughing.each {|method| send(method) }
      @str
    end
  end

  module Callbacks
    def before_laughing(*methods)
      @before_laughing = methods
    end
  end

  class Lol < Lmao
    before_laughing :downcase_please

    def downcase_please
      @str.downcase!
    end
  end

  a = Lol.new
  a.laughing # => "HAHAHAHAHAHHAHAHAH"

And as you can see, my before laughing callback don't work... because
the array @before_laughing is empty. I believe this can be fixed by
editing the way I save *methods into an Lol's instance method (from
inside Callbacks). But I don't really see how...

If you know the solution, thanks for your light!

···

--
Posted via http://www.ruby-forum.com/.

Thanks to **Mon_Ouie**, the solution is:

    class Lmao
      def initialize
        @str = "HAHAHAHAHAHHAHAHAH"
      end

      def self.inherited(base)
        base.extend(Callbacks)
      end

      def laughing
        self.class.callbacks_before_laughing.each {|method| send(method)
}
        @str
      end
    end

    module Callbacks
      def before_laughing(*methods)
        @before_laughing = methods
      end

      def callbacks_before_laughing
        @before_laughing
      end
    end

    class Lol < Lmao
      before_laughing :downcase_please

      def downcase_please
        @str.downcase!
      end
    end

Pretty awesome.

···

--
Posted via http://www.ruby-forum.com/.

The problem is that before_laughing is being invoked as a class method on
Lol. So when you say "before_laughing :downcase_please", you aren't setting
@before_laughing on instances of Lol, but rather on Lol itself.

I suppose one way to do this is to leave the instance var on the class, then
just ask the class for its contents and execute them the same way. With this
implementation, that means you can't add callbacks for individual objects,
though.

class Lmao
  def initialize
    @str = "HAHAHAHAHAHHAHAHAH"
  end

  def self.inherited(base)
    base.extend Callbacks
  end

  def laughing
    self.class.before_laughing.each do |method|
      send method
    end
    @str
  end
end

module Callbacks
  def before_laughing(*methods)
    if methods.empty?
      @before_laughing ||= Array.new
    else
      @before_laughing = methods
    end
  end
end

class Lol < Lmao
  before_laughing :downcase_please

  def downcase_please
    @str.downcase!
  end
end

a = Lol.new
a.laughing # => "hahahahahahhahahah"

Josh Cheek wrote in post #971483:

The problem is that before_laughing is being invoked as a class method
on
Lol. So when you say "before_laughing :downcase_please", you aren't
setting
@before_laughing on instances of Lol, but rather on Lol itself.

Okay.

I suppose one way to do this is to leave the instance var on the
class, then just ask the class for its contents and execute them
the same way.

Using your code, I have the same result then Mon_Ouie, with one less
method in the code and no errors when a callback isn't called. It's just
so cool.

With this implementation, that means you can't add callbacks
for individual objects, though.

However, I am not sure to understand this point. I believe I can do it.
Here I tried to add different callbacks on different classes (and every
thing is working find):

class Lol < Lmao
  before_laughing :downcase_please

?> def downcase_please

    @str.downcase!
  end
end

=> nil

?> a = Lol.new
=> #<Lol:0x101f11448 @str="HAHAHAHAHAHHAHAHAH">

a.laughing

=> "hahahahahahhahahah"

?> class Lool < Lmao

  before_laughing :add_ok

?> def add_ok

    @str.concat("_ok")
  end
end

=> nil

?> b = Lool.new
=> #<Lool:0x101ee9448 @str="HAHAHAHAHAHHAHAHAH">

b.laughing

=> "HAHAHAHAHAHHAHAHAH_ok"

?> class Loool < Lmao

  before_laughing :add_ok, :downcase_please

?> def add_ok

    @str.concat("_ok")
  end

?> def downcase_please

    @str.downcase!
  end
end

=> nil

?> c = Loool.new
=> #<Loool:0x101eb4310 @str="HAHAHAHAHAHHAHAHAH">

c.laughing

=> "hahahahahahhahahah_ok"

But if I understand correctly, I can not do so by class instance...
Well, okay. For my needs, it's just perfect anyway. Thank you!!

···

--
Posted via http://www.ruby-forum.com/\.

I mean that you can't add a callback for a given instance of the class.
Every instance of Lol will have the same before_laughing callbacks. It
shouldn't be too much more work to give them the ability to have such
behaviour, if you need it, I just thought I should point it out since your
initial implementation seemed to assume that the @before_laughing was set on
each instance.

···

On Thu, Dec 30, 2010 at 12:02 PM, Satsou Sa <moshimoshi.here@gmail.com>wrote:

> With this implementation, that means you can't add callbacks
> for individual objects, though.

However, I am not sure to understand this point. I believe I can do it.
Here I tried to add different callbacks on different classes (and every
thing is working find):

It may be too late but how about this?

···

----
class Lmao
  def initialize
    @str = "HAHAHAHAHAHHAHAHAH"
  end
  def laughing
    @str
  end
=begin
  def self.inherited(base)
    base.extend(Callbacks)
  end
=end
end

module Callbacks
  def before_laughing(*methods)
    define_method(:laughing) do
      methods.each do |m|
        send(m)
      end
      super
    end
  end
end

class Lol < Lmao
  extend Callbacks
  before_laughing :downcase_please
  def downcase_please
    @str.downcase!
  end
end

a = Lol.new
p a.laughing
----

Masa