Class: Squeel::Nodes::KeyPath

Inherits:
Node
  • Object
show all
Includes:
Operators, PredicateOperators
Defined in:
lib/squeel/nodes/key_path.rb

Overview

A node that stores a path of keys (of Symbol, Stub, or Join values) and an endpoint. Used similarly to a nested hash.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Node

#each, #grep

Constructor Details

#initialize(path, absolute = false) ⇒ KeyPath

Create a new KeyPath.

Parameters:

  • path (Array, Object)

    The intial path. Will be converted to an array if it isn't already.

  • absolute (Boolean) (defaults to: false)

    If the KeyPath should start from the base or remain relative to whatever location it's found.


25
26
27
28
29
# File 'lib/squeel/nodes/key_path.rb', line 25

def initialize(path, absolute = false)
  @path = Array(path)
  self.endpoint = Stub.new(endpoint) if Symbol === endpoint
  @absolute = absolute
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_id, *args, &block) ⇒ KeyPath

Appends to the KeyPath or delegates to the endpoint, as appropriate

Returns:

  • (KeyPath)

    The updated KeyPath


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/squeel/nodes/key_path.rb', line 186

def method_missing(method_id, *args, &block)
  super if method_id == :to_ary

  if endpoint.respond_to?(method_id)
    if Predicate === endpoint && method_id == :==
      false
    else
      # TODO: We really should not mutate here.
      self.endpoint = endpoint.send(method_id, *args)
      self
    end
  elsif Stub === endpoint || Join === endpoint
    if args.empty?
      @path << Stub.new(method_id)
    elsif (args.size == 1) && (Class === args[0])
      @path << Join.new(method_id, InnerJoin, args[0])
    else
      @path << Nodes::Function.new(method_id, args)
    end
    self
  else
    super
  end
end

Instance Attribute Details

#pathArray<Symbol, Stub, Join> (readonly)

Returns The path

Returns:


19
20
21
# File 'lib/squeel/nodes/key_path.rb', line 19

def path
  @path
end

Instance Method Details

#%(val) ⇒ KeyPath

Delegate % to the KeyPath's endpoint, with a bit of special logic for stubs or functions.

Parameters:

  • val

    The value to be supplied to the created/existing predicate

Returns:

  • (KeyPath)

    This KeyPath, with a predicate endpoint containing the given value


157
158
159
160
161
162
163
164
165
166
# File 'lib/squeel/nodes/key_path.rb', line 157

def %(val)
  case endpoint
  when Stub, Function
    Array === val ? self.in(val) : self.eq(val)
    self
  else
    endpoint % val
    self
  end
end

#&(other) ⇒ And

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to &

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (And)

    An And node with the KeyPath on the left side and the other object on the right.


69
70
71
# File 'lib/squeel/nodes/key_path.rb', line 69

def &(other)
  endpoint.respond_to?(:&) ? super : no_method_error(:&)
end

#*(other) ⇒ Operation

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to *

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (Operation)

    An operation (with operator *) with the KeyPath on its left and the other object on the right.


100
101
102
# File 'lib/squeel/nodes/key_path.rb', line 100

def *(other)
  endpoint.respond_to?(:*) ? super : no_method_error(:*)
end

#+(other) ⇒ Operation

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to +

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (Operation)

    An operation (with operator +) with the KeyPath on its left and the other object on the right.


84
85
86
# File 'lib/squeel/nodes/key_path.rb', line 84

def +(other)
  endpoint.respond_to?(:+) ? super : no_method_error(:+)
end

#-(other) ⇒ Operation

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to -

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (Operation)

    An operation (with operator -) with the KeyPath on its left and the other object on the right.


92
93
94
# File 'lib/squeel/nodes/key_path.rb', line 92

def -(other)
  endpoint.respond_to?(:-) ? super : no_method_error(:-)
end

#[email protected]Not

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to [email protected]

Returns:

  • (Not)

    A not node with the KeyPath as its expression


76
77
78
# File 'lib/squeel/nodes/key_path.rb', line 76

def [email protected]
  endpoint.respond_to?(:[email protected]) ? super : no_method_error(:[email protected])
end

#/(other) ⇒ Operation

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to /

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (Operation)

    An operation (with operator /) with the KeyPath on its left and the other object on the right.


108
109
110
# File 'lib/squeel/nodes/key_path.rb', line 108

def /(other)
  endpoint.respond_to?(:/) ? super : no_method_error(:/)
end

#absolute?Boolean

Whether or not the KeyPath should be interpreted relative to its current location

(if nested in a Hash, for instance) or as though it's at the base.

Returns:

  • (Boolean)

    The flag's value


34
35
36
# File 'lib/squeel/nodes/key_path.rb', line 34

def absolute?
  @absolute
end

#add_to_tree(hash) ⇒ Object


