Module Extlib::Hook::ClassMethods
In: lib/extlib/hook.rb
::String ByteArray Hash SimpleSet StandardError InvalidResourceError Logger Pool lib/extlib/logger.rb lib/extlib/simple_set.rb lib/extlib/byte_array.rb ClassMethods Hook Assertions lib/extlib/pooling.rb Pooling Inflection Extlib dot/m_31_0.png

Methods

Included Modules

Extlib::Assertions

Public Instance methods

Inject code that executes after the target instance method.

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

  target_method

@param block<Block> the code to run after the target_method

@note

  Either method_sym or block is required.

- @api public

[Source]

     # File lib/extlib/hook.rb, line 102
102:       def after(target_method, method_sym = nil, &block)
103:         install_hook :after, target_method, method_sym, :instance, &block
104:       end

Inject code that executes after the target class method.

@param target_method<Symbol> the name of the class method to inject after @param method_sym<Symbol> the name of the method to run after the target_method @param block<Block> the code to run after the target_method

@note

  Either method_sym or block is required.

- @api public

[Source]

    # File lib/extlib/hook.rb, line 70
70:       def after_class_method(target_method, method_sym = nil, &block)
71:         install_hook :after, target_method, method_sym, :class, &block
72:       end

— Helpers —

[Source]

     # File lib/extlib/hook.rb, line 378
378:       def args_for(method)
379:         if method.arity == 0
380:           "&block"
381:         elsif method.arity > 0
382:           "_" << (1 .. method.arity).to_a.join(", _") << ", &block"
383:         elsif (method.arity + 1) < 0
384:           "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block"
385:         else
386:           "*args, &block"
387:         end
388:       end

Inject code that executes before the target instance method.

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

  target_method

@param block<Block> the code to run before the target_method

@note

  Either method_sym or block is required.

- @api public

[Source]

    # File lib/extlib/hook.rb, line 86
86:       def before(target_method, method_sym = nil, &block)
87:         install_hook :before, target_method, method_sym, :instance, &block
88:       end

Inject code that executes before the target class method.

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

  target_method

@param block<Block> the code to run before the target_method

@note

  Either method_sym or block is required.

- @api public

[Source]

    # File lib/extlib/hook.rb, line 55
55:       def before_class_method(target_method, method_sym = nil, &block)
56:         install_hook :before, target_method, method_sym, :class, &block
57:       end

[Source]

     # File lib/extlib/hook.rb, line 147
147:       def class_hooks
148:         self.const_get("CLASS_HOOKS")
149:       end

[Source]

     # File lib/extlib/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})\nretval = nil\ncatch(:halt) do\n\#{hook_method_name(target_method, 'execute_before', 'hook_stack')}(\#{args})\nretval = \#{renamed_target}(\#{args})\n\#{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, \#{args})\nretval\nend\nend\n"
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).

[Source]

     # File lib/extlib/hook.rb, line 225
225:       def define_hook_stack_execution_methods(target_method, scope)
226:         unless registered_as_hook?(target_method, scope)
227:           raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method"
228:         end
229: 
230:         hooks = hooks_with_scope(scope)
231: 
232:         before_hooks = hooks[target_method][:before]
233:         before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n")
234: 
235:         after_hooks  = hooks[target_method][:after]
236:         after_hooks  = after_hooks.map{ |info| inline_call(info, scope) }.join("\n")
237: 
238:         before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
239:         after_hook_name  = hook_method_name(target_method, 'execute_after',  'hook_stack')
240: 
241:         hooks[target_method][:in].class_eval "\#{scope == :class ? 'class << self' : ''}\n\nprivate\n\nremove_method :\#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :\#{before_hook_name} }\ndef \#{before_hook_name}(*args)\n\#{before_hooks}\nend\n\nremove_method :\#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :\#{after_hook_name} }\ndef \#{after_hook_name}(*args)\n\#{after_hooks}\nend\n\n\#{scope == :class ? 'end' : ''}\n", __FILE__, __LINE__ + 1
242:       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.

[Source]

     # File lib/extlib/hook.rb, line 195
195:       def hook_method_name(target_method, prefix, suffix)
196:         target_method = target_method.to_s
197: 
198:         case target_method[-1,1]
199:           when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
200:           when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
201:           when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
202:           # I add a _nan_ suffix here so that we don't ever encounter
203:           # any naming conflicts.
204:           else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}"
205:         end
206:       end

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

[Source]

     # File lib/extlib/hook.rb, line 139
139:       def hooks_with_scope(scope)
140:         case scope
141:           when :class    then class_hooks
142:           when :instance then instance_hooks
143:           else raise ArgumentError, 'You need to pass :class or :instance as scope'
144:         end
145:       end

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

[Source]

     # File lib/extlib/hook.rb, line 263
