Class: CheapAdvice::Advised

Inherits:
Object
  • Object
show all
Includes:
Options
Defined in:
lib/cheap_advice.rb

Overview

Represents the application/binding of advice to a class and method.

Constant Summary collapse

INSTANCE_SEP =
'#'.freeze
MODULE_SEP =
'.'.freeze
@@mutex =
Mutex.new

Instance Attribute Summary collapse

Attributes included from Options

#options

Instance Method Summary collapse

Methods included from Options

#[], #[]=

Constructor Details

#initialize(*args) ⇒ Advised

Returns a new instance of Advised.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/cheap_advice.rb', line 215

def initialize *args
  @mutex = Mutex.new
  @advice, @mod, @meth, @kind, @options = *args

  case @kind
  when :instance, :class, :module
  else
    raise ArgumentError, "invalid kind #{kind.inspect}"
  end

  @options ||= { }

  @@mutex.synchronize do
    @advised_id = @@advised_id += 1
  end

  @old_meth = :"__advice_old_#{@@advised_id}_#{@meth}"
  @new_meth = :"__advice_new_#{@@advised_id}_#{@meth}"

  @before_meth = :"__advice_before_#{@@advised_id}_#{@meth}"
  @after_meth  = :"__advice_after_#{@@advised_id}_#{@meth}"
  @around_meth = :"__advice_around_#{@@advised_id}_#{@meth}"

  @enabled = 
    @advice_methods_applied = false
end

Instance Attribute Details

#adviceObject (readonly)

The Advice being applied to the Module and method.



198
199
200
# File 'lib/cheap_advice.rb', line 198

def advice
  @advice
end

#advised_idObject (readonly)

The unique Advised id used to generate unique method names.



204
205
206
# File 'lib/cheap_advice.rb', line 204

def advised_id
  @advised_id
end

#after_methObject (readonly)

The name of the before, after and around methods.



210
211
212
# File 'lib/cheap_advice.rb', line 210

def after_meth
  @after_meth
end

#around_methObject (readonly)

The name of the before, after and around methods.



210
211
212
# File 'lib/cheap_advice.rb', line 210

def around_meth
  @around_meth
end

#before_methObject (readonly)

The name of the before, after and around methods.



210
211
212
# File 'lib/cheap_advice.rb', line 210

def before_meth
  @before_meth
end

#enabledObject (readonly)

True if the Advised methods are currently installed.



213
214
215
# File 'lib/cheap_advice.rb', line 213

def enabled
  @enabled
end

#kindObject (readonly)

The Module, method and kind (instance, class or module method)



200
201
202
# File 'lib/cheap_advice.rb', line 200

def kind
  @kind
end

#methObject (readonly)

The Module, method and kind (instance, class or module method)



200
201
202
# File 'lib/cheap_advice.rb', line 200

def meth
  @meth
end

#modObject (readonly) Also known as: cls

The Module, method and kind (instance, class or module method)



200
201
202
# File 'lib/cheap_advice.rb', line 200

def mod
  @mod
end

#new_methObject (readonly)

The name of the old and new method being patched in.



207
208
209
# File 'lib/cheap_advice.rb', line 207

def new_meth
  @new_meth
end

#old_methObject (readonly)

The name of the old and new method being patched in.



207
208
209
# File 'lib/cheap_advice.rb', line 207

def old_meth
  @old_meth
end

Instance Method Details

#==(x) ⇒ Object

True if the advice, mod, method and kind are equal.



259
260
261
262
# File 'lib/cheap_advice.rb', line 259

def == x
  return false unless self.class === x
  @advice == x.advice && @mod == x.mod && @meth == x.meth && @kind == x.kind
end

#define_new_method!Object

Defines the new advised method in the target Module.



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
# File 'lib/cheap_advice.rb', line 315

def define_new_method!
  advised = self

  advised.mod_target.instance_eval do
    define_method advised.new_meth do | *args, &block |
      ar = ActivationRecord.new(advised, self, args, block)

      # Proc to invoke the old method with :before and :after advise hooks.
      body = Proc.new do
        self.__send__(advised.before_meth, ar)
        begin
          ar.result = self.__send__(advised.old_meth, *ar.args, &ar.block)
        rescue ::Object => err
          ar.error = err
        ensure
          self.__send__(advised.after_meth, ar)
        end
        ar.result
      end

      # Invoke the :around advice with the body Proc.
      self.__send__(advised.around_meth, ar, body)

      # Reraise Exception, if occured.
      raise ar.error if ar.error

      # Return the message result to caller.
      ar.result
    end # define_method
  end # instance_eval

  self
