Included Modules

Extlib::Hook::ClassMethods

Public Instance Methods

after(target_method, method_sym = nil, &block) click to toggle source

Inject code that executes after the target instance method.

@param target_method the name of the instance method to inject after @param method_sym the name of the method to run after the

  target_method

@param block the code to run after the target_method

@note

  Either method_sym or block is required.
  • @api public

     # File lib/extlib/hook.rb, line 110
110:       def after(target_method, method_sym = nil, &block)
111:         install_hook :after, target_method, method_sym, :instance, &block
112:       end
after_class_method(target_method, method_sym = nil, &block) click to toggle source

Inject code that executes after the target class method.

@param target_method the name of the class method to inject after @param method_sym the name of the method to run after the target_method @param block the code to run after the target_method

@note

  Either method_sym or block is required.
  • @api public

    # File lib/extlib/hook.rb, line 78
78:       def after_class_method(target_method, method_sym = nil, &block)
79:         install_hook :after, target_method, method_sym, :class, &block
80:       end
args_for(method) click to toggle source

— Helpers —

     # File lib/extlib/hook.rb, line 384
384:       def args_for(method)
385:         if method.arity == 0
386:           "&block"
387:         elsif method.arity > 0
388:           "_" << (1 .. method.arity).to_a.join(", _") << ", &block"
389:         elsif (method.arity + 1) < 0
390:           "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block"
391:         else
392:           "*args, &block"
393:         end
394:       end
before(target_method, method_sym = nil, &block) click to toggle source

Inject code that executes before the target instance method.

@param target_method the name of the instance method to inject before @param method_sym the name of the method to run before the

  target_method

@param block the code to run before the target_method

@note

  Either method_sym or block is required.
  • @api public

    # File lib/extlib/hook.rb, line 94
94:       def before(target_method, method_sym = nil, &block)
95:         install_hook :before, target_method, method_sym, :instance, &block
96:       end
before_class_method(target_method, method_sym = nil, &block) click to toggle source

Inject code that executes before the target class method.

@param target_method the name of the class method to inject before @param method_sym the name of the method to run before the

  target_method

@param block the code to run before the target_method

@note

  Either method_sym or block is required.
  • @api public

    # File lib/extlib/hook.rb, line 63
63:       def before_class_method(target_method, method_sym = nil, &block)
64:         install_hook :before, target_method, method_sym, :class, &block
65:       end
class_hooks() click to toggle source
     # File lib/extlib/hook.rb, line 155
155:       def class_hooks
156:         self.const_get("CLASS_HOOKS")
157:       end
define_advised_method(target_method, scope) click to toggle source
     # File lib/extlib/hook.rb, line 283
283:       def define_advised_method(target_method, scope)
284:         args = args_for(method_with_scope(target_method, scope))
285: 
286:         renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised')
287: 
288:         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
289: 
290:         if scope == :instance && !instance_methods(false).any? { |m| m.to_sym == target_method }
291:           send(:alias_method, renamed_target, target_method)
292: 
293:           proxy_module = Module.new
294:           proxy_module.class_eval(source, __FILE__, __LINE__)
295:           self.send(:include, proxy_module)
296:         else
297:           source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}}
298:           source = %{class << self\n#{source}\nend} if scope == :class
299:           class_eval(source, __FILE__, __LINE__)
300:         end
301:       end
define_hook_stack_execution_methods(target_method, scope) click to toggle source

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/extlib/hook.rb, line 233
233:       def define_hook_stack_execution_methods(target_method, scope)
234:         unless registered_as_hook?(target_method, scope)
235:           raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method"
236:         end
237: 
238:         hooks = hooks_with_scope(scope)
239: 
240:         before_hooks = hooks[target_method][:before]
241:         before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n")
242: 
243:         after_hooks  = hooks[target_method][:after]
244:         after_hooks  = after_hooks.map{ |info| inline_call(info, scope) }.join("\n")
245: 
246:         before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
247:         after_hook_name  = hook_method_name(target_method, 'execute_after',  'hook_stack')
248: 
249:         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
250:       end
hook_method_name(target_method, prefix, suffix) click to toggle source

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/extlib/hook.rb, line 203
203:       def hook_method_name(target_method, prefix, suffix)
204:         target_method = target_method.to_s
205: 
206:         case target_method[1,1]
207:           when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
208:           when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
209:           when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
210:           # I add a _nan_ suffix here so that we don't ever encounter
211:           # any naming conflicts.
212:           else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}"
213:         end
214:       end
hooks_with_scope(scope) click to toggle source

Returns the correct HOOKS Hash depending on whether we are working with class methods or instance methods

     # File lib/extlib/hook.rb, line 147
147:       def hooks_with_scope(scope)
148:         case scope
149:           when :class    then class_hooks
150:           when :instance then instance_hooks
151:           else raise ArgumentError, 'You need to pass :class or :instance as scope'
152:         end
153:       end
inline_call(method_info, scope) click to toggle source

Returns ruby code that will invoke the hook. It checks the arity of the hook method and passes arguments accordingly.

     # File lib/extlib/hook.rb, line 270
