class NoSE::KeyPath

A path from a primary key to a chain of foreign keys

Public Class Methods

new(keys = []) click to toggle source
# File lib/nose/statements.rb, line 112
def initialize(keys = [])
  fail InvalidKeyPathException, 'first key must be an ID' \
    unless keys.empty? || keys.first.instance_of?(Fields::IDField)

  keys_match = keys.each_cons(2).all? do |prev_key, key|
    key.parent == prev_key.entity
  end
  fail InvalidKeyPathException, 'keys must match along the path' \
    unless keys_match

  @keys = keys
end

Public Instance Methods

+(other) click to toggle source

Combine two key paths by gluing together the keys @return [KeyPath]

# File lib/nose/statements.rb, line 149
def +(other)
  fail TypeError unless other.is_a? KeyPath
  other_keys = other.instance_variable_get(:@keys)

  # Just copy if there's no combining necessary
  return dup if other_keys.empty?
  return other.dup if @keys.empty?

  # Only allow combining if the entities match
  fail ArgumentError unless other_keys.first.parent == entities.last

  # Combine the two paths
  KeyPath.new(@keys + other_keys[1..-1])
end
==(other, check_reverse = true) click to toggle source

Two key paths are equal if their underlying keys are equal or the reverse @return [Boolean]

# File lib/nose/statements.rb, line 127
def ==(other, check_reverse = true)
  @keys == other.instance_variable_get(:@keys) ||
    (check_reverse && reverse.send(:==, other.reverse, false))
end
Also aliased as: eql?
[](index) click to toggle source

Return a slice of the path @return [KeyPath]

# File lib/nose/statements.rb, line 166
def [](index)
  if index.is_a? Range
    keys = @keys[index]
    keys[0] = keys[0].entity.id_field \
      unless keys.empty? || keys[0].instance_of?(Fields::IDField)
    KeyPath.new(keys)
  else
    key = @keys[index]
    key = key.entity.id_field \
      unless key.nil? || key.instance_of?(Fields::IDField)
    key
  end
end
entities() click to toggle source

Return all the entities along the path @return [Array<Entity>]

# File lib/nose/statements.rb, line 200
def entities
  @entities ||= @keys.map(&:entity)
end
eql?(other, check_reverse = true)
Alias for: ==
find_field_parent(field) click to toggle source

Find the parent of a given field @Return [Entity]

# File lib/nose/statements.rb, line 238
def find_field_parent(field)
  parent = find do |key|
    field.parent == key.parent ||
      (key.is_a?(Fields::ForeignKeyField) && field.parent == key.entity)
  end

  # This field is not on this portion of the path, so skip
  return nil if parent.nil?

  parent = parent.parent unless parent.is_a?(Fields::ForeignKeyField)
  parent
end
include?(key) click to toggle source

Check if a key is included in the path @return [Boolean]

# File lib/nose/statements.rb, line 143
def include?(key)
  @keys.include?(key) || entities.any? { |e| e.id_field == key }
end
path_for_field(field) click to toggle source

Get the named path to reach this field through the list of keys @return [Array<String>]

# File lib/nose/statements.rb, line 228
def path_for_field(field)
  return [field.name] if @keys.first.parent == field.parent

  @keys.each_cons(2).take_while do |prev_key, _|
    prev_key.entity != field.parent
  end.map(&:last).map(&:name) << field.name
end
reverse() click to toggle source

Return the reverse of this path @return [KeyPath]

# File lib/nose/statements.rb, line 182
def reverse
  KeyPath.new reverse_path
end
reverse!() click to toggle source

Reverse this path in place @return [void]

# File lib/nose/statements.rb, line 188
def reverse!
  @keys = reverse_path
end
splice(target, entity) click to toggle source

Find where the path intersects the given entity and splice in the target path @return [KeyPath]

# File lib/nose/statements.rb, line 222
def splice(target, entity)
  split(entity) + target
end
split(entity) click to toggle source

Split the path where it intersects the given entity @return [KeyPath]

# File lib/nose/statements.rb, line 206
def split(entity)
  if first.parent == entity
    query_keys = KeyPath.new([entity.id_field])
  else
    query_keys = []
    each do |key|
      query_keys << key
      break if key.is_a?(Fields::ForeignKeyField) && key.entity == entity
    end
    query_keys = KeyPath.new(query_keys)
  end
end
start_with?(other, check_reverse = true) click to toggle source

Check if this path starts with another path @return [Boolean]

# File lib/nose/statements.rb, line 135
def start_with?(other, check_reverse = true)
  other_keys = other.instance_variable_get(:@keys)
  @keys[0..other_keys.length - 1] == other_keys ||
    (check_reverse && reverse.start_with?(other.reverse, false))
end
subpaths(include_self = true) click to toggle source

Produce all subpaths of this path @return [Enumerable<KeyPath>]

# File lib/nose/statements.rb, line 253
def subpaths(include_self = true)
  Enumerator.new do |enum|
    enum.yield self if include_self
    1.upto(@keys.length) do |i|
      i.upto(@keys.length) do |j|
        enum.yield self[i - 1..j - 1]
      end
    end
  end
end
to_a() click to toggle source

Simple wrapper so that we continue to be a KeyPath @return [KeyPath]

# File lib/nose/statements.rb, line 194
def to_a
  self
end

Private Instance Methods

reverse_path() click to toggle source

Get the reverse path @return [Array<Fields::Field>]

# File lib/nose/statements.rb, line 268
def reverse_path
  return [] if @keys.empty?
  [@keys.last.entity.id_field] + @keys[1..-1].reverse.map(&:reverse)
end