Module: Extlib::Hook::ClassMethods
- Includes:
- Assertions
- Defined in:
- lib/quickbooks/extlib/hook.rb
Instance Method Summary collapse
-
#after(target_method, method_sym = nil, &block) ⇒ Object
Inject code that executes after the target instance method.
-
#after_class_method(target_method, method_sym = nil, &block) ⇒ Object
Inject code that executes after the target class method.
-
#args_for(method) ⇒ Object
— Helpers —.
-
#before(target_method, method_sym = nil, &block) ⇒ Object
Inject code that executes before the target instance method.
-
#before_class_method(target_method, method_sym = nil, &block) ⇒ Object
Inject code that executes before the target class method.
- #class_hooks ⇒ Object
- #define_advised_method(target_method, scope) ⇒ Object
-
#define_hook_stack_execution_methods(target_method, scope) ⇒ Object
Defines two methods.
-
#hook_method_name(target_method, prefix, suffix) ⇒ Object
Generates names for the various utility methods.
-
#hooks_with_scope(scope) ⇒ Object
Returns the correct HOOKS Hash depending on whether we are working with class methods or instance methods.
-
#inline_call(method_info, scope) ⇒ Object
Returns ruby code that will invoke the hook.
-
#install_hook(type, target_method, method_sym, scope, &block) ⇒ Object
— Add a hook —.
- #instance_hooks ⇒ Object
- #method_with_scope(name, scope) ⇒ Object
-
#process_method_added(method_name, scope) ⇒ Object
This will need to be refactored.
- #quote_method(name) ⇒ Object
-
#register_class_hooks(*hooks) ⇒ Object
Register a class method as hookable.
-
#register_hook(target_method, scope) ⇒ Object
Registers a method as hookable.
-
#register_instance_hooks(*hooks) ⇒ Object
Register aninstance method as hookable.
-
#registered_as_hook?(target_method, scope) ⇒ Boolean
Is the method registered as a hookable in the given scope.
-
#reset_hook!(target_method, scope) ⇒ Object
Not yet implemented.
Methods included from Assertions
Instance Method Details
#after(target_method, method_sym = nil, &block) ⇒ Object
Either method_sym or block is required.
Inject code that executes after the target instance method.
-
102 103 104 |
# File 'lib/quickbooks/extlib/hook.rb', line 102 def after(target_method, method_sym = nil, &block) install_hook :after, target_method, method_sym, :instance, &block end |
#after_class_method(target_method, method_sym = nil, &block) ⇒ Object
Either method_sym or block is required.
Inject code that executes after the target class method.
-
70 71 72 |
# File 'lib/quickbooks/extlib/hook.rb', line 70 def after_class_method(target_method, method_sym = nil, &block) install_hook :after, target_method, method_sym, :class, &block end |
#args_for(method) ⇒ Object
— Helpers —
340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/quickbooks/extlib/hook.rb', line 340 def args_for(method) if method.arity == 0 "&block" elsif method.arity > 0 "_" << (1 .. method.arity).to_a.join(", _") << ", &block" elsif (method.arity + 1) < 0 "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block" else "*args, &block" end end |
#before(target_method, method_sym = nil, &block) ⇒ Object
Either method_sym or block is required.
Inject code that executes before the target instance method.
-
86 87 88 |
# File 'lib/quickbooks/extlib/hook.rb', line 86 def before(target_method, method_sym = nil, &block) install_hook :before, target_method, method_sym, :instance, &block end |
#before_class_method(target_method, method_sym = nil, &block) ⇒ Object
Either method_sym or block is required.
Inject code that executes before the target class method.
-
55 56 57 |
# File 'lib/quickbooks/extlib/hook.rb', line 55 def before_class_method(target_method, method_sym = nil, &block) install_hook :before, target_method, method_sym, :class, &block end |
#class_hooks ⇒ Object
147 148 149 |
# File 'lib/quickbooks/extlib/hook.rb', line 147 def class_hooks self.const_get("CLASS_HOOKS") end |
#define_advised_method(target_method, scope) ⇒ Object
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/quickbooks/extlib/hook.rb', line 269 def define_advised_method(target_method, scope) args = args_for(method_with_scope(target_method, scope)) renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised') source = <<-EOD 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 EOD if scope == :instance && !instance_methods(false).include?(target_method.to_s) send(:alias_method, renamed_target, target_method) proxy_module = Module.new proxy_module.class_eval(source, __FILE__, __LINE__) self.send(:include, proxy_module) else source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}} source = %{class << self\n#{source}\nend} if scope == :class class_eval(source, __FILE__, __LINE__) end end |
#define_hook_stack_execution_methods(target_method, scope) ⇒ Object
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).
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/quickbooks/extlib/hook.rb', line 225 def define_hook_stack_execution_methods(target_method, scope) unless registered_as_hook?(target_method, scope) raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method" end hooks = hooks_with_scope(scope) before_hooks = hooks[target_method][:before] before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n") after_hooks = hooks[target_method][:after] after_hooks = after_hooks.map{ |info| inline_call(info, scope) }.join("\n") source = %{ private def #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(*args) #{before_hooks} end def #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(*args) #{after_hooks} end } source = %{class << self\n#{source}\nend} if scope == :class hooks[target_method][:in].class_eval(source, __FILE__, __LINE__) end |
#hook_method_name(target_method, prefix, suffix) ⇒ Object
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.
195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/quickbooks/extlib/hook.rb', line 195 def hook_method_name(target_method, prefix, suffix) target_method = target_method.to_s case target_method[-1,1] when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}" when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}" when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}" # I add a _nan_ suffix here so that we don't ever encounter # any naming conflicts. else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}" end end |
#hooks_with_scope(scope) ⇒ Object
Returns the correct HOOKS Hash depending on whether we are working with class methods or instance methods
139 140 141 142 143 144 145 |
# File 'lib/quickbooks/extlib/hook.rb', line 139 def hooks_with_scope(scope) case scope when :class then class_hooks when :instance then instance_hooks else raise ArgumentError, 'You need to pass :class or :instance as scope' end end |
#inline_call(method_info, scope) ⇒ Object
Returns ruby code that will invoke the hook. It checks the arity of the hook method and passes arguments accordingly.
257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/quickbooks/extlib/hook.rb', line 257 def inline_call(method_info, scope) name = method_info[:name] if scope == :instance args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : '' %(#{name}(#{args}) if self.class <= ObjectSpace._id2ref(#{method_info[:from].object_id})) else args = respond_to?(name) && method(name).arity != 0 ? '*args' : '' %(#{name}(#{args}) if self <= ObjectSpace._id2ref(#{method_info[:from].object_id})) end end |
#install_hook(type, target_method, method_sym, scope, &block) ⇒ Object
— Add a hook —
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/quickbooks/extlib/hook.rb', line 301 def install_hook(type, target_method, method_sym, scope, &block) assert_kind_of 'target_method', target_method, Symbol assert_kind_of 'method_sym', method_sym, Symbol unless method_sym.nil? assert_kind_of 'scope', scope, Symbol if !block_given? and method_sym.nil? raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"." end if method_sym.to_s[-1,1] == '=' raise ArgumentError, "Methods ending in = cannot be hooks" end unless [ :class, :instance ].include?(scope) raise ArgumentError, 'You need to pass :class or :instance as scope' end register_hook(target_method, scope) unless registered_as_hook?(target_method, scope) hooks = hooks_with_scope(scope) if block method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym if scope == :class (class << self; self; end;).instance_eval do define_method(method_sym, &block) end else define_method(method_sym, &block) end end # Adds method to the stack an redefines the hook invocation method hooks[target_method][type] << { :name => method_sym, :from => self } define_hook_stack_execution_methods(target_method, scope) end |
#instance_hooks ⇒ Object
151 152 153 |
# File 'lib/quickbooks/extlib/hook.rb', line 151 def instance_hooks self.const_get("INSTANCE_HOOKS") end |
#method_with_scope(name, scope) ⇒ Object
352 353 354 355 356 357 358 |
# File 'lib/quickbooks/extlib/hook.rb', line 352 def method_with_scope(name, scope) case scope when :class then method(name) when :instance then instance_method(name) else raise ArgumentError, 'You need to pass :class or :instance as scope' end end |
#process_method_added(method_name, scope) ⇒ Object
This will need to be refactored
209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/quickbooks/extlib/hook.rb', line 209 def process_method_added(method_name, scope) hooks_with_scope(scope).each do |target_method, hooks| if hooks[:before].any? { |hook| hook[:name] == method_name } define_hook_stack_execution_methods(target_method, scope) end if hooks[:after].any? { |hook| hook[:name] == method_name } define_hook_stack_execution_methods(target_method, scope) end end end |
#quote_method(name) ⇒ Object
360 361 362 |
# File 'lib/quickbooks/extlib/hook.rb', line 360 def quote_method(name) name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_') end |
#register_class_hooks(*hooks) ⇒ Object
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.
-
114 115 116 |
# File 'lib/quickbooks/extlib/hook.rb', line 114 def register_class_hooks(*hooks) hooks.each { |hook| register_hook(hook, :class) } end |
#register_hook(target_method, scope) ⇒ Object
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.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/quickbooks/extlib/hook.rb', line 164 def register_hook(target_method, scope) if scope == :instance && !method_defined?(target_method) raise ArgumentError, "#{target_method} instance method does not exist" elsif scope == :class && !respond_to?(target_method) raise ArgumentError, "#{target_method} class method does not exist" end hooks = hooks_with_scope(scope) if hooks[target_method].nil? hooks[target_method] = { # We need to keep track of which class in the Inheritance chain the # method was declared hookable in. Every time a child declares a new # hook for the method, the hook stack invocations need to be redefined # in the original Class. See #define_hook_stack_execution_methods :before => [], :after => [], :in => self } define_hook_stack_execution_methods(target_method, scope) define_advised_method(target_method, scope) end end |
#register_instance_hooks(*hooks) ⇒ Object
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.
-
126 127 128 |
# File 'lib/quickbooks/extlib/hook.rb', line 126 def register_instance_hooks(*hooks) hooks.each { |hook| register_hook(hook, :instance) } end |
#registered_as_hook?(target_method, scope) ⇒ Boolean
Is the method registered as a hookable in the given scope.
188 189 190 |
# File 'lib/quickbooks/extlib/hook.rb', line 188 def registered_as_hook?(target_method, scope) ! hooks_with_scope(scope)[target_method].nil? end |
#reset_hook!(target_method, scope) ⇒ Object
Not yet implemented
131 132 133 |
# File 'lib/quickbooks/extlib/hook.rb', line 131 def reset_hook!(target_method, scope) raise NotImplementedError end |