180
181
182
# File 'lib/squeel/nodes/key_path.rb', line 180

def add_to_tree(hash)
  walk_through_path(path.dup, hash)
end

#endpointObject

Returns The endpoint, either another key as in the path, or a predicate, function, etc.

Returns:

  • The endpoint, either another key as in the path, or a predicate, function, etc.


39
40
41
# File 'lib/squeel/nodes/key_path.rb', line 39

def endpoint
  @path[-1]
end

#endpoint=(val) ⇒ Object

Set the new value of the KeyPath's endpoint.

Parameters:

  • val (Object)

    The new endpoint.

Returns:

  • The value just set.


46
47
48
# File 'lib/squeel/nodes/key_path.rb', line 46

def endpoint=(val)
  @path[-1] = val
end

#eql?(other) ⇒ Boolean

Object comparison

Returns:

  • (Boolean)

51
52
53
54
55
# File 'lib/squeel/nodes/key_path.rb', line 51

def eql?(other)
  self.class.eql?(other.class) &&
  self.path.eql?(other.path) &&
  self.absolute?.eql?(other.absolute?)
end

#hashObject

For use with equality tests


142
143
144
# File 'lib/squeel/nodes/key_path.rb', line 142

def hash
  [self.class, *path].hash
end

#initialize_copy(orig) ⇒ Object (private)

Prevent a cloned keypath from inadvertently modifying the path of its source.


215
216
217
218
# File 'lib/squeel/nodes/key_path.rb', line 215

def initialize_copy(orig)
  super
  @path = @path.dup
end

#no_method_error(method_id) ⇒ Object (private)

Raises a NoMethodError manually, bypassing #method_missing. Used by special-case operator overrides.

Raises:

  • (NoMethodError)

227
228
229
# File 'lib/squeel/nodes/key_path.rb', line 227

def no_method_error(method_id)
  raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
end

#op(operator, other) ⇒ Operation

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to #op

Parameters:

  • operator (String, Symbol)

    The custom operator

  • other

    The right hand side of the operation

Returns:

  • (Operation)

    An operation with the given custom operator, the KeyPath on its left and the other object on the right.


117
118
119
# File 'lib/squeel/nodes/key_path.rb', line 117

def op(operator, other)
  endpoint.respond_to?(:op) ? super : no_method_error(:op)
end

#path_without_endpointArray

Returns The KeyPath's path, minus its endpoint, as a single array.

Returns:

  • (Array)

    The KeyPath's path, minus its endpoint, as a single array.


169
170
171
# File 'lib/squeel/nodes/key_path.rb', line 169

def path_without_endpoint
  path[0..-2]
end

#sift(name, *args) ⇒ KeyPath

Allow KeyPath to have a sifter as its endpoint, if the endpoint is a chainable node (Stub or Join)

Parameters:

  • name (Symbol)

    The name of the sifter

Returns:

  • (KeyPath)

    This keypath, with a sifter as its endpoint


125
126
127
128
129
130
131
132
# File 'lib/squeel/nodes/key_path.rb', line 125

def sift(name, *args)
  if Stub === endpoint || Join === endpoint
    @path << Sifter.new(name, args)
    self
  else
    no_method_error :sift
  end
end

#to_sObject Also known as: to_str

Implement #to_s (and alias to #to_str) to play nicely with Active Record grouped calculations


175
176
177
# File 'lib/squeel/nodes/key_path.rb', line 175

def to_s
  path.map(&:to_s).join('.')
end

#to_symNilClass

expand_hash_conditions_for_aggregates assumes our hash keys can be converted to symbols, so this has to be implemented, but it doesn't really have to do anything useful.

Returns:

  • (NilClass)

    Just to avoid bombing out on expand_hash_conditions_for_aggregates


150
151
152
# File 'lib/squeel/nodes/key_path.rb', line 150

def to_sym
  nil
end

#walk_through_path(path, hash) ⇒ Object (private)


220
221
222
223
# File 'lib/squeel/nodes/key_path.rb', line 220

def walk_through_path(path, hash)
  cache = path.shift.add_to_tree(hash)
  path.empty? ? cache : walk_through_path(path, cache)
end

#|(other) ⇒ Or

Allow KeyPath to function like its endpoint, in the case where its endpoint responds to |

Parameters:

  • other

    The right hand side of the operation

Returns:

  • (Or)

    An Or node with the KeyPath on the left side and the other object on the right.


61
62
63
# File 'lib/squeel/nodes/key_path.rb', line 61

def |(other)
  endpoint.respond_to?(:|) ? super : no_method_error(:|)
end

#~KeyPath

Set the absolute flag on this KeyPath

Returns:

  • (KeyPath)

    This keypath, with its absolute flag set to true


136
137
138
139
# File 'lib/squeel/nodes/key_path.rb', line 136

def ~
  @absolute = true
  self
end