Module: DataMapper::Hook::ClassMethods
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 hook-able.
-
#register_hook(target_method, scope) ⇒ Object
Registers a method as hook-able.
-
#register_instance_hooks(*hooks) ⇒ Object
Register an instance method as hook-able.
-
#registered_as_hook?(target_method, scope) ⇒ Boolean
Is the method registered as a hook-able in the given scope.
-
#reset_hook!(_target_method, _scope) ⇒ Object
Not yet implemented.
Methods included from LocalObjectSpace
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.
-
105 106 107 |
# File 'lib/dm-core/support/hook.rb', line 105 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.
-
73 74 75 |
# File 'lib/dm-core/support/hook.rb', line 73 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 —
363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/dm-core/support/hook.rb', line 363 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.
-
89 90 91 |
# File 'lib/dm-core/support/hook.rb', line 89 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.
-
58 59 60 |
# File 'lib/dm-core/support/hook.rb', line 58 def before_class_method(target_method, method_sym = nil, &block) install_hook :before, target_method, method_sym, :class, &block end |
#class_hooks ⇒ Object
150 151 152 |
# File 'lib/dm-core/support/hook.rb', line 150 def class_hooks const_get('CLASS_HOOKS') end |
#define_advised_method(target_method, scope) ⇒ Object
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 298 |
# File 'lib/dm-core/support/hook.rb', line 270 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).none? { |m| m.to_sym == target_method } send(:alias_method, renamed_target, target_method) proxy_module = Module.new proxy_module.class_eval(source, __FILE__, __LINE__) 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).
223 224 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 254 |
# File 'lib/dm-core/support/hook.rb', line 223 def define_hook_stack_execution_methods(target_method, scope) raise ArgumentError, "#{target_method} has not be registered as a hook-able #{scope} method" unless registered_as_hook?(target_method, scope) 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") before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack') after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack') hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1 #{(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' : ''} RUBY 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.
198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/dm-core/support/hook.rb', line 198 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}_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
142 143 144 145 146 147 148 |
# File 'lib/dm-core/support/hook.rb', line 142 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.
258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/dm-core/support/hook.rb', line 258 def inline_call(method_info, scope) DataMapper::Hook::ClassMethods.hook_scopes << method_info[:from] name = method_info[:name] if scope == :instance args = (method_defined?(name) && instance_method(name).arity != 0) ? '*args' : '' %(#{name}(#{args}) if self.class <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id})) else args = (respond_to?(name) && method(name).arity != 0) ? '*args' : '' %(#{name}(#{args}) if self <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id})) end end |
#install_hook(type, target_method, method_sym, scope, &block) ⇒ Object
— Add a hook —
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 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/dm-core/support/hook.rb', line 302 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 raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"." if !block_given? && method_sym.nil? raise ArgumentError, 'Methods ending in = cannot be hooks' if method_sym.to_s[-1, 1] == '=' raise ArgumentError, 'You need to pass :class or :instance as scope' unless %i(class instance).include?(scope) if registered_as_hook?(target_method, scope) hooks = hooks_with_scope(scope) # if this hook is previously declared in a sibling or cousin we must move the :in class # to the common ancestor to get both hooks to run. unless hooks[target_method][:in] <=> self before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack') after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack') hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1 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 RUBY hooks[target_method][:in] = hooks[target_method][:in].superclass until hooks[target_method][:in] <=> self define_hook_stack_execution_methods(target_method, scope) hooks[target_method][:in].class_eval { define_advised_method(target_method, scope) } end else register_hook(target_method, scope) hooks = hooks_with_scope(scope) end # if we were passed a block, create a method out of it. if block method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym if scope == :class singleton_class.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
154 155 156 |
# File 'lib/dm-core/support/hook.rb', line 154 def instance_hooks const_get('INSTANCE_HOOKS') end |
#method_with_scope(name, scope) ⇒ Object
375 376 377 378 379 380 381 |
# File 'lib/dm-core/support/hook.rb', line 375 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
212 213 214 215 216 217 |
# File 'lib/dm-core/support/hook.rb', line 212 def process_method_added(method_name, scope) hooks_with_scope(scope).each do |target_method, hooks| define_hook_stack_execution_methods(target_method, scope) if hooks[:before].any? { |hook| hook[:name] == method_name } define_hook_stack_execution_methods(target_method, scope) if hooks[:after].any? { |hook| hook[:name] == method_name } end end |
#quote_method(name) ⇒ Object
383 384 385 |
# File 'lib/dm-core/support/hook.rb', line 383 def quote_method(name) name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_') end |
#register_class_hooks(*hooks) ⇒ Object
Register a class method as hook-able. 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.
-
117 118 119 |
# File 'lib/dm-core/support/hook.rb', line 117 def register_class_hooks(*hooks) hooks.each { |hook| register_hook(hook, :class) } end |
#register_hook(target_method, scope) ⇒ Object
Registers a method as hook-able. 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 hook-able so that the hook stacks are invoked appropriately.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/dm-core/support/hook.rb', line 167 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) return unless hooks[target_method].nil? hooks[target_method] = { # We need to keep track of which class in the Inheritance chain the # method was declared hook-able 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 |
#register_instance_hooks(*hooks) ⇒ Object
Register an instance method as hook-able. 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.
-
129 130 131 |
# File 'lib/dm-core/support/hook.rb', line 129 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 hook-able in the given scope.
191 192 193 |
# File 'lib/dm-core/support/hook.rb', line 191 def registered_as_hook?(target_method, scope) !hooks_with_scope(scope)[target_method].nil? end |
#reset_hook!(_target_method, _scope) ⇒ Object
Not yet implemented
134 135 136 |
# File 'lib/dm-core/support/hook.rb', line 134 def reset_hook!(_target_method, _scope) raise NotImplementedError end |