Class: ActiveGraph::Node::Query::QueryProxy

Inherits:
Object
  • Object
show all
Includes:
Dependent::QueryProxyMethods, QueryProxyEagerLoading, QueryProxyEnumerable, QueryProxyFindInBatches, QueryProxyMethods, QueryProxyMethodsOfMassUpdating
Defined in:
lib/active_graph/node/query/query_proxy.rb,
lib/active_graph/node/query/query_proxy_link.rb

Overview

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Classes: Link

Constant Summary collapse

METHODS =
%w(where where_not rel_where rel_where_not rel_order order skip limit)

Constants included from QueryProxyMethods

ActiveGraph::Node::Query::QueryProxyMethods::FIRST, ActiveGraph::Node::Query::QueryProxyMethods::LAST

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Dependent::QueryProxyMethods

#each_for_destruction, #unique_nodes

Methods included from QueryProxyEagerLoading

#association_tree_class, #first, #perform_query, #pluck_vars, #propagate_context, #with_associations, #with_associations_tree, #with_associations_tree=

Methods included from QueryProxyFindInBatches

#find_each, #find_in_batches

Methods included from QueryProxyMethodsOfMassUpdating

#add_rels, #delete, #delete_all, #delete_all_rels, #delete_rels_for_nodes, #destroy, #replace_with, #update_all, #update_all_rels

Methods included from QueryProxyMethods

#as, #as_models, #count, #distinct, #empty?, #exists?, #find, #find_or_create_by, #find_or_initialize_by, #first, #first_or_initialize, #first_rel_to, #having_rel, #include?, #last, #limit_value, #match_to, #not_having_rel, #optional, #order_property, #propagate_context, #rel, #rels, #rels_to, #size

Methods included from QueryProxyEnumerable

#==, #each, #each_rel, #each_with_rel, #fetch_result_cache, #pluck, #result, #result_cache?, #result_cache_for

Constructor Details

#initialize(model, association = nil, options = {}) ⇒ QueryProxy

