class Neo4j::ActiveNode::Query::QueryProxy
rubocop:disable Metrics/ClassLength
Constants
- METHODS
Attributes
The most recent node to start a QueryProxy
chain. Will be nil when using QueryProxy
chains on class methods.
The most recent node to start a QueryProxy
chain. Will be nil when using QueryProxy
chains on class methods.
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.
The relationship identifier most recently used by the QueryProxy
chain.
The most recent node to start a QueryProxy
chain. Will be nil when using QueryProxy
chains on class methods.
The most recent node to start a QueryProxy
chain. Will be nil when using QueryProxy
chains on class methods.
Public Class Methods
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.
@param [Constant] model The class which included ActiveNode
(typically a model, hence the name) from which the query originated. @param [Neo4j::ActiveNode::HasN::Association] association The ActiveNode
association (an object created by a has_one
or has_many
) that created this object. @param [Hash] options Additional options pertaining to the QueryProxy
object. These may include: @option options [String, Symbol] :node_var A string or symbol to be used by Cypher within its query string as an identifier @option options [String, Symbol] :rel_var Same as above but pertaining to a relationship identifier @option options [Range, Integer, Symbol, Hash] :rel_length A Range, a Integer, a Hash or a Symbol to indicate the variable-length/fixed-length
qualifier of the relationship. See http://neo4jrb.readthedocs.org/en/latest/Querying.html#variable-length-relationships.
@option options [Neo4j::Session] :session The session to be used for this query @option options [Neo4j::ActiveNode] :source_object The node instance at the start of the QueryProxy
chain @option options [QueryProxy] :query_proxy An existing QueryProxy
chain upon which this new object should be built
QueryProxy
objects are evaluated lazily.
# File lib/neo4j/active_node/query/query_proxy.rb 41 def initialize(model, association = nil, options = {}) 42 @model = model 43 @association = association 44 @context = options.delete(:context) 45 @options = options 46 @associations_spec = [] 47 48 instance_vars_from_options!(options) 49 50 @match_type = @optional ? :optional_match : :match 51 52 @rel_var = options[:rel] || _rel_chain_var 53 54 @chain = [] 55 @params = @query_proxy ? @query_proxy.instance_variable_get('@params') : {} 56 end
Public Instance Methods
To add a relationship for the node for the association on this QueryProxy
# File lib/neo4j/active_node/query/query_proxy.rb 172 def <<(other_node) 173 _create_relation_or_defer(other_node) 174 self 175 end
# File lib/neo4j/active_node/query/query_proxy.rb 198 def [](index) 199 # TODO: Maybe for this and other methods, use array if already loaded, otherwise 200 # use OFFSET and LIMIT 1? 201 self.to_a[index] 202 end
# File lib/neo4j/active_node/query/query_proxy.rb 235 def _create_relationship(other_node_or_nodes, properties) 236 association._create_relationship(@start_object, other_node_or_nodes, properties) 237 end
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.
# File lib/neo4j/active_node/query/query_proxy.rb 123 def _model_label_string(with_labels = true) 124 return if !@model || (!with_labels || @association_labels == false) 125 @model.mapped_label_names.map { |label_name| ":`#{label_name}`" }.join 126 end
# File lib/neo4j/active_node/query/query_proxy.rb 223 def _nodeify!(*args) 224 other_nodes = [args].flatten!.map! do |arg| 225 (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(id: arg) : arg 226 end.compact 227 228 if @model && other_nodes.any? { |other_node| !other_node.class.mapped_label_names.include?(@model.mapped_label_name) } 229 fail ArgumentError, "Node must be of the association's class when model is specified" 230 end 231 232 other_nodes 233 end
# File lib/neo4j/active_node/query/query_proxy.rb 110 def base_query(var, with_labels = true) 111 if @association 112 chain_var = _association_chain_var 113 (_association_query_start(chain_var) & _query).break.send(@match_type, 114 "(#{chain_var})#{_association_arrow}(#{var}#{_model_label_string})") 115 else 116 starting_query ? starting_query : _query_model_as(var, with_labels) 117 end 118 end
Executes the relation chain specified in the block, while keeping the current scope
@example Load all people that have friends
Person.all.branch { friends }.to_a # => Returns a list of `Person`
@example Load all people that has old friends
Person.all.branch { friends.where('age > 70') }.to_a # => Returns a list of `Person`
@yield the block that will be evaluated starting from the current scope
@return [QueryProxy] A new QueryProxy
# File lib/neo4j/active_node/query/query_proxy.rb 188 def branch(&block) 189 fail LocalJumpError, 'no block given' if block.nil? 190 # `as(identity)` is here to make sure we get the right variable 191 # There might be a deeper problem of the variable changing when we 192 # traverse an association 193 as(identity).instance_eval(&block).query.proxy_as(self.model, identity).tap do |new_query_proxy| 194 propagate_context(new_query_proxy) 195 end 196 end
# File lib/neo4j/active_node/query/query_proxy.rb 204 def create(other_nodes, properties = {}) 205 fail 'Can only create relationships on associations' if !@association 206 other_nodes = _nodeify!(*other_nodes) 207 208 Neo4j::ActiveBase.run_transaction do 209 other_nodes.each do |other_node| 210 if other_node.neo_id 211 other_node.try(:validate_reverse_has_one_core_rel, association, @start_object) 212 else 213 other_node.save 214 end 215 216 @start_object.association_proxy_cache.clear 217 218 _create_relationship(other_node, properties) 219 end 220 end 221 end
# File lib/neo4j/active_node/query/query_proxy.rb 68 def identity 69 @node_var || _result_string(_chain_level + 1) 70 end
# File lib/neo4j/active_node/query/query_proxy.rb 58 def inspect 59 formatted_nodes = Neo4j::ActiveNode::NodeListFormatter.new(to_a) 60 "#<QueryProxy #{@context} #{formatted_nodes.inspect}>" 61 end
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
# File lib/neo4j/active_node/query/query_proxy.rb 247 def method_missing(method_name, *args, &block) 248 if @model && @model.respond_to?(method_name) 249 scoping { @model.public_send(method_name, *args, &block) } 250 else 251 super 252 end 253 end
# File lib/neo4j/active_node/query/query_proxy.rb 265 def new_link(node_var = nil) 266 self.clone.tap do |new_query_proxy| 267 new_query_proxy.instance_variable_set('@result_cache', nil) 268 new_query_proxy.instance_variable_set('@node_var', node_var) if node_var 269 end 270 end
# File lib/neo4j/active_node/query/query_proxy.rb 259 def optional? 260 @optional == true 261 end
# File lib/neo4j/active_node/query/query_proxy.rb 81 def params(params) 82 new_link.tap { |new_query| new_query._add_params(params) } 83 end
Like calling query_as
, but for when you don't care about the variable name
# File lib/neo4j/active_node/query/query_proxy.rb 86 def query 87 query_as(identity) 88 end
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
. @param [String,Symbol] var The identifier to use for node at this link of the QueryProxy
chain.
- .. code-block
-
ruby
student.lessons.query_as(:l).with('your cypher here...')
# File lib/neo4j/active_node/query/query_proxy.rb 97 def query_as(var, with_labels = true) 98 query_from_chain(chain, base_query(var, with_labels).params(@params), var) 99 .tap { |query| query.proxy_chain_level = _chain_level } 100 end
# File lib/neo4j/active_node/query/query_proxy.rb 102 def query_from_chain(chain, base_query, var) 103 chain.inject(base_query) do |query, link| 104 args = link.args(var, rel_var) 105 106 args.is_a?(Array) ? query.send(link.clause, *args) : query.send(link.clause, args) 107 end 108 end
# File lib/neo4j/active_node/query/query_proxy.rb 239 def read_attribute_for_serialization(*args) 240 to_a.map { |o| o.read_attribute_for_serialization(*args) } 241 end
# File lib/neo4j/active_node/query/query_proxy.rb 75 def rel_identity 76 ActiveSupport::Deprecation.warn 'rel_identity is deprecated and may be removed from future releases, use rel_var instead.', caller 77 78 @rel_var 79 end
# File lib/neo4j/active_node/query/query_proxy.rb 255 def respond_to_missing?(method_name, include_all = false) 256 (@model && @model.respond_to?(method_name, include_all)) || super 257 end
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.
# File lib/neo4j/active_node/query/query_proxy.rb 139 def scoping 140 previous = @model.current_scope 141 @model.current_scope = self 142 yield 143 ensure 144 @model.current_scope = previous 145 end
Returns a string of the cypher query with return objects and params @param [Array] columns array containing symbols of identifiers used in the query @return [String]
# File lib/neo4j/active_node/query/query_proxy.rb 166 def to_cypher_with_params(columns = [self.identity]) 167 final_query = query.return_query(columns) 168 "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}" 169 end
# File lib/neo4j/active_node/query/query_proxy.rb 272 def unpersisted_start_object? 273 @start_object && @start_object.new_record? 274 end
Protected Instance Methods
# File lib/neo4j/active_node/query/query_proxy.rb 293 def _add_links(links) 294 @chain += links 295 end
Methods are underscored to prevent conflict with user class methods
# File lib/neo4j/active_node/query/query_proxy.rb 289 def _add_params(params) 290 @params = @params.merge(params) 291 end
# File lib/neo4j/active_node/query/query_proxy.rb 326 def _association_arrow(properties = {}, create = false) 327 @association && @association.arrow_cypher(@rel_var, properties, create, false, @rel_length) 328 end
# File lib/neo4j/active_node/query/query_proxy.rb 334 def _association_chain_var 335 fail 'Crazy error' if !(start_object || @query_proxy) 336 337 if start_object 338 :"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}" 339 else 340 @query_proxy.node_var || :"node#{_chain_level}" 341 end 342 end
# File lib/neo4j/active_node/query/query_proxy.rb 344 def _association_query_start(var) 345 # TODO: Better error 346 fail 'Crazy error' if !(object = (start_object || @query_proxy)) 347 348 object.query_as(var) 349 end
# File lib/neo4j/active_node/query/query_proxy.rb 330 def _chain_level 331 (@query_proxy ? @query_proxy._chain_level : (@chain_level || 0)) + 1 332 end
# File lib/neo4j/active_node/query/query_proxy.rb 278 def _create_relation_or_defer(other_node) 279 if @start_object._persisted_obj 280 create(other_node, {}) 281 elsif @association 282 @start_object.defer_create(@association.name, other_node) 283 else 284 fail 'Another crazy error!' 285 end 286 end
@param [String, Symbol] var The Cypher identifier to use within the match string @param [Boolean] with_labels Send “true” to include model labels where possible.
# File lib/neo4j/active_node/query/query_proxy.rb 303 def _match_arg(var, with_labels) 304 if @model && with_labels != false 305 labels = @model.respond_to?(:mapped_label_names) ? _model_label_string : @model 306 {var.to_sym => labels} 307 else 308 var.to_sym 309 end 310 end
# File lib/neo4j/active_node/query/query_proxy.rb 312 def _query 313 Neo4j::ActiveBase.new_query(context: @context) 314 end
# File lib/neo4j/active_node/query/query_proxy.rb 297 def _query_model_as(var, with_labels = true) 298 _query.break.send(@match_type, _match_arg(var, with_labels)) 299 end
# File lib/neo4j/active_node/query/query_proxy.rb 351 def _rel_chain_var 352 :"rel#{_chain_level - 1}" 353 end
# File lib/neo4j/active_node/query/query_proxy.rb 316 def _result_string(index = nil) 317 "result_#{(association || model).try(:name)}#{index}".downcase.tr(':', '').to_sym 318 end
# File lib/neo4j/active_node/query/query_proxy.rb 320 def _session 321 (@session || (@model && @model.neo4j_session)).tap do |session| 322 fail 'No session found!' if session.nil? 323 end 324 end
Private Instance Methods
# File lib/neo4j/active_node/query/query_proxy.rb 366 def build_deeper_query_proxy(method, args) 367 new_link.tap do |new_query_proxy| 368 Link.for_args(@model, method, args, association).each { |link| new_query_proxy._add_links(link) } 369 end 370 end
# File lib/neo4j/active_node/query/query_proxy.rb 359 def instance_vars_from_options!(options) 360 @node_var, @session, @source_object, @starting_query, @optional, 361 @start_object, @query_proxy, @chain_level, @association_labels, 362 @rel_length = options.values_at(:node, :session, :source_object, :starting_query, :optional, 363 :start_object, :query_proxy, :chain_level, :association_labels, :rel_length) 364 end