class ActiveGraph::Node::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 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.
@param [Constant] model The class which included Node
(typically a model, hence the name) from which the query originated. @param [ActiveGraph::Node::HasN::Association] association The Node
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 [ActiveGraph::Node] :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/active_graph/node/query/query_proxy.rb 40 def initialize(model, association = nil, options = {}) 41 @model = model 42 @association = association 43 @context = options.delete(:context) 44 @options = options 45 @associations_spec = [] 46 47 instance_vars_from_options!(options) 48 49 @match_type = @optional ? :optional_match : :match 50 51 @rel_var = options[:rel] || _rel_chain_var 52 53 @chain = [] 54 @params = @query_proxy ? @query_proxy.instance_variable_get('@params') : {} 55 end
Public Instance Methods
To add a relationship for the node for the association on this QueryProxy
# File lib/active_graph/node/query/query_proxy.rb 171 def <<(other_node) 172 _create_relation_or_defer(other_node) 173 self 174 end
# File lib/active_graph/node/query/query_proxy.rb 197 def [](index) 198 # TODO: Maybe for this and other methods, use array if already loaded, otherwise 199 # use OFFSET and LIMIT 1? 200 self.to_a[index] 201 end
# File lib/active_graph/node/query/query_proxy.rb 234 def _create_relationship(other_node_or_nodes, properties) 235 association._create_relationship(@start_object, other_node_or_nodes, properties) 236 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/active_graph/node/query/query_proxy.rb 122 def _model_label_string(with_labels = true) 123 return if !@model || (!with_labels || @association_labels == false) 124 @model.mapped_label_names.map { |label_name| ":`#{label_name}`" }.join 125 end
# File lib/active_graph/node/query/query_proxy.rb 222 def _nodeify!(*args) 223 other_nodes = [args].flatten!.map! do |arg| 224 (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(id: arg) : arg 225 end.compact 226 227 if @model && other_nodes.any? { |other_node| !other_node.class.mapped_label_names.include?(@model.mapped_label_name) } 228 fail ArgumentError, "Node must be of the association's class when model is specified" 229 end 230 231 other_nodes 232 end
# File lib/active_graph/node/query/query_proxy.rb 109 def base_query(var, with_labels = true) 110 if @association 111 chain_var = _association_chain_var 112 (_association_query_start(chain_var) & _query).break.send(@match_type, 113 "(#{chain_var})#{_association_arrow}(#{var}#{_model_label_string})") 114 else 115 starting_query ? starting_query : _query_model_as(var, with_labels) 116 end 117 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/active_graph/node/query/query_proxy.rb 187 def branch(&block) 188 fail LocalJumpError, 'no block given' if block.nil? 189 # `as(identity)` is here to make sure we get the right variable 190 # There might be a deeper problem of the variable changing when we 191 # traverse an association 192 as(identity).instance_eval(&block).query.proxy_as(self.model, identity).tap do |new_query_proxy| 193 propagate_context(new_query_proxy) 194 end 195 end
# File lib/active_graph/node/query/query_proxy.rb 203 def create(other_nodes, properties = {}) 204 fail 'Can only create relationships on associations' if !@association 205 other_nodes = _nodeify!(*other_nodes) 206 207 ActiveGraph::Base.transaction do 208 other_nodes.each do |other_node| 209 if other_node.neo_id 210 other_node.try(:delete_reverse_has_one_core_rel, association) 211 else 212 other_node.save 213 end 214 215 @start_object.association_proxy_cache.clear 216 217 _create_relationship(other_node, properties) 218 end 219 end 220 end
# File lib/active_graph/node/query/query_proxy.rb 67 def identity 68 @node_var || _result_string(_chain_level + 1) 69 end
# File lib/active_graph/node/query/query_proxy.rb 57 def inspect 58 formatted_nodes = ActiveGraph::Node::NodeListFormatter.new(to_a) 59 "#<QueryProxy #{@context} #{formatted_nodes.inspect}>" 60 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/active_graph/node/query/query_proxy.rb 246 def method_missing(method_name, *args, &block) 247 if @model && @model.respond_to?(method_name) 248 scoping { @model.public_send(method_name, *args, &block) } 249 else 250 super 251 end 252 end
# File lib/active_graph/node/query/query_proxy.rb 264 def new_link(node_var = nil) 265 self.clone.tap do |new_query_proxy| 266 new_query_proxy.instance_variable_set('@result_cache', nil) 267 new_query_proxy.instance_variable_set('@node_var', node_var) if node_var 268 end 269 end
# File lib/active_graph/node/query/query_proxy.rb 258 def optional? 259 @optional == true 260 end
# File lib/active_graph/node/query/query_proxy.rb 80 def params(params) 81 new_link.tap { |new_query| new_query._add_params(params) } 82 end
Like calling query_as
, but for when you don't care about the variable name
# File lib/active_graph/node/query/query_proxy.rb 85 def query 86 query_as(identity) 87 end
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
. @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/active_graph/node/query/query_proxy.rb 96 def query_as(var, with_labels = true) 97 query_from_chain(chain, base_query(var, with_labels).params(@params), var) 98 .tap { |query| query.proxy_chain_level = _chain_level } 99 end
# File lib/active_graph/node/query/query_proxy.rb 101 def query_from_chain(chain, base_query, var) 102 chain.inject(base_query) do |query, link| 103 args = link.args(var, rel_var) 104 105 args.is_a?(Array) ? query.send(link.clause, *args) : query.send(link.clause, args) 106 end 107 end
# File lib/active_graph/node/query/query_proxy.rb 238 def read_attribute_for_serialization(*args) 239 to_a.map { |o| o.read_attribute_for_serialization(*args) } 240 end
# File lib/active_graph/node/query/query_proxy.rb 74 def rel_identity 75 ActiveSupport::Deprecation.warn 'rel_identity is deprecated and may be removed from future releases, use rel_var instead.', caller 76 77 @rel_var 78 end
# File lib/active_graph/node/query/query_proxy.rb 254 def respond_to_missing?(method_name, include_all = false) 255 (@model && @model.respond_to?(method_name, include_all)) || super 256 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/active_graph/node/query/query_proxy.rb 138 def scoping 139 previous = @model.current_scope 140 @model.current_scope = self 141 yield 142 ensure 143 @model.current_scope = previous 144 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/active_graph/node/query/query_proxy.rb 165 def to_cypher_with_params(columns = [self.identity]) 166 final_query = query.return_query(columns) 167 "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}" 168 end
# File lib/active_graph/node/query/query_proxy.rb 271 def unpersisted_start_object? 272 @start_object && @start_object.new_record? 273 end
Protected Instance Methods
# File lib/active_graph/node/query/query_proxy.rb 292 def _add_links(links) 293 @chain += links 294 end
Methods are underscored to prevent conflict with user class methods
# File lib/active_graph/node/query/query_proxy.rb 288 def _add_params(params) 289 @params = @params.merge(params) 290 end
# File lib/active_graph/node/query/query_proxy.rb 319 def _association_arrow(properties = {}, create = false) 320 @association && @association.arrow_cypher(@rel_var, properties, create, false, @rel_length) 321 end
# File lib/active_graph/node/query/query_proxy.rb 327 def _association_chain_var 328 fail 'Crazy error' if !(start_object || @query_proxy) 329 330 if start_object 331 :"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}" 332 else 333 @query_proxy.node_var || :"node#{_chain_level}" 334 end 335 end
# File lib/active_graph/node/query/query_proxy.rb 337 def _association_query_start(var) 338 # TODO: Better error 339 fail 'Crazy error' if !(object = (start_object || @query_proxy)) 340 341 object.query_as(var) 342 end
# File lib/active_graph/node/query/query_proxy.rb 323 def _chain_level 324 (@query_proxy ? @query_proxy._chain_level : (@chain_level || 0)) + 1 325 end
# File lib/active_graph/node/query/query_proxy.rb 277 def _create_relation_or_defer(other_node) 278 if @start_object._persisted_obj 279 create(other_node, {}) 280 elsif @association 281 @start_object.defer_create(@association.name, other_node) 282 else 283 fail 'Another crazy error!' 284 end 285 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/active_graph/node/query/query_proxy.rb 302 def _match_arg(var, with_labels) 303 if @model && with_labels != false 304 labels = @model.respond_to?(:mapped_label_names) ? _model_label_string : @model 305 {var.to_sym => labels} 306 else 307 var.to_sym 308 end 309 end
# File lib/active_graph/node/query/query_proxy.rb 311 def _query 312 ActiveGraph::Base.new_query(context: @context) 313 end
# File lib/active_graph/node/query/query_proxy.rb 296 def _query_model_as(var, with_labels = true) 297 _query.break.send(@match_type, _match_arg(var, with_labels)) 298 end
# File lib/active_graph/node/query/query_proxy.rb 344 def _rel_chain_var 345 :"rel#{_chain_level - 1}" 346 end
# File lib/active_graph/node/query/query_proxy.rb 315 def _result_string(index = nil) 316 "result_#{(association || model).try(:name)}#{index}".downcase.tr(':', '').to_sym 317 end
Private Instance Methods
# File lib/active_graph/node/query/query_proxy.rb 359 def build_deeper_query_proxy(method, args) 360 new_link.tap do |new_query_proxy| 361 Link.for_args(@model, method, args, association).each { |link| new_query_proxy._add_links(link) } 362 end 363 end
# File lib/active_graph/node/query/query_proxy.rb 352 def instance_vars_from_options!(options) 353 @node_var, @source_object, @starting_query, @optional, 354 @start_object, @query_proxy, @chain_level, @association_labels, 355 @rel_length = options.values_at(:node, :source_object, :starting_query, :optional, 356 :start_object, :query_proxy, :chain_level, :association_labels, :rel_length) 357 end