Module: Interfaces::Castable

Included in:
Object
Defined in:
lib/interfaces/castable.rb

Overview

a mixin that defines the ‘as’ method to allow an object to be cast as an interface

Constant Summary collapse

BUILT_IN_CONVERSIONS =
{
  Array => :to_a,
  String => :to_s,
  Symbol => :to_sym,
  Integer => :to_i,
  Float => :to_f,
  Hash => :to_h # only on ruby 2.0
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.convert_type(instance, type) ⇒ Object

attempts to convert non-interface types



14
15
16
17
18
19
20
21
# File 'lib/interfaces/castable.rb', line 14

def self.convert_type(instance, type)
  method = BUILT_IN_CONVERSIONS[type]
  if method && instance.respond_to?(method)
    instance.send(method)
  else
    raise NonConvertableObjectError, "Don't know how to convert #{instance} to #{type}"
  end
end

Instance Method Details

#as(interface) ⇒ Object

casts an object to an instance of a particular Interface

Raises:



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
# File 'lib/interfaces/castable.rb', line 31

def as(interface)
  # interface must be a class
  raise InterfaceError, "#{interface} is not a class" unless interface.is_a?(Class)

  # check if object already is an instance of interface
  return self if self.kind_of?(interface)

  # check if interface is really an interface
  if interface < Interface
    # cache the resulting interface so that we can load it faster next
    # time and so that it can save state
    cache = self.instance_variable_get(:@interface_cache)
    unless cache
      cache = {}
      self.instance_variable_set(:@interface_cache, cache)
    end

    cache[interface] ||= begin
      i = interface.new
      delegate = self
      non_implemented_methods = []

      # define singleton methods that delegate back to this object for each abstract method
      interface.abstract_methods.each do |method|
        non_implemented_methods << method unless self.respond_to?(method)
        i.define_singleton_method(method) do |*args|
          delegate.send(method, *args)
        end
      end

      # raise an exception if all abstract methods are not overridden
      unless non_implemented_methods.empty?
        raise NonConformingObjectError, "#{self} does not conform to interface #{interface}.  Expected methods not implemented: #{non_implemented_methods.join(", ")}"
      end

      # define singleton methods that delegate back to this object for each optional method
      interface.optional_methods.each do |method|
        if self.respond_to?(method)
          i.define_singleton_method(method) do |*args|
            delegate.send(method, *args)
          end
        end
      end

      i
    end
  else
    # interface is not really an interface, it's just a Class
    # use some built-in conversions
    Castable.convert_type(self, interface)
  end
end

#conforms_to?(interface) ⇒ Boolean

returns true if an object conforms to the given interface (implements the abstract methods)

Returns:

  • (Boolean)

Raises:



24
25
26
27
28
# File 'lib/interfaces/castable.rb', line 24

def conforms_to?(interface)
  # interface must be an Interface
  raise InterfaceError, "#{interface} is not an Interface" unless interface < Interface
  interface.abstract_methods.all? { |method| self.respond_to?(method) }
end