# * George Moschovitis # (c) 2005 Navel, all rights reserved. # $Id: aspects.rb 20 2005-04-15 15:18:36Z gmosx $ require 'glue/property' module Glue # An Aspect is a class that defines advices. class Aspect class << self def wrap(target, methods = target.instance_methods, pre = :pre, post = :post) target.send(:include, Aspects) unless target.ancestors.include?(Aspects) target.wrap(self, :pre => pre, :post => post) end alias_method :observe, :wrap end def wrap(target, methods = target.instance_methods, pre = :pre, post = :post) target.send(:include, Aspects) unless target.ancestors.include?(Aspects) target.wrap(self, :pre => pre, :post => post) end alias_method :observe, :wrap end # Add support for Aspect Oriented Programming (AOP). # # === Examples # # class Controller # pre :force_login, :where => :prepend # wrap Benchmark, :on => :index # post :taraa, :on => login # end # # module Timestamped # pre :on => :og_insert { |this| this.create_time = Time.now } # pre :on => :og_update { |this| this.update_time = Time.now } # pre :on => [:og_insert, :og_update] { |this| this.create_time = Time.now } # end module Aspects # Store the code and the metadata (options) for # an Advice. Advice = Struct.new(:code, :options) # Apply the advices to the target class. def self.wrap(target, methods = target.instance_methods) include_advice_modules(target) for m in [methods].flatten args = [] target.instance_method(m).arity.times { |i| args << "a#{i}" } args = args.join(',') target.module_eval <<-end_eval, __FILE__, __LINE__ alias_method :__unwrapped_#{m}, :#{m} def #{m}(#{args}) #{gen_advice_code(m, target.advices, :pre)} __unwrapped_#{m}(#{args}) #{gen_advice_code(m, target.advices, :post)} end end_eval end end # Include Modules that define advices. def self.include_advice_modules(target) for a in target.advices if a.code.is_a?(Module) and (!a.code.class.ancestors.include?(Class)) target.module_eval %{ include #{a.code} } options = a.options.reject { |k,v| k == :pre || k == :post } method = (a.options[:pre] || 'pre').to_s if a.code.instance_methods.include?(method) options.update(:where => :prepend, :join => :pre) target.advices << Advice.new(method, options) end method = (a.options[:post] || 'post').to_s if a.code.instance_methods.include?(method) options.update(:where => :append, :join => :post) target.advices << Advice.new(method, options) end end end # Remove the original advice. target.advices.delete_if do |a| a.code.is_a?(Module) and (!a.code.class.ancestors.include?(Class)) end end # Generates the code to call the aspects. def self.gen_advice_code(method, advices, join = :pre) # :nodoc: code = '' advices.each_with_index do |advice, idx| o = options = advice.options if only = options[:only] || options[:on] next unless [only].flatten.include?(method.to_sym) elsif except = options[:except] next if [except].flatten.include?(method.to_sym) end advice = advice.code if advice.is_a?(Symbol) or advice.is_a?(String) next if o[:join] != join code << "#{advice}; " elsif advice.respond_to?('call') next if o[:join] != join code << "self.class.advices[#{idx}].code.call(self); " elsif advice.is_a?(Class) if advice.class.ancestors.include?(Class) if m = o[join] and advice.methods.include?(m.to_s) code << "#{advice}.#{m}(self); " end else # Module, allready handled. end else if m = o[join] and advice.methods.include?(m.to_s) code << "self.class.advices[#{idx}].code.#{m}(self); " end end end return code end def self.append_features(base) super base.extend(ClassMethods) base.module_eval %{ Glue::PropertyUtils.enchant(self) def self.advices __meta[:advices] || [] end def self.advices=(advices) __meta[:advices] = advices end # def self.inherited(child) # super # child.send(:include, Aspects) # end # # def self.append_features(base) # super # base.send(:include, Aspects) # end } end module ClassMethods # Add a pre (before) advice. def pre(*args, &block) o = options = { :join => :pre, :where => :prepend, } options.update(args.pop) if args.last.is_a?(Hash) if block_given? advices = [ Advice.new(block, options) ] else advices = args.collect { |a| Advice.new(a, options) } end if options[:where] == :prepend self.advices = advices + self.advices else self.advices = self.advices + advices end end alias_method :before, :pre # Add a post (after) advice. def post(*args, &block) o = options = { :join => :post, :where => :append, } options.update(args.pop) if args.last.is_a?(Hash) if block_given? advices = [ Advice.new(block, options) ] else advices = args.collect { |a| Advice.new(a, options) } end if options[:where] == :prepend self.advices = advices + self.advices else self.advices = self.advices + advices end end alias_method :after, :post # Add a wrap (arround) aspect. An aspect is a class that # responds to the before and after advices. def wrap(*args) o = options = { :pre => :pre, :post => :post } options.update(args.pop) if args.last.is_a?(Hash) for aspect in args self.advices << Advice.new(aspect, options) end end alias_method :around, :wrap alias_method :observer, :wrap end end end