Class: Octiron::Transmogrifiers::Registry

Inherits:
Object
  • Object
show all
Includes:
Support::Identifiers
Defined in:
lib/octiron/transmogrifiers/registry.rb

Overview

Registers transmogrifiers between one (event) class and another.

A transmogrifier is an object with a call method or a block that accepts an instance of one (event) class and produces an instance of another (event) class.

The registry also exposes a #transmogrify function which uses any registered transmogrifier, or raises an error if there is no transmogrification possible.

One piece of magic makes this particularly powerful: the registry creates a graph of how to transmogrify an object to another by chaining transmogrifiers. That is, if there is a transmogrifier that turns A into B, and one that turns B into C, then by chaining both the registry can also turn A into C directly. (This is done via the ‘rgl’ gem).

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Support::Identifiers

#identify

Methods included from Support::Constantize

#constantize

Methods included from Support::CamelCase

#camel_case

Constructor Details

#initialize(default_namespace = ::Octiron::Transmogrifiers) ⇒ Registry

Returns a new instance of Registry.

Parameters:

  • default_namespace (Symbol) (defaults to: ::Octiron::Transmogrifiers)

    The default namespace to look in for Transmogrifier classes.



43
44
45
46
# File 'lib/octiron/transmogrifiers/registry.rb', line 43

def initialize(default_namespace = ::Octiron::Transmogrifiers)
  @default_namespace = default_namespace.to_s
  clear
end

Instance Attribute Details

#default_namespaceString (readonly)

Returns the default namespace to search for transmogrifiers.

Returns:

  • (String)

    the default namespace to search for transmogrifiers



38
39
40
# File 'lib/octiron/transmogrifiers/registry.rb', line 38

def default_namespace
  @default_namespace
end

Instance Method Details

#clearObject

Clears the registry of all transmogrifiers



50
51
52
53
54
55
56
# File 'lib/octiron/transmogrifiers/registry.rb', line 50

def clear
  @graph = RGL::DirectedAdjacencyGraph.new
  @visitor = RGL::DijkstraVisitor.new(@graph)
  @map_data = {}
  @map = nil
  @transmogrifiers = {}
end

#deregister(from, to) ⇒ Object Also known as: unregister

Deregister transmogrifier

Parameters:

  • from (Class, String, other)

    A class or String nameing a transmogrifier source. With prototype Hash matching, this would be a prototype.

  • to (Class, String, other)

    Transmogrifier target.



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/octiron/transmogrifiers/registry.rb', line 113

def deregister(from, to)
  # Convert to canonical names
  from_name = identify(from)
  to_name = identify(to)
  key = [from_name, to_name]

  # Graph, map data and transmogrifiers need to be modified
  @graph.remove_edge(from_name, to_name)
  @map_data.delete(key)
  @map = RGL::EdgePropertiesMap.new(@map_data, true)

  @transmogrifiers.delete(key)
end

#register(from, to, overwrite = false, transmogrifier_object = nil, &transmogrifier_proc) ⇒ Object

Register transmogrifier

Parameters:

  • from (Class, String, other)

    A class or String nameing a transmogrifier source. With prototype Hash matching, this would be a prototype.

  • to (Class, String, other)

    Transmogrifier target.

  • overwrite (Boolean) (defaults to: false)

    The registry can only hold one transmogrifier per from -> to pair. If overwrite is true, registering a transmogrifier where one already exists overwrites the old one, otherwise an exception is raised.

  • transmogrifier_object (Object) (defaults to: nil)

    Transmogrifier object that must implement a ‘#call` method accepting an instance of the event class provided in the first parameter. If nil, a block needs to be provided.

  • transmogrifier_proc (Proc)

    Transmogrifier block that accepts an instance of the event class provided in the first parameter. If nil, a transmogrifier object must be provided.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/octiron/transmogrifiers/registry.rb', line 75

def register(from, to, overwrite = false, transmogrifier_object = nil,
             &transmogrifier_proc)
  transmogrifier = transmogrifier_proc || transmogrifier_object
  if not transmogrifier
    raise ArgumentError, "Please pass either an object or a transmogrifier "\
        "block"
  end

  # Convert to canonical names
  from_name = identify(from)
  to_name = identify(to)
  key = [from_name, to_name]

  # We treat the graph as authoritative for what transmogrifiers exist.
  if @graph.has_edge?(from_name, to_name)
    if not overwrite
      raise ArgumentError, "Registry already knows a transmogrifier for "\
          "#{key}, aborting!"
    end
  end

  # Add edges and map data for the shortest path search. We treat all paths
  # as equally weighted.
  @graph.add_edge(from_name, to_name)
  @map_data[key] = 1
  @map = RGL::EdgePropertiesMap.new(@map_data, true)

  # Finally, register transmogrifier
  @transmogrifiers[key] = transmogrifier
end

#transmogrify(from, to, verify_results = true) ⇒ Object

Transmogrify an object of one class into another class. If ‘verify_results` is true, the transmogrification result is checked to match the target class or hash, and an error is raised if there is no match.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/octiron/transmogrifiers/registry.rb', line 134

def transmogrify(from, to, verify_results = true)
  # Get lookup keys
  from_name = from.class.to_s
  if from.is_a?(Hash)
    # Finding the correct from_name is tricky, because from is not a
    # prototype, but the graph and all intermediate
    from_name = best_matching_hash_prototype(from)
  end
  to_name = identify(to)

  # We'll ask the graph for the shortest path. If there is none, we can't
  # transmogrify. (Note: the @map changes with each registration/
  # deregistration, so we instanciate the algorithm here).
  algo = RGL::DijkstraAlgorithm.new(@graph, @map, @visitor)
  path = algo.shortest_path(from_name, to_name)

  if path.nil?
    raise ArgumentError, "No transmogrifiers for #{[from_name, to_name]} "\
        "found, aborting!"
  end

  # Transmogrify for each part of the path
  input = from
  result = nil
  path.inject do |step_from, step_to|
    # Call transmogrifier
    key = [step_from, step_to]
    result = @transmogrifiers[key].call(input)

    # Verify result
    if verify_results
      if result.nil?
        raise "Transmogrifier returned nil result!"
      end

      if step_to.is_a?(Hash)
        result.extend(::Collapsium::PrototypeMatch)
        if not result.prototype_match(step_to)
          raise "Transmogrifier returned Hash that did not match prototype "\
                "#{step_to}, aborting!"
        end
      elsif result.class.to_s != step_to
        raise "Transmogrifier returned result of invalid class "\
            "#{result.class}, aborting!"
      end
    end

    # Result is input for the next transmogrifier in the chain
    input = result

    # Make step_to the next step_from
    next step_to
  end
  return result
end