apotonick/hooks

hooks in a mixin

chikamichi opened this issue · 6 comments

Hi, taking my example as code base, I turned the Cat class into a module :

require 'hooks'

# That's the generic cat. It does pretty much nothing.
# It's not really a *cat* by the way, just a Ruby class.
# Don't forget to document this properly, cats are literate coders.
module Cat
  include Hooks

  @likes = 'food'
  define_hook :after_dinner # void default implementation

  def dinner_time!
    puts "It's dinner time!"
    run_hook :after_dinner, @likes
  end
end

# But here's *my* REAL cat. It's dumb and hangry. It's a cat.
# MY CAT.
class MyCat
  include Cat

  def initialize(likes = nil)
    @likes = likes ? likes : 'food'
  end

  after_dinner do |what|
    puts "I had a great time. I'd like some more #{what}!"
  end

  after_dinner :want_some_more

  def want_some_more(what)
    puts "No, really, is there some more #{@likes}?"
  end
end

# dumb cat
Cat.new.dinner_time! # => It's dinner time!

# dumber cat
my_cat = MyCat.new('fish')
my_cat.dinner_time!  # => It's dinner time!
                     # => I had a great time. I'd like some more fish!
                     # => No, really, is there some more fish?

Running the script shouts:

tmp_hooks.rb:27:in `<class:MyCat>': undefined method `after_dinner' for MyCat:Class (NoMethodError)
from tmp_hooks.rb:20:in `<main>'

#define_hook doesn't work in a module since it is calling superclass to inherit callbacks from ancestor classes. Right now, the Hooks module is not designed to be placed in modules but classes, only. Does it make sense to define hooks in modules and then mix them into classes?

Well, that'd be quite useful IMO, mixins are everywhere. I guess we could introspect a little the ancestors chain to handle both cases. I don't have much time right now (really must handle the ruby-lang.org refacto right now!), but I'll come back to it one day :)

it does work in module. On both instance and class level, which ActiveSupport/callbacks can't do. Thanks.

require 'rubygems'
require 'active_support/concern'
require 'hooks'

module Mod1
  extend ActiveSupport::Concern

  included do
    define_hook :after_save
    after_save :log

    define_hook :after_create
    after_create :log
    after_create :send_email
  end

  def log
    puts 'instance log'
  end

  module ClassMethods
    def log
      puts "class log"
    end

    def send_email
      puts "send email"
    end
  end
end

class Cat
  include Hooks
  include Mod1

  def save
    puts 'save'
    run_hook :after_save
  end

  class << self
    def create
      puts 'create'
      run_hook :after_create
    end
  end
end

Cat.new.save
Cat.create

This should be closed, as @dorren pointed out. You don't need ActiveSupport::Concern either, you can use the built-in Module.included method.

module Cat
  def self.included(base)
    base.send :include, Hooks
    base.define_hook :after_save
    base.define_hook :after_create
  end
end

Ok, awesome! :D Is anyone keen to write a test for using hooks with a module? Is the module then to be included in an object or how do you guys use this?

Thanks @dorren.