270:       def inline_call(method_info, scope)
271:         Extlib::Hook::ClassMethods.hook_scopes << method_info[:from]
272:         name = method_info[:name]
273: 
274:         if scope == :instance
275:           args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : ''
276:           %(#{name}(#{args}) if self.class <= Extlib::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
277:         else
278:           args = respond_to?(name) && method(name).arity != 0 ? '*args' : ''
279:           %(#{name}(#{args}) if self <= Extlib::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
280:         end
281:       end
install_hook(type, target_method, method_sym, scope, &block) click to toggle source

— Add a hook —

     # File lib/extlib/hook.rb, line 315
315:       def install_hook(type, target_method, method_sym, scope, &block)
316:         assert_kind_of 'target_method', target_method, Symbol
317:         assert_kind_of 'method_sym',    method_sym,    Symbol unless method_sym.nil?
318:         assert_kind_of 'scope',         scope,         Symbol
319: 
320:         if !block_given? and method_sym.nil?
321:           raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"."
322:         end
323: 
324:         if method_sym.to_s[1,1] == '='
325:           raise ArgumentError, "Methods ending in = cannot be hooks"
326:         end
327: 
328:         unless [ :class, :instance ].include?(scope)
329:           raise ArgumentError, 'You need to pass :class or :instance as scope'
330:         end
331: 
332:         if registered_as_hook?(target_method, scope)
333:           hooks = hooks_with_scope(scope)
334: 
335:           #if this hook is previously declared in a sibling or cousin we must move the :in class
336:           #to the common ancestor to get both hooks to run.
337:           if !(hooks[target_method][:in] <=> self)
338:             before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
339:             after_hook_name  = hook_method_name(target_method, 'execute_after',  'hook_stack')
340: 
341:             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
342: 
343:             while !(hooks[target_method][:in] <=> self) do
344:               hooks[target_method][:in] = hooks[target_method][:in].superclass
345:             end
346: 
347:             define_hook_stack_execution_methods(target_method, scope)
348:             hooks[target_method][:in].class_eval{define_advised_method(target_method, scope)}
349:           end
350:         else
351:           register_hook(target_method, scope)
352:           hooks = hooks_with_scope(scope)
353:         end
354: 
355:         #if  we were passed a block, create a method out of it.
356:         if block
357:           method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym
358:           if scope == :class
359:             meta_class.instance_eval do
360:               define_method(method_sym, &block)
361:             end
362:           else
363:             define_method(method_sym, &block)
364:           end
365:         end
366: 
367:         # Adds method to the stack an redefines the hook invocation method
368:         hooks[target_method][type] << { :name => method_sym, :from => self }
369:         define_hook_stack_execution_methods(target_method, scope)
370:       end
instance_hooks() click to toggle source
     # File lib/extlib/hook.rb, line 159
159:       def instance_hooks
160:         self.const_get("INSTANCE_HOOKS")
161:       end
method_with_scope(name, scope) click to toggle source
     # File lib/extlib/hook.rb, line 396
396:       def method_with_scope(name, scope)
397:         case scope
398:           when :class    then method(name)
399:           when :instance then instance_method(name)
400:           else raise ArgumentError, 'You need to pass :class or :instance as scope'
401:         end
402:       end
process_method_added(method_name, scope) click to toggle source

This will need to be refactored

     # File lib/extlib/hook.rb, line 217
217:       def process_method_added(method_name, scope)
218:         hooks_with_scope(scope).each do |target_method, hooks|
219:           if hooks[:before].any? { |hook| hook[:name] == method_name }
220:             define_hook_stack_execution_methods(target_method, scope)
221:           end
222: 
223:           if hooks[:after].any? { |hook| hook[:name] == method_name }
224:             define_hook_stack_execution_methods(target_method, scope)
225:           end
226:         end
227:       end
quote_method(name) click to toggle source
     # File lib/extlib/hook.rb, line 404
404:       def quote_method(name)
405:         name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
406:       end
register_class_hooks(*hooks) click to toggle source

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 The name of the class method that should

  be hookable
  • @api public

     # File lib/extlib/hook.rb, line 122
122:       def register_class_hooks(*hooks)
123:         hooks.each { |hook| register_hook(hook, :class) }
124:       end
register_hook(target_method, scope) click to toggle source

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/extlib/hook.rb, line 172
172:       def register_hook(target_method, scope)
173:         if scope == :instance && !method_defined?(target_method)
174:           raise ArgumentError, "#{target_method} instance method does not exist"
175:         elsif scope == :class && !respond_to?(target_method)
176:           raise ArgumentError, "#{target_method} class method does not exist"
177:         end
178: 
179:         hooks = hooks_with_scope(scope)
180: 
181:         if hooks[target_method].nil?
182:           hooks[target_method] = {
183:             # We need to keep track of which class in the Inheritance chain the
184:             # method was declared hookable in. Every time a child declares a new
185:             # hook for the method, the hook stack invocations need to be redefined
186:             # in the original Class. See #define_hook_stack_execution_methods
187:             :before => [], :after => [], :in => self
188:           }
189: 
190:           define_hook_stack_execution_methods(target_method, scope)
191:           define_advised_method(target_method, scope)
192:         end
193:       end
register_instance_hooks(*hooks) click to toggle source

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 The name of the instance method that should

  be hookable
  • @api public

     # File lib/extlib/hook.rb, line 134
134:       def register_instance_hooks(*hooks)
135:         hooks.each { |hook| register_hook(hook, :instance) }
136:       end
registered_as_hook?(target_method, scope) click to toggle source

Is the method registered as a hookable in the given scope.

     # File lib/extlib/hook.rb, line 196
196:       def registered_as_hook?(target_method, scope)
197:         ! hooks_with_scope(scope)[target_method].nil?
198:       end
reset_hook!(target_method, scope) click to toggle source

Not yet implemented

     # File lib/extlib/hook.rb, line 139
139:       def reset_hook!(target_method, scope)
140:         raise NotImplementedError
141:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.