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!

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.

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 C. 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!!

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

On Thu, Dec 30, 2010 at 12:02 PM, Satsou Sa
[email protected]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):

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.