263:       def inline_call(method_info, scope)
264:         name = method_info[:name]
265: 
266:         if scope == :instance
267:           args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : ''
268:           %(#{name}(#{args}) if self.class <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
269:         else
270:           args = respond_to?(name) && method(name).arity != 0 ? '*args' : ''
271:           %(#{name}(#{args}) if self <= ObjectSpace._id2ref(#{method_info[:from].object_id}))
272:         end
273:       end

— Add a hook —

[Source]

     # File lib/extlib/hook.rb, line 308
308:       def install_hook(type, target_method, method_sym, scope, &block)
309:         assert_kind_of 'target_method', target_method, Symbol
310:         assert_kind_of 'method_sym',    method_sym,    Symbol unless method_sym.nil?
311:         assert_kind_of 'scope',         scope,         Symbol
312: 
313:         if !block_given? and method_sym.nil?
314:           raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"."
315:         end
316: 
317:         if method_sym.to_s[-1,1] == '='
318:           raise ArgumentError, "Methods ending in = cannot be hooks"
319:         end
320: 
321:         unless [ :class, :instance ].include?(scope)
322:           raise ArgumentError, 'You need to pass :class or :instance as scope'
323:         end
324: 
325:         if registered_as_hook?(target_method, scope)
326:           hooks = hooks_with_scope(scope)
327: 
328:           #if this hook is previously declared in a sibling or cousin we must move the :in class
329:           #to the common ancestor to get both hooks to run.
330:           if !(hooks[target_method][:in] <=> self)
331:             before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
332:             after_hook_name  = hook_method_name(target_method, 'execute_after',  'hook_stack')
333: 
334:             hooks[target_method][:in].class_eval "remove_method :\#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :\#{before_hook_name} }\ndef \#{before_hook_name}(*args)\nsuper\nend\n\nremove_method :\#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :\#{before_hook_name} }\ndef \#{after_hook_name}(*args)\nsuper\nend\n", __FILE__, __LINE__ + 1
335: 
336:             while !(hooks[target_method][:in] <=> self) do
337:               hooks[target_method][:in] = hooks[target_method][:in].superclass
338:             end
339: 
340:             define_hook_stack_execution_methods(target_method, scope)
341:             hooks[target_method][:in].class_eval{define_advised_method(target_method, scope)}
342:           end
343:         else
344:           register_hook(target_method, scope)
345:           hooks = hooks_with_scope(scope)
346:         end
347: 
348:         #if  we were passed a block, create a method out of it.
349:         if block
350:           method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym
351:           if scope == :class
352:             meta_class.instance_eval do
353:               define_method(method_sym, &block)
354:             end
355:           else
356:             define_method(method_sym, &block)
357:           end
358:         end
359: 
360:         # Adds method to the stack an redefines the hook invocation method
361:         hooks[target_method][type] << { :name => method_sym, :from => self }
362:         define_hook_stack_execution_methods(target_method, scope)
363:       end

[Source]

     # File lib/extlib/hook.rb, line 151
151:       def instance_hooks
152:         self.const_get("INSTANCE_HOOKS")
153:       end

[Source]

     # File lib/extlib/hook.rb, line 390
390:       def method_with_scope(name, scope)
391:         case scope
392:           when :class    then method(name)
393:           when :instance then instance_method(name)
394:           else raise ArgumentError, 'You need to pass :class or :instance as scope'
395:         end
396:       end

This will need to be refactored

[Source]

     # File lib/extlib/hook.rb, line 209
209:       def process_method_added(method_name, scope)
210:         hooks_with_scope(scope).each do |target_method, hooks|
211:           if hooks[:before].any? { |hook| hook[:name] == method_name }
212:             define_hook_stack_execution_methods(target_method, scope)
213:           end
214: 
215:           if hooks[:after].any? { |hook| hook[:name] == method_name }
216:             define_hook_stack_execution_methods(target_method, scope)
217:           end
218:         end
219:       end

[Source]

     # File lib/extlib/hook.rb, line 398
398:       def quote_method(name)
399:         name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
400:       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<Symbol> The name of the class method that should

  be hookable

- @api public

[Source]

     # File lib/extlib/hook.rb, line 114
114:       def register_class_hooks(*hooks)
115:         hooks.each { |hook| register_hook(hook, :class) }
116:       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.

[Source]

     # File lib/extlib/hook.rb, line 164
164:       def register_hook(target_method, scope)
165:         if scope == :instance && !method_defined?(target_method)
166:           raise ArgumentError, "#{target_method} instance method does not exist"
167:         elsif scope == :class && !respond_to?(target_method)
168:           raise ArgumentError, "#{target_method} class method does not exist"
169:         end
170: 
171:         hooks = hooks_with_scope(scope)
172: 
173:         if hooks[target_method].nil?
174:           hooks[target_method] = {
175:             # We need to keep track of which class in the Inheritance chain the
176:             # method was declared hookable in. Every time a child declares a new
177:             # hook for the method, the hook stack invocations need to be redefined
178:             # in the original Class. See #define_hook_stack_execution_methods
179:             :before => [], :after => [], :in => self
180:           }
181: 
182:           define_hook_stack_execution_methods(target_method, scope)
183:           define_advised_method(target_method, scope)
184:         end
185:       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<Symbol> The name of the instance method that should

  be hookable

- @api public

[Source]

     # File lib/extlib/hook.rb, line 126
126:       def register_instance_hooks(*hooks)
127:         hooks.each { |hook| register_hook(hook, :instance) }
128:       end

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

[Source]

     # File lib/extlib/hook.rb, line 188
188:       def registered_as_hook?(target_method, scope)
189:         ! hooks_with_scope(scope)[target_method].nil?
190:       end

Not yet implemented

[Source]

     # File lib/extlib/hook.rb, line 131
131:       def reset_hook!(target_method, scope)
132:         raise NotImplementedError
133:       end

[Validate]