class Octiron::Transmogrifiers::Registry
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).
Attributes
@return (String) the default namespace to search for transmogrifiers
Public Class Methods
@param default_namespace
(Symbol) The default namespace to look in for
Transmogrifier classes.
# File lib/octiron/transmogrifiers/registry.rb, line 43 def initialize(default_namespace = ::Octiron::Transmogrifiers) @default_namespace = default_namespace.to_s clear end
Public Instance Methods
Clears the registry of all transmogrifiers
# 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 transmogrifier
@param from (Class, String, other) A class or String nameing a
transmogrifier source. With prototype Hash matching, this would be a prototype.
@param to (Class, String, other) Transmogrifier target.
# 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 transmogrifier
@param from (Class, String, other) A class or String nameing a
transmogrifier source. With prototype Hash matching, this would be a prototype.
@param to (Class, String, other) Transmogrifier target. @param overwrite (Boolean) 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.
@param transmogrifier_object (Object) 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.
@param 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.
# 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 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.
# 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
Private Instance Methods
# File lib/octiron/transmogrifiers/registry.rb, line 194 def best_matching_hash_prototype(value) value.extend(::Collapsium::PrototypeMatch) best_score = -1 best_proto = nil @transmogrifiers.each do |key, _| proto = key[0] if not proto.is_a?(Hash) next end score = value.prototype_match_score(proto) if score > best_score best_score = score best_proto = proto end end return best_proto end