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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/detach.rb', line 31

def method_missing(name, *args, &block)
	obj_method_missing = -> {
		BasicObject.instance_method(:method_missing).bind(self).call(name, *args, &block)
	}

	# some early returns are necessary when Detach has been mixed into
	# the class of object 'main'
	case name
	when :to_ary
		return obj_method_missing.call
	when :taking
		return Types.instance_method(:taking).bind(self.class).call if self.class == Object
	end

	(score,best) = (public_methods+protected_methods+private_methods).grep(/^#{Regexp.escape(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)
		nreq = ctypes.size

		# 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
				if s
					if t[0].ancestors.include?(t[1])
						s += t[1].ancestors.size
					else
						s = nil
					end
				end

			} || 0
		else
			score = 0
		end

		score += (1 + nreq) if args.size == params.size

		[ score, candidate ]

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

	(not score or score == 0) ? obj_method_missing.call : 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