Class: NoSE::KeyPath

Inherits:
Object show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/nose/statements.rb

Overview

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

Instance Method Summary collapse

Methods included from Enumerable

#partitions, #prefixes, #product_by, #sum_by

Constructor Details

#initialize(keys = []) ⇒ KeyPath

Returns a new instance of KeyPath.



112
113
114
115
116
117
118
119
120
121
122
123
# 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

Instance Method Details

#+(other) ⇒ KeyPath

Combine two key paths by gluing together the keys

Returns:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 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) ⇒ Boolean Also known as: eql?

Two key paths are equal if their underlying keys are equal or the reverse

Returns:

  • (Boolean)


127
128
129
130
# 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

#[](index) ⇒ KeyPath

Return a slice of the path

Returns:



166
167
168
169
170
171
172
173
174
175
176
177
178
# 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

#entitiesArray<Entity>

Return all the entities along the path

Returns:



200
201
202
# File 'lib/nose/statements.rb', line 200

def entities
  @entities ||= @keys.map(&:entity)
end

#find_field_parent(field) ⇒ Object

Find the parent of a given field



238
239
240
241
242
243
244
245
246
247
248
249
# 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) ⇒ Boolean

Check if a key is included in the path

Returns:

  • (Boolean)


143
144
145
# 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) ⇒ Array<String>

Get the named path to reach this field through the list of keys

Returns:



228
229
230
231
232
233
234
# 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

#reverseKeyPath

Return the reverse of this path

Returns:



182
183
184
# File 'lib/nose/statements.rb', line 182

def reverse
  KeyPath.new reverse_path
end

#reverse!void

This method returns an undefined value.

Reverse this path in place



188
189
190
# File 'lib/nose/statements.rb', line 188

def reverse!
  @keys = reverse_path
end

#splice(target, entity) ⇒ KeyPath

Find where the path intersects the given entity and splice in the target path

Returns:



222
223
224
# File 'lib/nose/statements.rb', line 222

def splice(target, entity)
  split(entity) + target
end

#split(entity) ⇒ KeyPath

Split the path where it intersects the given entity

Returns:



206
207
208
209
210
211
212
213
214
215
216
217
# 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) ⇒ Boolean

Check if this path starts with another path

Returns:

  • (Boolean)


135
136
137
138
139
# 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) ⇒ Enumerable<KeyPath>

Produce all subpaths of this path

Returns:



253
254
255
256
257
258
259
260
261
262
# 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_aKeyPath

Simple wrapper so that we continue to be a KeyPath

Returns:



194
195
196
# File 'lib/nose/statements.rb', line 194

def to_a
  self
end