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

Defined Under Namespace

Classes: Link

Constant Summary collapse

METHODS =
%w(where where_not rel_where 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

#each, #with_associations, #with_associations_spec

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_models, #count, #empty?, #exists?, #find, #find_or_create_by, #first, #first_rel_to, #include?, #last, #limit_value, #match_to, #optional, #order_property, #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, Fixnum, Symbol, Hash)

    A Range, a Fixnum, 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



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

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



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

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.



14
15
16
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 14

def association
  @association
end

#contextObject

Returns the value of attribute context.



261
262
263
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 261

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.



14
15
16
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 14

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.



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

def node_var
  @node_var
end

#query_proxyObject (readonly)

Returns the value of attribute query_proxy.



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

def query_proxy
  @query_proxy
end

#rel_varObject (readonly)

The relationship identifier most recently used by the QueryProxy chain.



71
72
73
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 71

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.



14
15
16
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 14

def source_object
  @source_object
end

#start_objectObject (readonly)

Returns the value of attribute start_object.



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

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.



14
15
16
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 14

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



166
167
168
169
170
171
172
173
174
175
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 166

def <<(other_node)
  if @start_object._persisted_obj
    create(other_node, {})
  elsif @association
    @start_object.defer_create(@association.name, other_node)
  else
    fail 'Another crazy error!'
  end
  self
end

#[](index) ⇒ Object



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

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



233
234
235
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 233

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.



117
118
119
120
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 117

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



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

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



104
105
106
107
108
109
110
111
112
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 104

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
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 188

def branch(&block)
  if block
    instance_eval(&block).query.proxy_as(self.model, identity)
  else
    fail LocalJumpError, 'no block given'
  end
end

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



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

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

  Neo4j::Transaction.run do
    other_nodes.each do |other_node|
      other_node.save unless other_node.neo_id

      return false if @association.perform_callback(@start_object, other_node, :before) == false

      @start_object.association_proxy_cache.clear

      _create_relationship(other_node, properties)

      @association.perform_callback(@start_object, other_node, :after)
    end
  end
end

#identityObject Also known as: node_identity



65
66
67
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 65

def identity
  @node_var || _result_string
end

#inspectObject



56
57
58
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 56

def inspect
  "#<QueryProxy #{@context} CYPHER: #{self.to_cypher.inspect}>"
end


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

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:



257
258
259
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 257

def optional?
  @optional == true
end

#params(params) ⇒ Object



78
79
80
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 78

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



83
84
85
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 83

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.



94
95
96
97
98
99
100
101
102
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 94

def query_as(var, with_labels = true)
  result_query = @chain.inject(base_query(var, with_labels).params(@params)) do |query, link|
    args = link.args(var, rel_var)

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

  result_query.tap { |query| query.proxy_chain_level = _chain_level }
end

#read_attribute_for_serialization(*args) ⇒ Object



237
238
239
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 237

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

#rel_identityObject



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

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:



253
254
255
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 253

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.



133
134
135
136
137
138
139
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 133

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)


160
161
162
163
# File 'lib/neo4j/active_node/query/query_proxy.rb', line 160

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