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 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 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.
-
106 107 108 |
# File 'lib/dm-core/support/hook.rb', line 106 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.
-
74 75 76 |
# File 'lib/dm-core/support/hook.rb', line 74 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 —
379 380 381 382 383 384 385 386 387 388 389 |
# File 'lib/dm-core/support/hook.rb', line 379 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.
-
90 91 92 |
# File 'lib/dm-core/support/hook.rb', line 90 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.
-
59 60 61 |
# File 'lib/dm-core/support/hook.rb', line 59 def before_class_method(target_method, method_sym = nil, &block) install_hook :before, target_method, method_sym, :class, &block end |
#class_hooks ⇒ Object
151 152 153 |
# File 'lib/dm-core/support/hook.rb', line 151 def class_hooks self.const_get("CLASS_HOOKS") end |
#define_advised_method(target_method, scope) ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/dm-core/support/hook.rb', line 278 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).any? { |m| m.to_sym == target_method } 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).
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 255 256 257 258 259 260 261 262 |
# File 'lib/dm-core/support/hook.rb', line 229 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") 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.
199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/dm-core/support/hook.rb', line 199 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
143 144 145 146 147 148 149 |
# File 'lib/dm-core/support/hook.rb', line 143 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.
266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/dm-core/support/hook.rb', line 266 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 —
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 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/dm-core/support/hook.rb', line 310 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 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. if !(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 while !(hooks[target_method][:in] <=> self) do hooks[target_method][:in] = hooks[target_method][:in].superclass end 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
155 156 157 |
# File 'lib/dm-core/support/hook.rb', line 155 def instance_hooks self.const_get("INSTANCE_HOOKS") end |
#method_with_scope(name, scope) ⇒ Object
391 392 393 394 395 396 397 |
# File 'lib/dm-core/support/hook.rb', line 391 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
213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/dm-core/support/hook.rb', line 213 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
399 400 401 |
# File 'lib/dm-core/support/hook.rb', line 399 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.
-
118 119 120 |
# File 'lib/dm-core/support/hook.rb', line 118 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.
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/dm-core/support/hook.rb', line 168 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.
-
130 131 132 |
# File 'lib/dm-core/support/hook.rb', line 130 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.
192 193 194 |
# File 'lib/dm-core/support/hook.rb', line 192 def registered_as_hook?(target_method, scope) ! hooks_with_scope(scope)[target_method].nil? end |
#reset_hook!(target_method, scope) ⇒ Object
Not yet implemented
135 136 137 |
# File 'lib/dm-core/support/hook.rb', line 135 def reset_hook!(target_method, scope) raise NotImplementedError end |