module Metasploit::Model::Association::Tree

Functions for turning a compact tree of compact as passed to {Metasploit::Model::Search::Association::ClassMethods#search_associations} into an expanded {Metasploit::Model::Search::Association::ClassMethods#search_association_tree}.

Public Class Methods

expand(compact) click to toggle source

Expands a `compact` association into an expanded association tree.

@param compact [Array, Hash{Symbol => Array,Hash,Symbol}, Symbol] a compact association as passed to

{Metasploit::Model::Search::Association::ClassMethods#search_associations}.

@return [Hash{Symbol => Hash,nil}]

# File lib/metasploit/model/association/tree.rb, line 10
def self.expand(compact)
  case compact
    when Array
      compact.reduce({}) { |hash, association|
        hash.merge(expand(association))
      }
    when Hash
      child_by_parent = compact

      child_by_parent.each_with_object({}) { |(parent, child), hash|
        hash[parent] = expand(child)
      }
    when Symbol
      association = compact

      {association => nil}
  end
end
merge(first_expanded, second_expanded) click to toggle source

@note Unlike `Hash#deep_merge`, `second_expanded`'s values aren't favored over `first`'s values. Instead whichever

side is present is used and if both `first` and `second_expanded` are present, then their `Hash#key`s' values are
recursively merged.

Merges two expanded association trees.

@param first_expanded [nil, Hash{Symbol => nil,Hash}] An expanded association tree as from {expand} @param second_expanded [nil, Hash{Symbol => nil,Hash}] An expanded association tree as from {expand} @return [nil, Hash{Symbol => nil,Hash}] a new expanded association tree.

# File lib/metasploit/model/association/tree.rb, line 38
def self.merge(first_expanded, second_expanded)
  if first_expanded.nil? && second_expanded.nil?
    nil
  elsif !first_expanded.nil? && second_expanded.nil?
    first_expanded
  elsif first_expanded.nil? && !second_expanded.nil?
    second_expanded
  else
    first_keys = first_expanded.keys
    key_set = Set.new(first_keys)

    second_keys = second_expanded.keys
    key_set.merge(second_keys)

    key_set.each_with_object({}) do |key, merged|
      first_child = first_expanded[key]
      second_child = second_expanded[key]

      merged[key] = merge(first_child, second_child)
    end
  end
end
operators(expanded, options={}) click to toggle source

Calculates association operators for the `expanded` association tree.

@param expanded [Hash{Symbol => Hash,nil}, nil] An expanded association tree. @param options [Hash{Symbol => Class}] @option options [Class, reflect_on_association] :class The `Class` on which the top-level key associations in

`expanded` are declared.

@return [Array<Metasploit::Model::Search::Operator::Association>]

# File lib/metasploit/model/association/tree.rb, line 68
def self.operators(expanded, options={})
  expanded ||= {}

  options.assert_valid_keys(:class)
  klass = options.fetch(:class)

  expanded.flat_map { |parent_association, child_tree|
    reflection = reflect_on_association_on_class(parent_association, klass)
    association_class = reflection.klass

    association_search_with_operators = association_class.search_with_operator_by_name.each_value

    child_tree_operators = operators(
        child_tree,
        class: reflection.klass
    )

    [association_search_with_operators, child_tree_operators].flat_map { |enumerator|
      enumerator.map { |source_operator|
        Metasploit::Model::Search::Operator::Association.new(
            association: parent_association,
            klass: klass,
            source_operator: source_operator
        )
      }
    }
  }
end

Private Class Methods

reflect_on_association_on_class(association, klass) click to toggle source

Return the association reflection for `association` on `klass`.

@param association [Symbol] name of an association on `klass`. @param klass [#reflect_on_association] `Class` on which `association` is declared. @return [#klass] Association reflection that can give the `#klass` pointed to by the association. @raise [Metasploit::Model::Association::Error] if `association` is not declared on `klass`. @raise [NameError] if `klass` does not respond to `reflect_on_association`.

# File lib/metasploit/model/association/tree.rb, line 106
def self.reflect_on_association_on_class(association, klass)
  begin
    reflection = klass.reflect_on_association(association)
  rescue NameError
    raise NameError,
          "#{self} does not respond to reflect_on_association.  "                        "It can be added to ActiveModels by including Metasploit::Model::Association into the class."
  end

  unless reflection
    raise Metasploit::Model::Association::Error.new(
              model: klass,
              name: association
          )
  end

  reflection
end