class ActiveGraph::Core::Query
Allows for generation of cypher queries via ruby method calls (inspired by ActiveRecord / arel syntax)
Can be used to express cypher queries in ruby nicely, or to more easily generate queries programatically.
Also, queries can be passed around an application to progressively build a query across different concerns
See also the following link for full cypher language documentation: docs.neo4j.org/chunked/milestone/cypher-query-lang.html
Constants
- BREAK_METHODS
- CLAUSES
- CLAUSIFY_CLAUSE
- DEFINED_CLAUSES
- EMPTY
Returns a CYPHER query string from the object query representation @example
Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
@return [String] Resulting cypher query string
- METHODS
@method detach_delete *args DETACH DELETE clause @return [Query]
- NEWLINE
Attributes
For instances where you turn a QueryProxy into a Query
and then back to a QueryProxy with `#proxy_as`
Public Class Methods
# File lib/active_graph/core/query.rb 70 def initialize(options = {}) 71 @options = options 72 @clauses = [] 73 @_params = {} 74 @params = Parameters.new 75 end
Public Instance Methods
# File lib/active_graph/core/query.rb 374 def &(other) 375 self.class.new.tap do |new_query| 376 new_query.options = options.merge(other.options) 377 new_query.clauses = clauses + other.clauses 378 end.params(other._params) 379 end
Allows what's been built of the query so far to be frozen and the rest built anew. Can be called multiple times in a string of method calls @example
# Creates a query representing the cypher: MATCH (q:Person), r:Car MATCH (p: Person)-->q Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
# File lib/active_graph/core/query.rb 211 def break 212 build_deeper_query(nil) 213 end
# File lib/active_graph/core/query.rb 390 def clause?(method) 391 clause_class = DEFINED_CLAUSES[method] || CLAUSIFY_CLAUSE.call(method) 392 clauses.any? { |clause| clause.is_a?(clause_class) } 393 end
# File lib/active_graph/core/query.rb 343 def context 344 @options[:context] 345 end
# File lib/active_graph/core/query.rb 381 def copy 382 dup.tap do |query| 383 to_cypher 384 query.instance_variable_set('@params'.freeze, @params.copy) 385 query.instance_variable_set('@partitioned_clauses'.freeze, nil) 386 query.instance_variable_set('@response'.freeze, nil) 387 end 388 end
# File lib/active_graph/core/query.rb 260 def count(var = nil) 261 v = var.nil? ? '*' : var 262 pluck("count(#{v})").first 263 end
# File lib/active_graph/core/query.rb 265 def each 266 response.each { |object| yield object } 267 end
Executes a query without returning the result @return [Boolean] true if successful @raise [ActiveGraph::Server::CypherResponse::ResponseError] Raises errors from neo4j server
# File lib/active_graph/core/query.rb 278 def exec 279 response 280 281 true 282 end
# File lib/active_graph/core/query.rb 77 def inspect 78 "#<Query CYPHER: #{ANSI::YELLOW}#{to_cypher.inspect}#{ANSI::CLEAR}>" 79 end
# File lib/active_graph/core/query.rb 245 def match_nodes(hash, optional_match = false) 246 hash.inject(self) do |query, (variable, node_object)| 247 neo_id = (node_object.respond_to?(:neo_id) ? node_object.neo_id : node_object) 248 249 match_method = optional_match ? :optional_match : :match 250 query.send(match_method, variable).where(variable => {neo_id: neo_id}) 251 end 252 end
# File lib/active_graph/core/query.rb 254 def optional_match_nodes(hash) 255 match_nodes(hash, true) 256 end
# File lib/active_graph/core/query.rb 347 def parameters 348 to_cypher 349 merge_params 350 end
Allows for the specification of values for params specified in query @example
# Creates a query representing the cypher: MATCH (q: Person {id: $id}) # Calls to params don't affect the cypher query generated, but the params will be # Passed down when the query is made Query.new.match('(q: Person {id: $id})').params(id: 12)
# File lib/active_graph/core/query.rb 222 def params(args) 223 copy.tap { |new_query| new_query.instance_variable_get('@params'.freeze).add_params(args) } 224 end
# File lib/active_graph/core/query.rb 352 def partitioned_clauses 353 @partitioned_clauses ||= PartitionedClauses.new(@clauses) 354 end
Return the specified columns as an array. If one column is specified, a one-dimensional array is returned with the values of that column If two columns are specified, a n-dimensional array is returned with the values of those columns
@example
Query.new.match(n: :Person).return(p: :name}.pluck(p: :name) # => Array of names
@example
Query.new.match(n: :Person).return(p: :name}.pluck('p, DISTINCT p.name') # => Array of [node, name] pairs
# File lib/active_graph/core/query.rb 293 def pluck(*columns) 294 fail ArgumentError, 'No columns specified for Query#pluck' if columns.size.zero? 295 296 query = return_query(columns) 297 columns = query.response.keys 298 299 if columns.size == 1 300 column = columns[0] 301 query.map { |row| row[column] } 302 else 303 query.map { |row| columns.map { |column| row[column] } } 304 end 305 end
# File lib/active_graph/core/query.rb 339 def pretty_cypher 340 to_cypher(pretty: true) 341 end
# File lib/active_graph/core/query.rb 356 def print_cypher 357 puts to_cypher(pretty: true).gsub(/\e[^m]+m/, '') 358 end
Creates a ActiveGraph::Node::Query::QueryProxy
object that builds off of a Core::Query
object.
@param [Class] model An Node
model to be used as the start of a new QueryuProxy chain @param [Symbol] var The variable to be used to refer to the object from within the new QueryProxy @param [Boolean] optional Indicate whether the new QueryProxy will use MATCH or OPTIONAL MATCH. @return [ActiveGraph::Node::Query::QueryProxy] A QueryProxy object.
# File lib/active_graph/core/query_ext.rb 10 def proxy_as(model, var, optional = false) 11 # TODO: Discuss whether it's necessary to call `break` on the query or if this should be left to the user. 12 ActiveGraph::Node::Query::QueryProxy.new(model, nil, node: var, optional: optional, starting_query: self, chain_level: @proxy_chain_level) 13 end
Calls proxy_as
with `optional` set true. This doesn't offer anything different from calling `proxy_as` directly but it may be more readable.
# File lib/active_graph/core/query_ext.rb 16 def proxy_as_optional(model, var) 17 proxy_as(model, var, true) 18 end
# File lib/active_graph/core/query.rb 241 def raise_if_cypher_error!(response) 242 response.raise_cypher_error if response.respond_to?(:error?) && response.error? 243 end
Clears out previous order clauses and allows only for those specified by args
# File lib/active_graph/core/query.rb 186 def reorder(*args) 187 query = copy 188 189 query.remove_clause_class(OrderClause) 190 query.order(*args) 191 end
# File lib/active_graph/core/query.rb 235 def response 236 return @response if @response 237 238 @response = ActiveGraph::Base.query(self, wrap: !unwrapped?) 239 end
# File lib/active_graph/core/query.rb 307 def return_query(columns) 308 query = copy 309 query.remove_clause_class(ReturnClause) 310 311 query.return(*columns) 312 end
Works the same as the set method, but when given a nested array it will set properties rather than setting entire objects @example
# Creates a query representing the cypher: MATCH (n:Person) SET n.age = 19 Query.new.match(n: :Person).set_props(n: {age: 19})
# File lib/active_graph/core/query.rb 203 def set_props(*args) # rubocop:disable Naming/AccessorMethodName 204 build_deeper_query(SetClause, args, set_props: true) 205 end
# File lib/active_graph/core/query.rb 321 def to_cypher(options = {}) 322 join_string = options[:pretty] ? NEWLINE : EMPTY 323 324 cypher_string = partitioned_clauses.map do |clauses| 325 clauses_by_class = clauses.group_by(&:class) 326 327 cypher_parts = CLAUSES.map do |clause_class| 328 clause_class.to_cypher(clauses, options[:pretty]) if clauses = clauses_by_class[clause_class] 329 end.compact 330 331 cypher_parts.join(join_string).tap(&:strip!) 332 end.join(join_string) 333 334 cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser] 335 cypher_string.tap(&:strip!) 336 end
Returns a CYPHER query specifying the union of the callee object's query and the argument's query
@example
# Generates cypher: MATCH (n:Person) UNION MATCH (o:Person) WHERE o.age = 10 q = ActiveGraph::Core::Query.new.match(o: :Person).where(o: {age: 10}) result = ActiveGraph::Core::Query.new.match(n: :Person).union_cypher(q)
@param other [Query] Second half of UNION @param options [Hash] Specify {all: true} to use UNION ALL @return [String] Resulting UNION cypher query string
# File lib/active_graph/core/query.rb 370 def union_cypher(other, options = {}) 371 "#{to_cypher} UNION#{options[:all] ? ' ALL' : ''} #{other.to_cypher}" 372 end
# File lib/active_graph/core/query.rb 226 def unwrapped 227 @_unwrapped_obj = true 228 self 229 end
# File lib/active_graph/core/query.rb 231 def unwrapped? 232 !!@_unwrapped_obj 233 end
Works the same as the where method, but the clause is surrounded by a Cypher NOT() function
# File lib/active_graph/core/query.rb 195 def where_not(*args) 196 build_deeper_query(WhereClause, args, not: true) 197 end
Protected Instance Methods
# File lib/active_graph/core/query.rb 399 def add_clauses(clauses) 400 @clauses += clauses 401 end
# File lib/active_graph/core/query.rb 403 def remove_clause_class(clause_class) 404 @clauses = @clauses.reject { |clause| clause.is_a?(clause_class) } 405 end
Private Instance Methods
# File lib/active_graph/core/query.rb 409 def build_deeper_query(clause_class, args = {}, options = {}) 410 copy.tap do |new_query| 411 new_query.add_clauses [nil] if [nil, WithClause].include?(clause_class) 412 new_query.add_clauses clause_class.from_args(args, new_query.instance_variable_get('@params'.freeze), options) if clause_class 413 end 414 end
SHOULD BE DEPRECATED
# File lib/active_graph/core/query.rb 479 def merge_params 480 @merge_params_base ||= @clauses.compact.inject({}) { |params, clause| params.merge!(clause.params) } 481 @params.to_hash.merge(@merge_params_base) 482 end