module RubyExt::Callbacks class AbstractCallback attr_accessor :terminator attr_reader :executor def executor= executor @executor = executor.must_be.a Symbol, Proc end attr_reader :conditions def conditions= conditions @conditions = {} conditions.each do |k, v| @conditions[k.to_sym] = if v.is_a? Symbol v.to_s elsif v.is_a? Array v.collect{|e| e.to_s} else v end end @conditions end def terminate? target, result unless terminator.nil? if terminator.is_a? Proc terminator.call target, result else result == terminator end else false end end def run? target, inf if cond = conditions[:if] evaluate_if(cond, target, inf) elsif cond = conditions[:unless] !evaluate_if(cond, target, inf) elsif cond = conditions[:only] evaluate_only(cond, inf) elsif cond = conditions[:except] !evaluate_only(cond, inf) else true end end def add_to_chain target, inf, &the_next if run? target, inf build_block target, &the_next else the_next end end alias_method :deep_clone, :clone protected def evaluate_if cond, target, inf if cond.is_a? String target.send cond elsif cond.is_a? Proc cond.call target, inf else must_be.never_called end end def evaluate_only cond, inf method = inf[:method].to_s if cond.is_a? String cond == method elsif cond.is_a? Array cond.include? method end end end class BeforeCallback < AbstractCallback def build_block target, &the_next lambda do result = if executor.is_a? Symbol target.send executor elsif executor.is_a? Proc executor.call target else must_be.never_called end the_next.call unless terminate? target, result end end end class AroundCallback < AbstractCallback def build_block target, &the_next lambda do if executor.is_a? Symbol target.send executor, &the_next elsif executor.is_a? Proc executor.call target, the_next else must_be.never_called end end end end class AfterCallback < AbstractCallback def build_block target, &the_next lambda do result = if executor.is_a? Symbol target.send executor elsif executor.is_a? Proc executor.call target else must_be.never_called end the_next.call unless terminate? target, result end end end def run_callbacks callback_name, additional_information = {}, &block callback_name = callback_name.to_s block.must_be.defined callbacks = self.class.callbacks[callback_name] chain_head = block if callbacks and !callbacks.empty? callbacks.reverse_each do |callback| block = callback.add_to_chain self, additional_information, &chain_head chain_head = block if block end end chain_head.call end module ClassMethods inheritable_accessor :callbacks, {} def set_callback callback_name, type, *executor_or_options, &block # parsing arguments type, callback_name = type.to_s, callback_name.to_s opt = executor_or_options.extract_options! "You can't provide both method name and block for filter!" if block and !executor_or_options.empty? executor = block || executor_or_options.first type.must_be.in %w{before around after} executor.must_be.defined # creating callback callback = case type when 'before' then BeforeCallback.new when 'around' then AroundCallback.new when 'after' then AfterCallback.new end callback.executor = executor callback.terminator = opt.delete :terminator callback.conditions = opt callbacks[callback_name] ||= [] callbacks[callback_name] << callback end end end