# frozen_string_literal: true # in some class # def self.before(proc=nil, &block) # BeforeAndAfter.add(self, caller[0], :before, proc || block) # end # or # BeforeAndAfter.define self, :before, :after # # then to execute # instance_object = SomeClass.new # BeforeAndAfter.execute(instance_object, :before) # or # before do # ... # end # or # def before # super # ... # end # # logic is very simple, keep all pointers to all blocks in one class, resolve and execute as needed # we keep methods and ponters in different hashes to allow hot reload while development module BeforeAndAfter extend self @@methods = {} @@pointers = {} def add(klass, unique_id, action, method) key = Crypt.md5(unique_id) @@pointers[key] = method @@methods[klass.to_s] ||= {} @@methods[klass.to_s][action] ||= [] @@methods[klass.to_s][action].push(key) unless @@methods[klass.to_s][action].index(key) end def execute(instance_object, action) instance_object.send(action) # execute for self and parent for name in instance_object.class.ancestors.reverse.map(&:to_s) next if name == 'Object' if @@methods[name] && @@methods[name][action] for el in @@methods[name][action].map { |o| @@pointers[o] } if el.kind_of?(Symbol) instance_object.send(el) else instance_object.instance_exec &el end end end end end def define(klass, *args) for action in args klass.class_eval %[ def #{action} end def self.#{action}(proc=nil, opts={}, &block) BeforeAndAfter.add(self, caller[0], :#{action}, proc || block) end ] end end end