DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Lance has posted 24 posts at DZone. View Full User Profile

Ruby Metaprogramming - Subclassable Callback Module

08.15.2010
| 3069 views |
  • submit to reddit
        
require 'rubygems'
require 'active_model'

# use this if you want to create before and after filters around a commonly used
# method, but still want to define the method in subclasses.
module SubclassableCallbacks
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    attr_accessor :subclassable_callbacks
    
    def subclassable_callbacks(*methods)
      @subclassable_callbacks ||= []
      
      unless methods.empty?
        @subclassable_callbacks = @subclassable_callbacks.concat(methods).flatten.compact.map(&:to_sym).uniq
        define_model_callbacks *methods
        class_eval do
          methods.each do |variable|
            define_method "implementation_#{variable.to_s}" do; end
            define_method variable do
              send("_run_#{variable.to_s}_callbacks") do
                send("implementation_#{variable.to_s}")
              end
            end
            alias_method "superclass_#{variable.to_s}", variable
          end
        end
      end
      
      @subclassable_callbacks
    end
  end
  
  def self.override
    ObjectSpace.each_object(Class) do |object|
      if object.ancestors.include?(SubclassableCallbacks)
        object.class_eval do
          methods = object.subclassable_callbacks
          last_index = object.superclass.ancestors.index(SubclassableCallbacks)
          if last_index && last_index > 0
            superclass_methods = object.superclass.ancestors[0..last_index - 1].map(&:subclassable_callbacks)
            methods = methods.concat(superclass_methods).flatten.uniq
          end 
          methods.each do |name|
            alias_method "subclass_#{name}", "#{name}"
            alias_method "implementation_#{name}", "subclass_#{name}"
            alias_method "#{name}", "superclass_#{name}"
          end
        end
      end
    end
  end
end

class BaseModel
  extend ActiveModel::Callbacks
  include SubclassableCallbacks

  subclassable_callbacks :save
  
  before_save { puts "[save:before]"}
  after_save { puts "[save:after]"}
end

class SomeModel < BaseModel
  def save
    puts "[saving some_model...]"
    true
  end
end

class AnotherModel < BaseModel
  def save
    puts "[saving another_model...]"
    true
  end
end

SubclassableCallbacks.override

SomeModel.new.save
#=> [save:before]
#=> [saving some_model...]
#=> [save:after]
AnotherModel.new.save
#=> [save:before]
#=> [saving another_model...]
#=> [save:after]