class NoSE::KeyPath
A path from a primary key to a chain of foreign keys
Public Class Methods
# 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
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
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
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
Return all the entities along the path @return [Array<Entity>]
# File lib/nose/statements.rb, line 200 def entities @entities ||= @keys.map(&:entity) end
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
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
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
Return the reverse of this path @return [KeyPath]
# File lib/nose/statements.rb, line 182 def reverse KeyPath.new reverse_path end
Reverse this path in place @return [void]
# File lib/nose/statements.rb, line 188 def reverse! @keys = reverse_path end
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 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
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
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
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
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