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

default_namespace[R]

@return (String) the default namespace to search for transmogrifiers

Public Class Methods

new(default_namespace = ::Octiron::Transmogrifiers) click to toggle source

@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

clear() click to toggle source

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(from, to) click to toggle source

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
Also aliased as: unregister
register(from, to, overwrite = false, transmogrifier_object = nil, &transmogrifier_proc) click to toggle source

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(from, to, verify_results = true) click to toggle source

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
unregister(from, to)
Alias for: deregister

Private Instance Methods

best_matching_hash_prototype(value) click to toggle source
# 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