QueryProxy is Node’s Cypher DSL. While the name might imply that it creates queries in a general sense, it is actually referring to ActiveGraph::Core::Query, which is a pure Ruby Cypher DSL provided by the activegraph gem. QueryProxy provides ActiveRecord-like methods for common patterns. When it’s not handling CRUD for relationships and queries, it provides Node’s association chaining (‘student.lessons.teachers.where(age: 30).hobbies`) and enjoys long walks on the beach.

It should not ever be necessary to instantiate a new QueryProxy object directly, it always happens as a result of calling a method that makes use of it.

originated. has_many) that created this object. QueryProxy objects are evaluated lazily.

Parameters:

  • model (Constant)

    The class which included Node (typically a model, hence the name) from which the query

  • association (ActiveGraph::Node::HasN::Association) (defaults to: nil)

    The Node association (an object created by a has_one or

  • options (Hash) (defaults to: {})

    Additional options pertaining to the QueryProxy object. These may include:

Options Hash (options):

  • :node_var (String, Symbol)

    A string or symbol to be used by Cypher within its query string as an identifier

  • :rel_var (String, Symbol)

    Same as above but pertaining to a relationship identifier

  • :rel_length (Range, Integer, Symbol, Hash)

    A Range, a Integer, a Hash or a Symbol to indicate the variable-length/fixed-length qualifier of the relationship. See neo4jrb.readthedocs.org/en/latest/Querying.html#variable-length-relationships.

  • :source_object (ActiveGraph::Node)

    The node instance at the start of the QueryProxy chain

  • :query_proxy (QueryProxy)

    An existing QueryProxy chain upon which this new object should be built



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/active_graph/node/query/query_proxy.rb', line 40

def initialize(model, association = nil, options = {})
  @model = model
  @association = association
  @context = options.delete(:context)
  @options = options
  @associations_spec = []

  instance_vars_from_options!(options)

  @match_type = @optional ? :optional_match : :match

  @rel_var = options[:rel] || _rel_chain_var

  @chain = []
  @params = @query_proxy ? @query_proxy.instance_variable_get('@params') : {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

QueryProxy objects act as a representation of a model at the class level so we pass through calls This allows us to define class functions for reusable query chaining or for end-of-query aggregation/summarizing



246
247
248
249
250
251
252
# File 'lib/active_graph/node/query/query_proxy.rb', line 246

def method_missing(method_name, *args, &block)
  if @model && @model.respond_to?(method_name)
    scoping { @model.public_send(method_name, *args, &block) }
  else
    super
  end
end

Instance Attribute Details

#associationObject (readonly)

The most recent node to start a QueryProxy chain. Will be nil when using QueryProxy chains on class methods.



16
17
18
# File 'lib/active_graph/node/query/query_proxy.rb', line 16

def association
  @association
end

#contextObject

Returns the value of attribute context.



262
263
264
# File 'lib/active_graph/node/query/query_proxy.rb', line 262

def context
  @context
end

#modelObject (readonly)

The most recent node to start a QueryProxy chain. Will be nil when using QueryProxy chains on class methods.



16
17
18
# File 'lib/active_graph/node/query/query_proxy.rb', line 16

def model
  @model
end

#node_varObject (readonly)

The current node identifier on deck, so to speak. It is the object that will be returned by calling each and the last node link in the QueryProxy chain.



66
67
68
# File 'lib/active_graph/node/query/query_proxy.rb', line 66

def node_var
  @node_var
end

#query_proxyObject (readonly)

Returns the value of attribute query_proxy.



62
63
64
# File 'lib/active_graph/node/query/query_proxy.rb', line 62

def query_proxy
  @query_proxy
end

#rel_varObject (readonly)

The relationship identifier most recently used by the QueryProxy chain.



73
74
75
# File 'lib/active_graph/node/query/query_proxy.rb', line 73

def rel_var
  @rel_var
end

#source_objectObject (readonly)

The most recent node to start a QueryProxy chain. Will be nil when using QueryProxy chains on class methods.



16
17
18
# File 'lib/active_graph/node/query/query_proxy.rb', line 16

def source_object
  @source_object
end

#start_objectObject (readonly)

Returns the value of attribute start_object.



62
63
64
# File 'lib/active_graph/node/query/query_proxy.rb', line 62

def start_object
  @start_object
end

#starting_queryObject (readonly)

The most recent node to start a QueryProxy chain. Will be nil when using QueryProxy chains on class methods.



16
17
18
# File 'lib/active_graph/node/query/query_proxy.rb', line 16

def starting_query
  @starting_query
end

Instance Method Details

#<<(other_node) ⇒ Object

To add a relationship for the node for the association on this QueryProxy



171
172
173
174
# File 'lib/active_graph/node/query/query_proxy.rb', line 171

def <<(other_node)
  _create_relation_or_defer(other_node)
  self
end

#[](index) ⇒ Object



197
198
199
200
201
# File 'lib/active_graph/node/query/query_proxy.rb', line 197

def [](index)
  # TODO: Maybe for this and other methods, use array if already loaded, otherwise
  # use OFFSET and LIMIT 1?
  self.to_a[index]
end

#_create_relationship(other_node_or_nodes, properties) ⇒ Object



234
235
236
# File 'lib/active_graph/node/query/query_proxy.rb', line 234

def _create_relationship(other_node_or_nodes, properties)
  association._create_relationship(@start_object, other_node_or_nodes, properties)
end

#_model_label_string(with_labels = true) ⇒ Object

param [TrueClass, FalseClass] with_labels This param is used by certain QueryProxy methods that already have the neo_id and therefore do not need labels. The @association_labels instance var is set during init and used during association chaining to keep labels out of Cypher queries.



122
123
124
125
# File 'lib/active_graph/node/query/query_proxy.rb', line 122

def _model_label_string(with_labels = true)
  return if !@model || (!with_labels || @association_labels == false)
  @model.mapped_label_names.map { |label_name| ":`#{label_name}`" }.join
end

#_nodeify!(*args) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/active_graph/node/query/query_proxy.rb', line 222

def _nodeify!(*args)
  other_nodes = [args].flatten!.map! do |arg|
    (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(id: arg) : arg
  end.compact

  if @model && other_nodes.any? { |other_node| !other_node.class.mapped_label_names.include?(@model.mapped_label_name) }
    fail ArgumentError, "Node must be of the association's class when model is specified"
  end

  other_nodes
end

#base_query(var, with_labels = true) ⇒ Object



109
110
111
112
113
114
115
116
117
# File 'lib/active_graph/node/query/query_proxy.rb', line 109

def base_query(var, with_labels = true)
  if @association
    chain_var = _association_chain_var
    (_association_query_start(chain_var) & _query).break.send(@match_type,
                                                              "(#{chain_var})#{_association_arrow}(#{var}#{_model_label_string})")
  else
    starting_query ? starting_query : _query_model_as(var, with_labels)
  end
end

#branch { ... } ⇒ QueryProxy

Executes the relation chain specified in the block, while keeping the current scope

Examples:

Load all people that have friends

Person.all.branch { friends }.to_a # => Returns a list of `Person`

Load all people that has old friends

Person.all.branch { friends.where('age > 70') }.to_a # => Returns a list of `Person`

Yields:

  • the block that will be evaluated starting from the current scope

Returns:



187
188
189
190
191
192
193
194
195
# File 'lib/active_graph/node/query/query_proxy.rb', line 187

def branch(&block)
  fail LocalJumpError, 'no block given' if block.nil?
  # `as(identity)` is here to make sure we get the right variable
  # There might be a deeper problem of the variable changing when we
  # traverse an association
  as(identity).instance_eval(&block).query.proxy_as(self.model, identity).tap do |new_query_proxy|
    propagate_context(new_query_proxy)
  end
end

#create(other_nodes, properties = {}) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/active_graph/node/query/query_proxy.rb', line 203

def create(other_nodes, properties = {})
  fail 'Can only create relationships on associations' if !@association
  other_nodes = _nodeify!(*other_nodes)

  ActiveGraph::Base.transaction do
    other_nodes.each do |other_node|
      if other_node.neo_id
        other_node.try(:delete_reverse_has_one_core_rel, association)
      else
        other_node.save
      end

      @start_object.association_proxy_cache.clear

      _create_relationship(other_node, properties)
    end
  end
end

#identityObject Also known as: node_identity



67
68
69
# File 'lib/active_graph/node/query/query_proxy.rb', line 67

def identity
  @node_var || _result_string(_chain_level + 1)
end

#inspectObject



57
58
59
60
# File 'lib/active_graph/node/query/query_proxy.rb', line 57

def inspect
  formatted_nodes = ActiveGraph::Node::NodeListFormatter.new(to_a)
  "#<QueryProxy #{@context} #{formatted_nodes.inspect}>"
end


264
265
266
267
268
269
# File 'lib/active_graph/node/query/query_proxy.rb', line 264

def new_link(node_var = nil)
  self.clone.tap do |new_query_proxy|
    new_query_proxy.instance_variable_set('@result_cache', nil)
    new_query_proxy.instance_variable_set('@node_var', node_var) if node_var
  end
end

#optional?Boolean

Returns:



258
259
260
# File 'lib/active_graph/node/query/query_proxy.rb', line 258

def optional?
  @optional == true
end

#params(params) ⇒ Object



80
81
82
# File 'lib/active_graph/node/query/query_proxy.rb', line 80

def params(params)
  new_link.tap { |new_query| new_query._add_params(params) }
end

#queryObject

Like calling #query_as, but for when you don’t care about the variable name



85
86
87
# File 'lib/active_graph/node/query/query_proxy.rb', line 85

def query
  query_as(identity)
end

#query_as(var, with_labels = true) ⇒ Object

Build a ActiveGraph::Core::Query object for the QueryProxy. This is necessary when you want to take an existing QueryProxy chain and work with it from the more powerful (but less friendly) ActiveGraph::Core::Query.

.. code-block

ruby

student.lessons.query_as(:l).with('your cypher here...')

Parameters:

  • var (String, Symbol)

    The identifier to use for node at this link of the QueryProxy chain.



96
97
98
99
# File 'lib/active_graph/node/query/query_proxy.rb', line 96

def query_as(var, with_labels = true)
  query_from_chain(chain, base_query(var, with_labels).params(@params), var)
    .tap { |query| query.proxy_chain_level = _chain_level }
end

#query_from_chain(chain, base_query, var) ⇒ Object



101
102
103
104
105
106
107
# File 'lib/active_graph/node/query/query_proxy.rb', line 101

def query_from_chain(chain, base_query, var)
  chain.inject(base_query) do |query, link|
    args = link.args(var, rel_var)

    args.is_a?(Array) ? query.send(link.clause, *args) : query.send(link.clause, args)
  end
end

#read_attribute_for_serialization(*args) ⇒ Object



238
239
240
# File 'lib/active_graph/node/query/query_proxy.rb', line 238

def read_attribute_for_serialization(*args)
  to_a.map { |o| o.read_attribute_for_serialization(*args) }
end

#rel_identityObject



74
75
76
77
78
# File 'lib/active_graph/node/query/query_proxy.rb', line 74

def rel_identity
  ActiveSupport::Deprecation.warn 'rel_identity is deprecated and may be removed from future releases, use rel_var instead.', caller

  @rel_var
end

#respond_to_missing?(method_name, include_all = false) ⇒ Boolean

Returns:



254
255
256
# File 'lib/active_graph/node/query/query_proxy.rb', line 254

def respond_to_missing?(method_name, include_all = false)
  (@model && @model.respond_to?(method_name, include_all)) || super
end

#scopingObject

Scope all queries to the current scope.

.. code-block

ruby

Comment.where(post_id: 1).scoping do
  Comment.first
end

TODO: unscoped Please check unscoped if you want to remove all previous scopes (including the default_scope) during the execution of a block.



138
139
140
141
142
143
144
# File 'lib/active_graph/node/query/query_proxy.rb', line 138

def scoping
  previous = @model.current_scope
  @model.current_scope = self
  yield
ensure
  @model.current_scope = previous
end

#to_cypher_with_params(columns = [self.identity]) ⇒ String

Returns a string of the cypher query with return objects and params

Parameters:

  • columns (Array) (defaults to: [self.identity])

    array containing symbols of identifiers used in the query

Returns:

  • (String)


165
166
167
168
# File 'lib/active_graph/node/query/query_proxy.rb', line 165

def to_cypher_with_params(columns = [self.identity])
  final_query = query.return_query(columns)
  "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}"
end

#unpersisted_start_object?Boolean

Returns:



271
272
273
# File 'lib/active_graph/node/query/query_proxy.rb', line 271

def unpersisted_start_object?
  @start_object && @start_object.new_record?
end