Module: Detach

Defined in:
lib/detach.rb

Overview

The Detach mixin provides method dispatch according to argument types. Method definitions are separated by name and signatue, allowing for C++ or Java style overloading.

Example:

class Bar
  include Detach
  taking['String','String']
  def foo(a,b)
    a.upcase + b.upcase
  end
  taking['Integer','Integer']
  def foo(a=42,b)
    a * b
  end
  taking['Object','String']
  def foo(a,*b)
    b.map {|s| s.upcase + a.to_s}.join
  end
end

Defined Under Namespace

Modules: Types

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Provides run-time method lookup according to the types of the args.

All methods matching the name are scored according to both arity and type. Varargs and default values are interpolated with actual values. Predefined classes are compared to actual classes using equality and inheritence checks.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/detach.rb', line 31

def method_missing(name, *args, &block)
  (score,best) = (public_methods+protected_methods+private_methods).grep(/^#{name}\(/).collect {|candidate|
    # extract paramters
    params = /\((.*)\)/.match(candidate.to_s)[1].scan(/(\w+)-([\w:)]+)/).collect {|s,t|
      [s.to_sym, t.split(/::/).inject(Kernel) {|m,c| m = m.const_get(c)}]
    }
    # form the list of all required argument classes
    ctypes = params.values_at(*params.each_index.select {|i| params[i].first == :req}).map(&:last)

    # NOTE: ruby only allows a single *args, or a list of a=1, b=2--not both together--
    # only one of the following will execute

    # (A) insert any optional argument classes for as many extra are present
    params.each_index.select {|i| params[i].first == :opt}.each {|i|
      ctypes.insert(i, params[i].last) if args.size > ctypes.size
    }
    # (B) insert the remaining arguments by exploding the appropriate class by the number extra
    params.each_index.select {|i| params[i].first == :rest}.each {|i|
      ctypes.insert(i, *([params[i].last] * (args.size - ctypes.size))) if args.size > ctypes.size
    }

    # now score the given args by comparing their actual classes to the predefined classes
    if args.empty? and ctypes.empty?
      score = 1
    elsif ctypes.size == args.size
      score = args.map(&:class).zip(ctypes).inject(0) {|s,t| 
        # apply each class comparison and require nonzero matches
        s and ->(n) {s += n if n > 0}[ [ :==, :<=, ].select {|op| ->(a,&b) {b[*a]}[t, &op]}.size ]
      } || 0
    else
      score = 0
    end

    [ score, candidate ]

  }.max {|a,b| a[0] <=> b[0]}

  (not score or score == 0) ? super : method(best)[*args, &block]
end

Class Method Details

.included(base) ⇒ Object

Extends the base class with the module Detach::Types.



23
24
25
# File 'lib/detach.rb', line 23

def self.included(base)
  base.extend(Types)
end