end

#disable!Object Also known as: unadvise!

Disables the advice on this method.



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/cheap_advice.rb', line 417

def disable!
  @mutex.synchronize do
    return self if ! @enabled

    this = self
    mod_target.instance_eval do
      if method_defined? this.old_meth
        alias_method this.meth, this.old_meth

        case this.scope
        when :private
          private this.meth
        when :protected
          protected this.meth
        end
      end
    end

    disabled!
    @enabled = false
  end

  self
end

#disabled!Object

Called when Advised is enabled. Instances can override this method.



451
452
453
# File 'lib/cheap_advice.rb', line 451

def disabled!
  self
end

#enable!Object Also known as: advise!

Enables the advice on this method.



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/cheap_advice.rb', line 384

def enable!
  @mutex.synchronize do
    return self if @enabled

    this = self
    mod_target.instance_eval do
      case this.scope
      when :public
      else
        public this.meth
      end

      alias_method this.old_meth, this.meth if
        ! method_defined? this.old_meth

      alias_method this.meth, this.new_meth

      case this.scope
      when :private
        private this.meth
      when :protected
        protected this.meth
      end
    end

    enabled!
    @enabled = true
  end
  self
end

#enabled!Object

Called when Advised is enabled. Instances can override this method.



445
446
447
# File 'lib/cheap_advice.rb', line 445

def enabled!
  self
end

#hashObject

Support for Hash.



265
266
267
# File 'lib/cheap_advice.rb', line 265

def hash
  @advice.hash ^ @mod.hash ^ @meth.hash ^ @kind.hash
end

#meth_to_sObject

The string name for the method. Returns “Foo#bar” for an instance method named :bar on class Foo. Returns “Foo.bar” for a class or module method named .bar on class Foo.



253
254
255
256
# File 'lib/cheap_advice.rb', line 253

def meth_to_s
  @meth_to_s ||=
    "#{@mod}#{@kind == :instance ? INSTANCE_SEP : MODULE_SEP}#{@meth}".freeze
end

#mod_resolveObject

Resolves mod Strings to the actual target Module.



282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/cheap_advice.rb', line 282

def mod_resolve
  case @mod
  when Module
    @mod
  when String, Symbol
    @mod.to_s.split('::').
      reject { | name | name.empty?}.
      inject(Object) { | namespace, name | namespace.const_get(name) }
  else
    raise TypeError, "mod_resolve: expected Module, String, Symbol, given #{@mod.class}"
  end
end

#mod_targetObject

Returns the target Module for the kind of method.



270
271
272
273
274
275
276
277
278
279
# File 'lib/cheap_advice.rb', line 270

def mod_target
  case @kind
  when :instance
    mod_resolve
  when :class, :module
    (class << mod_resolve; self; end)
  else
    raise ArgumentError, "mod_target: invalid kind #{kind.inspect}"
  end
end

#register_advice_methods!Object

Registers the before, after and around advice methods.



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/cheap_advice.rb', line 296

def register_advice_methods!
  scope # force calculation of scope before aliasing methods.

  @mutex.synchronize do
    return self if @advice_methods_registered

    this = self
    mod_target.instance_eval do
      define_method(this.before_meth, &this.advice.before)
      define_method(this.after_meth,  &this.advice.after)
      define_method(this.around_meth, &this.advice.around)
    end

    @advice_methods_registered = true
  end
  self
end

#scopeObject



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/cheap_advice.rb', line 350

def scope
  @scope ||= @mutex.synchronize do
    this = self
    mod_target.instance_eval do
     case
      when private_instance_methods(false).include?(this.meth.to_s)
        :private
      when protected_instance_methods(false).include?(this.meth.to_s)
        :protected
      else
        :public
      end
    end
  end
end

#set_options!(options) ⇒ Object



242
243
244
245
# File 'lib/cheap_advice.rb', line 242

def set_options! options
  @options = options || { }
  self
end