Inject code that executes after the target instance method.
@param target_method
target_method
@param block
@note
Either method_sym or block is required.
@api public
# File lib/dm-core/support/hook.rb, line 103 103: def after(target_method, method_sym = nil, &block) 104: install_hook :after, target_method, method_sym, :instance, &block 105: end
Inject code that executes after the target class method.
@param target_method
@note
Either method_sym or block is required.
@api public
# File lib/dm-core/support/hook.rb, line 71 71: def after_class_method(target_method, method_sym = nil, &block) 72: install_hook :after, target_method, method_sym, :class, &block 73: end
— Helpers —
# File lib/dm-core/support/hook.rb, line 376 376: def args_for(method) 377: if method.arity == 0 378: "&block" 379: elsif method.arity > 0 380: "_" << (1 .. method.arity).to_a.join(", _") << ", &block" 381: elsif (method.arity + 1) < 0 382: "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block" 383: else 384: "*args, &block" 385: end 386: end
Inject code that executes before the target instance method.
@param target_method
target_method
@param block
@note
Either method_sym or block is required.
@api public
# File lib/dm-core/support/hook.rb, line 87 87: def before(target_method, method_sym = nil, &block) 88: install_hook :before, target_method, method_sym, :instance, &block 89: end
Inject code that executes before the target class method.
@param target_method
target_method
@param block
@note
Either method_sym or block is required.
@api public
# File lib/dm-core/support/hook.rb, line 56 56: def before_class_method(target_method, method_sym = nil, &block) 57: install_hook :before, target_method, method_sym, :class, &block 58: end
# File lib/dm-core/support/hook.rb, line 148 148: def class_hooks 149: self.const_get("CLASS_HOOKS") 150: end
# File lib/dm-core/support/hook.rb, line 275 275: def define_advised_method(target_method, scope) 276: args = args_for(method_with_scope(target_method, scope)) 277: 278: renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised') 279: 280: source = def #{target_method}(#{args}) retval = nil catch(:halt) do #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(#{args}) retval = #{renamed_target}(#{args}) #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, #{args}) retval end end 281: 282: if scope == :instance && !instance_methods(false).any? { |m| m.to_sym == target_method } 283: send(:alias_method, renamed_target, target_method) 284: 285: proxy_module = Module.new 286: proxy_module.class_eval(source, __FILE__, __LINE__) 287: self.send(:include, proxy_module) 288: else 289: source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}} 290: source = %{class << self\n#{source}\nend} if scope == :class 291: class_eval(source, __FILE__, __LINE__) 292: end 293: end
Defines two methods. One method executes the before hook stack. The other executes the after hook stack. This method will be called many times during the Class definition process. It should be called for each hook that is defined. It will also be called when a hook is redefined (to make sure that the arity hasn’t changed).
# File lib/dm-core/support/hook.rb, line 226 226: def define_hook_stack_execution_methods(target_method, scope) 227: unless registered_as_hook?(target_method, scope) 228: raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method" 229: end 230: 231: hooks = hooks_with_scope(scope) 232: 233: before_hooks = hooks[target_method][:before] 234: before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n") 235: 236: after_hooks = hooks[target_method][:after] 237: after_hooks = after_hooks.map{ |info| inline_call(info, scope) }.join("\n") 238: 239: before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack') 240: after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack') 241: 242: hooks[target_method][:in].class_eval #{scope == :class ? 'class << self' : ''} private remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} } def #{before_hook_name}(*args) #{before_hooks} end remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{after_hook_name} } def #{after_hook_name}(*args) #{after_hooks} end #{scope == :class ? 'end' : ''}, __FILE__, __LINE__ + 1 243: end
Generates names for the various utility methods. We need to do this because the various utility methods should not end in = so, while we’re at it, we might as well get rid of all punctuation.
# File lib/dm-core/support/hook.rb, line 196 196: def hook_method_name(target_method, prefix, suffix) 197: target_method = target_method.to_s 198: 199: case target_method[1,1] 200: when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}" 201: when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}" 202: when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}" 203: # I add a _nan_ suffix here so that we don't ever encounter 204: # any naming conflicts. 205: else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}" 206: end 207: end
Returns the correct HOOKS Hash depending on whether we are working with class methods or instance methods
# File lib/dm-core/support/hook.rb, line 140 140: def hooks_with_scope(scope) 141: case scope 142: when :class then class_hooks 143: when :instance then instance_hooks 144: else raise ArgumentError, 'You need to pass :class or :instance as scope' 145: end 146: end
Returns ruby code that will invoke the hook. It checks the arity of the hook method and passes arguments accordingly.
# File lib/dm-core/support/hook.rb, line 263 263: def inline_call(method_info, scope) 264: DataMapper::Hook::ClassMethods.hook_scopes << method_info[:from] 265: name = method_info[:name] 266: if scope == :instance 267: args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : '' 268: %(#{name}(#{args}) if self.class <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id})) 269: else 270: args = respond_to?(name) && method(name).arity != 0 ? '*args' : '' 271: %(#{name}(#{args}) if self <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id})) 272: end 273: end
— Add a hook —
# File lib/dm-core/support/hook.rb, line 307 307: def install_hook(type, target_method, method_sym, scope, &block) 308: assert_kind_of 'target_method', target_method, Symbol 309: assert_kind_of 'method_sym', method_sym, Symbol unless method_sym.nil? 310: assert_kind_of 'scope', scope, Symbol 311: 312: if !block_given? and method_sym.nil? 313: raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"." 314: end 315: 316: if method_sym.to_s[1,1] == '=' 317: raise ArgumentError, "Methods ending in = cannot be hooks" 318: end 319: 320: unless [ :class, :instance ].include?(scope) 321: raise ArgumentError, 'You need to pass :class or :instance as scope' 322: end 323: 324: if registered_as_hook?(target_method, scope) 325: hooks = hooks_with_scope(scope) 326: 327: #if this hook is previously declared in a sibling or cousin we must move the :in class 328: #to the common ancestor to get both hooks to run. 329: if !(hooks[target_method][:in] <=> self) 330: before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack') 331: after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack') 332: 333: hooks[target_method][:in].class_eval remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} } def #{before_hook_name}(*args) super end remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} } def #{after_hook_name}(*args) super end, __FILE__, __LINE__ + 1 334: 335: while !(hooks[target_method][:in] <=> self) do 336: hooks[target_method][:in] = hooks[target_method][:in].superclass 337: end 338: 339: define_hook_stack_execution_methods(target_method, scope) 340: hooks[target_method][:in].class_eval{define_advised_method(target_method, scope)} 341: end 342: else 343: register_hook(target_method, scope) 344: hooks = hooks_with_scope(scope) 345: end 346: 347: #if we were passed a block, create a method out of it. 348: if block 349: method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym 350: if scope == :class 351: singleton_class.instance_eval do 352: define_method(method_sym, &block) 353: end 354: else 355: define_method(method_sym, &block) 356: end 357: end 358: 359: # Adds method to the stack an redefines the hook invocation method 360: hooks[target_method][type] << { :name => method_sym, :from => self } 361: define_hook_stack_execution_methods(target_method, scope) 362: end
# File lib/dm-core/support/hook.rb, line 152 152: def instance_hooks 153: self.const_get("INSTANCE_HOOKS") 154: end
# File lib/dm-core/support/hook.rb, line 388 388: def method_with_scope(name, scope) 389: case scope 390: when :class then method(name) 391: when :instance then instance_method(name) 392: else raise ArgumentError, 'You need to pass :class or :instance as scope' 393: end 394: end
This will need to be refactored
# File lib/dm-core/support/hook.rb, line 210 210: def process_method_added(method_name, scope) 211: hooks_with_scope(scope).each do |target_method, hooks| 212: if hooks[:before].any? { |hook| hook[:name] == method_name } 213: define_hook_stack_execution_methods(target_method, scope) 214: end 215: 216: if hooks[:after].any? { |hook| hook[:name] == method_name } 217: define_hook_stack_execution_methods(target_method, scope) 218: end 219: end 220: end
# File lib/dm-core/support/hook.rb, line 396 396: def quote_method(name) 397: name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_') 398: end
Register a class method as hookable. Registering a method means that before hooks will be run immediately before the method is invoked and after hooks will be called immediately after the method is invoked.
@param hookable_method
be hookable
@api public
# File lib/dm-core/support/hook.rb, line 115 115: def register_class_hooks(*hooks) 116: hooks.each { |hook| register_hook(hook, :class) } 117: end
Registers a method as hookable. Registering hooks involves the following process
Create a blank entry in the HOOK Hash for the method.
Define the methods that execute the before and after hook stack. These methods will be no-ops at first, but everytime a new hook is defined, the methods will be redefined to incorporate the new hook.
Redefine the method that is to be hookable so that the hook stacks are invoked approprietly.
# File lib/dm-core/support/hook.rb, line 165 165: def register_hook(target_method, scope) 166: if scope == :instance && !method_defined?(target_method) 167: raise ArgumentError, "#{target_method} instance method does not exist" 168: elsif scope == :class && !respond_to?(target_method) 169: raise ArgumentError, "#{target_method} class method does not exist" 170: end 171: 172: hooks = hooks_with_scope(scope) 173: 174: if hooks[target_method].nil? 175: hooks[target_method] = { 176: # We need to keep track of which class in the Inheritance chain the 177: # method was declared hookable in. Every time a child declares a new 178: # hook for the method, the hook stack invocations need to be redefined 179: # in the original Class. See #define_hook_stack_execution_methods 180: :before => [], :after => [], :in => self 181: } 182: 183: define_hook_stack_execution_methods(target_method, scope) 184: define_advised_method(target_method, scope) 185: end 186: end
Register aninstance method as hookable. Registering a method means that before hooks will be run immediately before the method is invoked and after hooks will be called immediately after the method is invoked.
@param hookable_method
be hookable
@api public
# File lib/dm-core/support/hook.rb, line 127 127: def register_instance_hooks(*hooks) 128: hooks.each { |hook| register_hook(hook, :instance) } 129: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.