Class: Neo4j::ActiveNode::Query::QueryProxy

Inherits:
Object
  • Object
show all
Includes:
Dependent::QueryProxyMethods, QueryProxyEagerLoading, QueryProxyEnumerable, QueryProxyFindInBatches, QueryProxyMethods, QueryProxyMethodsOfMassUpdating
Defined in:
lib/neo4j/active_node/query/query_proxy.rb,
lib/neo4j/active_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

Neo4j::ActiveNode::Query::QueryProxyMethods::FIRST, Neo4j::ActiveNode::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

#delete, #delete_all, #delete_all_rels, #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 ActiveNode’s Cypher DSL. While the name might imply that it creates queries in a general sense, it is actually referring to Neo4j::Core::Query, which is a pure Ruby Cypher DSL provided by the neo4j-core gem. QueryProxy provides ActiveRecord-like methods for common patterns. When it’s not handling CRUD for relationships and queries, it provides ActiveNode’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 ActiveNode (typically a model, hence the name) from which the query

  • association (Neo4j::ActiveNode::HasN::Association) (defaults to: nil)

    The ActiveNode 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.

  • :session (Neo4j::Session)

    The session to be used for this query

  • :source_object (Neo4j::ActiveNode)

    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



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

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



247
248
249
250
251
252
253
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 247

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/neo4j/active_node/query/query_proxy.rb', line 16

def association
  @association
end

#contextObject

Returns the value of attribute context.



263
264
265
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 263

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/neo4j/active_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.



67
68
69
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 67

def node_var
  @node_var
end

#query_proxyObject (readonly)

Returns the value of attribute query_proxy.



63
64
65
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 63

def query_proxy
  @query_proxy
end

#rel_varObject (readonly)

The relationship identifier most recently used by the QueryProxy chain.



74
75
76
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 74

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/neo4j/active_node/query/query_proxy.rb', line 16

def source_object
  @source_object
end

#start_objectObject (readonly)

Returns the value of attribute start_object.



63
64
65
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 63

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/neo4j/active_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



172
173
174
175
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 172

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

#[](index) ⇒ Object



198
199
200
201
202
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 198

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



235
236
237
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 235

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.



123
124
125
126
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 123

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



223
224
225
226
227
228
229
230
231
232
233
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 223

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



110
111
112
113
114
115
116
117
118
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 110

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:



188
189
190
191
192
193
194
195
196
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 188

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



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

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

  Neo4j::ActiveBase.run_transaction do
    other_nodes.each do |other_node|
      if other_node.neo_id
        other_node.try(:validate_reverse_has_one_core_rel, association, @start_object)
      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



68
69
70
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 68

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

#inspectObject



58
59
60
61
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 58

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


265
266
267
268
269
270
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 265

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:



259
260
261
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 259

def optional?
  @optional == true
end

#params(params) ⇒ Object



81
82
83
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 81

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



86
87
88
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 86

def query
  query_as(identity)
end

#query_as(var, with_labels = true) ⇒ Object

Build a Neo4j::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) Neo4j::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.



97
98
99
100
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 97

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



102
103
104
105
106
107
108
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 102

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



239
240
241
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 239

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

#rel_identityObject



75
76
77
78
79
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 75

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:



255
256
257
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 255

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.



139
140
141
142
143
144
145
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 139

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)


166
167
168
169
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 166

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:



272
273
274
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 272

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